|
1 | 1 | // Cron Job Scheduler
|
2 |
| -// Copyright (c) 2017 - 2019 Sport Trades Ltd |
| 2 | +// Copyright (c) 2017 - 2020 Sport Trades Ltd, 2021 Jaskirat Rajasansir |
3 | 3 |
|
4 | 4 | // Documentation: https://github.com/BuaBook/kdb-common/wiki/cron.q
|
5 | 5 |
|
|
16 | 16 | / @see .cron.status
|
17 | 17 | .cron.cfg.logStatus:1b;
|
18 | 18 |
|
19 |
| -/ The supported run type for the cron system |
20 |
| -.cron.cfg.runners:()!(); |
21 |
| -.cron.cfg.runners[`once]:`.cron.i.runOnce; |
22 |
| -.cron.cfg.runners[`repeat]:`.cron.i.runRepeat; |
| 19 | +/ The mode of operaton for the cron system. There are 2 supported modes: |
| 20 | +/ * ticking: Traditional timer system with the timer function running on a frequent interval |
| 21 | +/ * tickless: New approach to only 'tick' the timer when the next job is due to run. Can reduce process load when infrequent jobs are run |
| 22 | +.cron.cfg.mode:`ticking; |
23 | 23 |
|
24 | 24 |
|
25 | 25 | / Unique job ID for each cron job added
|
|
34 | 34 | `.cron.status upsert @[first .cron.status; `result; :; (::)];
|
35 | 35 |
|
36 | 36 |
|
37 |
| -/ NOTE: The initialistion function will not overwrite .z.ts if it is already set. |
| 37 | +/ The supported run type for the cron system |
| 38 | +.cron.runners:(`symbol$())!`symbol$(); |
| 39 | +.cron.runners[`once]: `.cron.i.runOnce; |
| 40 | +.cron.runners[`repeat]:`.cron.i.runRepeat; |
| 41 | + |
| 42 | +/ The supported tick modes for the cron system |
| 43 | +.cron.supportedModes:(`symbol$())!`symbol$(); |
| 44 | +.cron.supportedModes[`ticking]: `.cron.mode.ticking; |
| 45 | +.cron.supportedModes[`tickless]:`.cron.mode.tickless; |
| 46 | + |
| 47 | +/ The maximum supported timer interval as a timespan |
| 48 | +.cron.maxTimerAsTimespan:.convert.msToTimespan 0Wi - 1; |
| 49 | + |
| 50 | +/ One millisecond as a timespan (to not require calculation each time) |
| 51 | +.cron.oneMsAsTimespan:.convert.msToTimespan 1; |
| 52 | + |
| 53 | + |
| 54 | +/ NOTE: If '.z.ts' is defined at initialisation, the function will short-circuit and not configure the library |
38 | 55 | .cron.init:{
|
39 | 56 | if[.ns.isSet `.z.ts;
|
40 | 57 | .log.if.warn "Timer function is already set. Cron will not override automatically";
|
41 | 58 | :(::);
|
42 | 59 | ];
|
43 | 60 |
|
44 | 61 | set[`.z.ts; .cron.ts];
|
45 |
| - .cron.enable[]; |
| 62 | + .cron.changeMode .cron.cfg.mode; |
46 | 63 |
|
47 | 64 | if[not `.cron.cleanStatus in exec func from .cron.jobs;
|
48 | 65 | .cron.addRepeatForeverJob[`.cron.cleanStatus; (::); `timestamp$.time.today[]+1; 1D];
|
49 | 66 | ];
|
50 | 67 | };
|
51 | 68 |
|
52 | 69 |
|
53 |
| -/ Starts the kdb timer to activate the cron system |
54 |
| -.cron.enable:{ |
55 |
| - if[0 < system"t"; |
56 |
| - :(::); |
| 70 | +/ Changes between the supported cron timer modes |
| 71 | +/ @param mode (Symbol) The cron timer mode to use |
| 72 | +/ @throws InvalidCronModeException If the mode is not one of the supported modes |
| 73 | +/ @see .cron.supportedModes |
| 74 | +.cron.changeMode:{[mode] |
| 75 | + if[not mode in key .cron.supportedModes; |
| 76 | + .log.if.error "Cron timer mode is invalid. Must be one of: ",.convert.listToString key .cron.supportedModes; |
| 77 | + '"InvalidCronModeException"; |
57 | 78 | ];
|
58 | 79 |
|
59 |
| - .log.if.info "Enabling cron job scheduler [ Timer Interval: ",string[.cron.cfg.timerInterval]," ms ]"; |
60 |
| - |
61 |
| - .util.system "t ",string .cron.cfg.timerInterval; |
62 |
| - }; |
| 80 | + .cron.cfg.mode:mode; |
| 81 | + .cron.supportedModes[.cron.cfg.mode][]; |
| 82 | + }; |
63 | 83 |
|
64 |
| -/ Disableds the kdb timer to deactivate the cron system |
| 84 | +/ Disables the kdb timer to deactivate the cron system |
65 | 85 | .cron.disable:{
|
66 |
| - if[0 = system"t"; |
67 |
| - :(::); |
68 |
| - ]; |
69 |
| - |
70 | 86 | .log.if.info "Disabling cron job scheduler";
|
71 | 87 | .log.if.warn " No scheduled jobs will be executed until cron is enabled again";
|
72 | 88 |
|
73 |
| - .util.system "t 0"; |
| 89 | + system "t 0"; |
74 | 90 | };
|
75 | 91 |
|
76 | 92 | / General job addition function. Adds a job to the cron system for execution
|
77 | 93 | / @param func (Symbol) Symbol reference to the function to execute
|
78 | 94 | / @param args () Any arguments that are required to execute the function. Pass generic null (::) for no arguments
|
79 |
| -/ @param runType (Symbol) The type of cron job to add. See .cron.cfg.runners |
80 |
| -/ @param startTime (Timestamp) The first time the job will be run |
81 |
| -/ @param endTime (Timestamp) The time to finish a repeating job executing. Pass null (0Np) to repeat forever or for one time jobs |
| 95 | +/ @param runType (Symbol) The type of cron job to add. See .cron.runners |
| 96 | +/ @param startTime (Timestamp) The first time the job will be run. NOTE: Timestamp will be rounded to the nearest millisecond |
| 97 | +/ @param endTime (Timestamp) The time to finish a repeating job executing. Pass null (0Np) to repeat forever or for one time jobs. NOTE: Timestamp will be rounded to the nearest millisecond |
82 | 98 | / @param interval (Timespan) The interval at which repeating jobs should recur. Pass null (0Nn) for one time jobs
|
83 | 99 | / @returns (Long) The ID of the new cron job
|
84 | 100 | / @throws InvalidCronJobIntervalException If the interval specified is smaller than the cron interval
|
85 | 101 | / @throws FunctionDoesNotExistFunction If the function for the cron job does not exist
|
86 | 102 | / @throws ReferenceIsNotAFunctionException If the symbol reference for the function is not actually a function
|
87 |
| -/ @throws InvalidCronRunTypeException If the run type specified is not present in .cron.cfg.runners |
| 103 | +/ @throws InvalidCronRunTypeException If the run type specified is not present in .cron.runners |
88 | 104 | / @throws InvalidCronJobTimeException If the start time specified is before the current time or the end time is before the start time
|
89 | 105 | .cron.add:{[func;args;runType;startTime;endTime;interval]
|
90 | 106 | if[not .ns.isSet func;
|
|
97 | 113 | '"ReferenceIsNotAFunctionException";
|
98 | 114 | ];
|
99 | 115 |
|
100 |
| - if[not runType in key .cron.cfg.runners; |
101 |
| - .log.if.error "Invalid cron run type. Expecting one of: ",.convert.listToString key .cron.cfg.runners; |
| 116 | + if[not runType in key .cron.runners; |
| 117 | + .log.if.error "Invalid cron run type. Expecting one of: ",.convert.listToString key .cron.runners; |
102 | 118 | '"InvalidCronRunTypeException";
|
103 | 119 | ];
|
104 | 120 |
|
105 |
| - if[startTime < .time.today[]+`second$.time.nowAsTime[]; |
| 121 | + if[not all .type.isTimestamp each (startTime; endTime); |
| 122 | + .log.if.error "Invalid start time or end time. Must be a timestamp"; |
| 123 | + '"InvalidCronJobTimeException"; |
| 124 | + ]; |
| 125 | + |
| 126 | + startTime:.time.roundTimestampToMs startTime; |
| 127 | + endTime:.time.roundTimestampToMs endTime; |
| 128 | + |
| 129 | + if[startTime < .time.nowAsMsRoundedTimestamp[]; |
106 | 130 | .log.if.error "Cron job start time is in the past. Cannot add job";
|
107 | 131 | '"InvalidCronJobTimeException";
|
108 | 132 | ];
|
|
111 | 135 | .log.if.error "Cron job end time specified is before the start time. Cannot add job";
|
112 | 136 | '"InvalidCronJobTimeException";
|
113 | 137 | ];
|
114 |
| - |
115 |
| - if[not[.util.isEmpty interval] & .cron.cfg.timerInterval > .convert.timespanToMs interval; |
116 |
| - .log.if.error "Cron job repeat interval is shorter than the cron timer interval. Cannot add job"; |
| 138 | + |
| 139 | + if[(`ticking = .cron.cfg.mode) & not[.util.isEmpty interval] & .cron.cfg.timerInterval > .convert.timespanToMs interval; |
| 140 | + .log.if.error "Cron job repeat interval is shorter than the cron timer interval (ticking). Cannot add job"; |
117 | 141 | '"InvalidCronJobIntervalException";
|
118 | 142 | ];
|
119 | 143 |
|
|
122 | 146 |
|
123 | 147 | `.cron.jobs upsert (jobId;func;args;runType;startTime;endTime;interval;startTime);
|
124 | 148 |
|
| 149 | + if[`tickless = .cron.cfg.mode; |
| 150 | + .cron.i.setNextTick[]; |
| 151 | + ]; |
| 152 | + |
125 | 153 | :jobId;
|
126 | 154 | };
|
127 | 155 |
|
|
164 | 192 | ];
|
165 | 193 |
|
166 | 194 | update nextRunTime:0Wp from `.cron.jobs where id = jobId;
|
| 195 | + |
| 196 | + if[`tickless = .cron.cfg.mode; |
| 197 | + .cron.i.setNextTick[]; |
| 198 | + ]; |
167 | 199 | };
|
168 | 200 |
|
169 | 201 | / Removes all entries from .cron.status and all jobs that will not run again. By default this is run at
|
|
177 | 209 | / The main cron function that is bound to .z.ts as part of the initialisation
|
178 | 210 | .cron.ts:{
|
179 | 211 | toRun:0!select id, runType from .cron.jobs where nextRunTime <= .time.now[];
|
180 |
| - { get[.cron.cfg.runners x`runType] x`id } each toRun; |
| 212 | + .cron.runners[toRun`runType] @' toRun`id; |
| 213 | + |
| 214 | + if[`tickless = .cron.cfg.mode; |
| 215 | + .cron.i.setNextTick[]; |
| 216 | + ]; |
181 | 217 | };
|
182 | 218 |
|
183 | 219 | / Execution function for jobs that only run once
|
|
245 | 281 | :status;
|
246 | 282 | };
|
247 | 283 |
|
| 284 | +/ Updates the 'tickless' timer tick based on the next run time. If no more cron jobs are scheduled to run, the timer will be disabled |
| 285 | +/ until a new job is added |
| 286 | +/ @see .cron.jobs |
| 287 | +/ @see .cron.oneMsAsTimespan |
| 288 | +/ @see .cron.maxTimerAsTimespan |
| 289 | +.cron.i.setNextTick:{ |
| 290 | + nextRun:exec min nextRunTime from .cron.jobs; |
| 291 | + |
| 292 | + if[.type.isInfinite nextRun; |
| 293 | + .log.if.trace "No active cron jobs scheduled. Disabling system timer"; |
| 294 | + system "t 0"; |
| 295 | + :(::); |
| 296 | + ]; |
| 297 | + |
| 298 | + / Always make sure the next timer tick: |
| 299 | + / * Is not 0 (so accidentally disabled) |
| 300 | + / * Is not greater than max integer - 1 |
| 301 | + timer:.cron.maxTimerAsTimespan & .cron.oneMsAsTimespan | nextRun - .time.roundTimestampToMs .time.now[]; |
| 302 | + timerMs:.convert.timespanToMs timer; |
| 303 | + |
| 304 | + if[timerMs = system "t"; |
| 305 | + :(::); |
| 306 | + ]; |
| 307 | + |
| 308 | + system "t ",string timerMs; |
| 309 | + |
| 310 | + .log.if.trace "Tickless cron timer updated [ Next Run: ",string[timer]," (",string[timerMs]," ms) ]"; |
| 311 | + }; |
| 312 | + |
| 313 | + |
| 314 | +/ Enables the 'ticking' cron mode |
| 315 | +/ NOTE: Does not validate the configured ticking mode |
| 316 | +/ @see .cron.cfg.timerInterval |
| 317 | +.cron.mode.ticking:{ |
| 318 | + .log.if.info "Enabling cron job scheduler [ Mode: Ticking ] [ Timer Interval: ",string[.cron.cfg.timerInterval]," ms ]"; |
| 319 | + system "t ",string .cron.cfg.timerInterval; |
| 320 | + }; |
| 321 | + |
| 322 | +/ Enables the 'tickless' cron mode |
| 323 | +/ NOTE: Does not validate the configured ticking mode |
| 324 | +/ @see .cron.i.setNextTick |
| 325 | +.cron.mode.tickless:{ |
| 326 | + .log.if.info "Enabling cron job scheduler [ Mode: Tickless ]"; |
| 327 | + .cron.i.setNextTick[]; |
| 328 | + }; |
0 commit comments