summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/minicron.cpp360
-rw-r--r--src/minicron.h226
-rw-r--r--src/tests/minicron.cpp53
3 files changed, 639 insertions, 0 deletions
diff --git a/src/minicron.cpp b/src/minicron.cpp
new file mode 100644
index 0000000..f20a382
--- /dev/null
+++ b/src/minicron.cpp
@@ -0,0 +1,360 @@
1#include "bu/minicron.h"
2
3#include <stdlib.h>
4#include <time.h>
5
6Bu::MiniCron::MiniCron() :
7 jidNext( 1 )
8{
9}
10
11Bu::MiniCron::~MiniCron()
12{
13}
14
15bool Bu::MiniCron::hasJobs()
16{
17 return !hJobs.isEmpty();
18}
19
20time_t Bu::MiniCron::getNextRun()
21{
22 if( hasJobs() )
23 return hJobs.peek()->getNextRun();
24 return -1;
25}
26
27void Bu::MiniCron::poll()
28{
29 time_t tNow = time( NULL );
30
31 while( !hJobs.isEmpty() )
32 {
33 if( hJobs.peek()->getNextRun() <= tNow )
34 {
35 Job *pJob = hJobs.dequeue();
36 pJob->run();
37 if( pJob->bContinue )
38 {
39 hJobs.enqueue( pJob );
40 }
41 else
42 {
43 delete pJob;
44 }
45 }
46 else
47 {
48 break;
49 }
50 }
51}
52
53Bu::MiniCron::JobId Bu::MiniCron::addJob( Bu::MiniCron::CronSignal sigJob,
54 const Bu::MiniCron::Timer &t )
55{
56 JobId jid = jidNext++;
57 Job *pJob = new Job( jid );
58 pJob->sigJob = sigJob;
59 pJob->pTimer = t.clone();
60 pJob->tNextRun = pJob->pTimer->nextTime();
61 hJobs.enqueue( pJob );
62
63 return jid;
64}
65
66Bu::MiniCron::JobId Bu::MiniCron::addJobOnce( Bu::MiniCron::CronSignal sigJob,
67 const Bu::MiniCron::Timer &t )
68{
69 JobId jid = jidNext++;
70 Job *pJob = new Job( jid, false );
71 pJob->sigJob = sigJob;
72 pJob->pTimer = t.clone();
73 pJob->tNextRun = pJob->pTimer->nextTime();
74 hJobs.enqueue( pJob );
75
76 return jid;
77}
78
79void Bu::MiniCron::removeJob( JobId jid )
80{
81 Bu::List<Job *> lJobs;
82 while( !hJobs.isEmpty() )
83 {
84 Job *pJob = hJobs.dequeue();
85 if( pJob->getId() == jid )
86 {
87 delete pJob;
88 }
89 else
90 lJobs.append( pJob );
91 }
92
93 for( Bu::List<Job *>::iterator i = lJobs.begin(); i; i++ )
94 {
95 hJobs.enqueue( *i );
96 }
97}
98
99Bu::MiniCron::Job::Job( JobId jid, bool bRepeat ) :
100 pTimer( NULL ),
101 bContinue( bRepeat ),
102 jid( jid ),
103 tAdded( time( NULL ) ),
104 iRunCount( 0 )
105{
106}
107
108Bu::MiniCron::Job::~Job()
109{
110 delete pTimer;
111 pTimer = NULL;
112}
113
114void Bu::MiniCron::Job::run()
115{
116 iRunCount++;
117 tNextRun = pTimer->nextTime();
118 sigJob( *this );
119}
120
121time_t Bu::MiniCron::Job::getNextRun()
122{
123 return tNextRun;
124}
125
126void Bu::MiniCron::Job::calcNextRun()
127{
128 if( pTimer )
129 tNextRun = pTimer->nextTime();
130}
131
132void Bu::MiniCron::Job::setTimer( const Timer &t )
133{
134 delete pTimer;
135 pTimer = t.clone();
136}
137
138void Bu::MiniCron::Job::stop()
139{
140 bContinue = false;
141}
142
143void Bu::MiniCron::Job::resume()
144{
145 bContinue = true;
146}
147
148Bu::MiniCron::JobId Bu::MiniCron::Job::getId()
149{
150 return jid;
151}
152
153time_t Bu::MiniCron::Job::getTimeCreated()
154{
155 return tAdded;
156}
157
158int Bu::MiniCron::Job::getRunCount()
159{
160 return iRunCount;
161}
162
163Bu::MiniCron::Timer::Timer()
164{
165}
166
167Bu::MiniCron::Timer::~Timer()
168{
169}
170
171Bu::MiniCron::TimerInterval::TimerInterval( time_t tFirst, time_t tInterval ) :
172 tNext( tFirst ),
173 tInterval( tInterval )
174{
175}
176
177Bu::MiniCron::TimerInterval::~TimerInterval()
178{
179}
180
181time_t Bu::MiniCron::TimerInterval::nextTime()
182{
183 time_t tRet = tNext;
184 tNext += tInterval;
185 return tRet;
186}
187
188Bu::MiniCron::TimerBasic::TimerBasic( const Bu::FString &s ) :
189 tLast( -1 ),
190 sSpec( s )
191{
192}
193
194Bu::MiniCron::TimerBasic::~TimerBasic()
195{
196}
197
198time_t Bu::MiniCron::TimerBasic::nextTime()
199{
200 if( tLast == -1 )
201 tLast = time( NULL );
202
203 Bu::FString::const_iterator i = sSpec.begin();
204 switch( lex( i ) )
205 {
206 case tokDaily:
207 {
208 int iHour = lexInt( i );
209 int iMin = lexInt( i );
210
211 struct tm t;
212 ::memcpy( &t, localtime( &tLast ), sizeof(struct tm) );
213 if( iHour < t.tm_hour ||
214 (iHour == t.tm_hour && iMin <= t.tm_min) )
215 {
216 t.tm_mday++;
217 }
218 t.tm_hour = iHour;
219 t.tm_min = iMin;
220 t.tm_sec = 0;
221 tLast = mktime( &t );
222 }
223 break;
224
225 case tokHourly:
226 {
227 int iMin = lexInt( i );
228
229 struct tm t;
230 ::memcpy( &t, localtime( &tLast ), sizeof(struct tm) );
231 if( iMin <= t.tm_min )
232 t.tm_hour++;
233 t.tm_min = iMin;
234 t.tm_sec = 0;
235 tLast = mktime( &t );
236 }
237 break;
238
239 case tokWeekly:
240 {
241 int iDay = lexInt( i );
242 int iHour = lexInt( i );
243 int iMin = lexInt( i );
244
245 struct tm t;
246 ::memcpy( &t, localtime( &tLast ), sizeof(struct tm) );
247 if( iDay < t.tm_wday ||
248 (iDay == t.tm_wday && iHour < t.tm_hour) ||
249 (iDay == t.tm_wday && iHour == t.tm_hour
250 && iMin <= t.tm_min) )
251 {
252 if( iDay <= t.tm_wday )
253 t.tm_mday += 7 - (t.tm_wday-iDay);
254 else
255 t.tm_mday += 7 - (iDay-t.tm_wday);
256 }
257 else
258 {
259 t.tm_mday += (iDay-t.tm_wday);
260 }
261 t.tm_hour = iHour;
262 t.tm_min = iMin;
263 t.tm_sec = 0;
264 tLast = mktime( &t );
265 }
266 break;
267
268 case tokMonthly:
269 break;
270
271 case tokYearly:
272 break;
273
274 default:
275 break;
276 }
277
278 return tLast;
279}
280
281Bu::MiniCron::TimerBasic::Token Bu::MiniCron::TimerBasic::lex(
282 Bu::FString::const_iterator &i )
283{
284 if( !i )
285 {
286 return tokEos;
287 }
288
289 Bu::FString::const_iterator b = i;
290
291 for(; b && (*b == ' ' || *b == '\t'); b++ ) { i = b+1; }
292 for(; b && *b != ' ' && *b != '\t'; b++ ) { }
293
294 Bu::FString sTok( i, b );
295 i = b;
296
297 if( sTok == "daily" )
298 return tokDaily;
299 else if( sTok == "hourly" )
300 return tokHourly;
301 else if( sTok == "weekly" )
302 return tokWeekly;
303 else if( sTok == "monthly" )
304 return tokMonthly;
305 else if( sTok == "yearly" )
306 return tokYearly;
307 else if( sTok == "sun" )
308 {
309 iVal = 0;
310 return valInt;
311 }
312 else if( sTok == "mon" )
313 {
314 iVal = 1;
315 return valInt;
316 }
317 else if( sTok == "tue" )
318 {
319 iVal = 2;
320 return valInt;
321 }
322 else if( sTok == "wed" )
323 {
324 iVal = 3;
325 return valInt;
326 }
327 else if( sTok == "thu" )
328 {
329 iVal = 4;
330 return valInt;
331 }
332 else if( sTok == "fri" )
333 {
334 iVal = 5;
335 return valInt;
336 }
337 else if( sTok == "sat" )
338 {
339 iVal = 6;
340 return valInt;
341 }
342 else if( sTok[0] >= '0' && sTok[0] <= '9' )
343 {
344 iVal = strtol( sTok.getStr(), NULL, 0 );
345 return valInt;
346 }
347
348 return tokErr;
349}
350
351int Bu::MiniCron::TimerBasic::lexInt( Bu::FString::const_iterator &i )
352{
353 Token t = lex( i );
354 if( t == tokEos )
355 return 0;
356 if( t != valInt )
357 throw Bu::ExceptionBase("Expected int, got something else.");
358 return iVal;
359}
360
diff --git a/src/minicron.h b/src/minicron.h
new file mode 100644
index 0000000..b100ad0
--- /dev/null
+++ b/src/minicron.h
@@ -0,0 +1,226 @@
1#ifndef BU_MINICRON_H
2#define BU_MINICRON_H
3
4#include "bu/signals.h"
5#include "bu/heap.h"
6#include "bu/fstring.h"
7
8#include <time.h>
9
10namespace Bu
11{
12 /**
13 * A simple cron like system designed to be embedded in any program. This
14 * class creates a simple cron system that can run any number of jobs at
15 * customizable intervals or schedules. It does not support some of the
16 * more complex scheduling that some cron systems can do such as load
17 * balancing directly, but this could be done on the job side.
18 *
19 * This system is synchronous, it does not use any threads on it's own, but
20 * it is threadsafe, so a cron thread could be created if desired.
21 *
22 * The operation is fairly simple, jobs can be added at any time, and use
23 * any timer they would like, even custom timers. When it is time for a
24 * job to be run it signals the slot provided when the job was added. Every
25 * job slot recieves a handle to the job object so that it may control it's
26 * own lifetime and get information about itself. In addition, every job
27 * is assigned a unique ID that can be used to control it's operation
28 * at any time.
29 *
30 * By default a job will continually reschedule itself after being run
31 * unless it calls stop() on it's job object, it is removed using
32 * removeJob() on the cron object, or it is added with addJobOnce.
33 */
34 class MiniCron
35 {
36 public:
37 class Job;
38 class Timer;
39 typedef Bu::Signal1<void, Bu::MiniCron::Job &> CronSignal;
40 typedef int JobId;
41
42 MiniCron();
43 virtual ~MiniCron();
44
45 /**
46 * Tells you if there are jobs registered in the MiniCron.
47 *@returns true if there are jobs, false otherwise.
48 */
49 virtual bool hasJobs();
50
51 /**
52 * If there are jobs, tells you the time the next one will execute.
53 *@returns The timestamp that the next job will execute at.
54 */
55 virtual time_t getNextRun();
56
57 /**
58 * Call this regularly to execute all jobs that should be executed.
59 * This will loop until all jobs who's run time match the current time
60 * or are below the current time (we've missed them).
61 * If there is nothing to run, the runtime of this funcion is constant,
62 * it is very fast. Otherwise it executes at log(N) per job run,
63 * O(N*log(N)).
64 */
65 virtual void poll();
66
67 /**
68 * Add a job for repeated scheduling. Pass in a slot to signal, and a
69 * Timer object to use to do the scheduling. This function returns a
70 * JobId which can be used at a later time to control the execution of
71 * the job.
72 */
73 virtual JobId addJob( CronSignal sigJob, const Timer &t );
74
75 /**
76 * Add a job for one time scheduling. Pass in a slot to signal, and a
77 * Timer object to use to schodule the one run of this job. This
78 * function returns a JobId which can be used at a later time to control
79 * the execution of the job.
80 */
81 virtual JobId addJobOnce( CronSignal sigJob, const Timer &t );
82
83 /**
84 * Remove a job, preventing all future runs of the job. If there is no
85 * job matching the given JobId then nothing will happen. However, this
86 * function is relatively expensive compared to the others in this class
87 * and has a worse case runtime of 2*N*log(N), still not that bad, and
88 * a O(N*log(N)).
89 */
90 virtual void removeJob( JobId jid );
91
92 class Timer
93 {
94 public:
95 Timer();
96 virtual ~Timer();
97
98 virtual time_t nextTime()=0;
99 virtual Timer *clone() const = 0;
100 };
101
102 class TimerInterval : public Timer
103 {
104 public:
105 TimerInterval( time_t tFirst, time_t tInterval );
106 virtual ~TimerInterval();
107
108 virtual time_t nextTime();
109 virtual Timer *clone() const
110 { return new TimerInterval( *this ); }
111 private:
112 time_t tNext;
113 time_t tInterval;
114 };
115
116 class TimerBasic : public Timer
117 {
118 public:
119 TimerBasic( const Bu::FString &s );
120 virtual ~TimerBasic();
121
122 virtual time_t nextTime();
123 virtual Timer *clone() const
124 { return new TimerBasic( *this ); }
125
126 private:
127 enum Token
128 {
129 tokDaily,
130 tokHourly,
131 tokWeekly,
132 tokMonthly,
133 tokYearly,
134 valInt,
135 tokErr,
136 tokEos
137 };
138 Token lex( Bu::FString::const_iterator &i );
139 int lexInt( Bu::FString::const_iterator &i );
140 int iVal; //< A temp variable for parsing.
141 time_t tLast;
142 Bu::FString sSpec;
143 };
144
145 class Job
146 {
147 friend class Bu::MiniCron;
148 public:
149 Job( JobId jid, bool bRepeat=true );
150 virtual ~Job();
151
152 /**
153 * Execute this job once, increment the runcount and schedule the
154 * next occurance of it.
155 */
156 void run();
157
158 /**
159 * Get the time this job will next run.
160 */
161 time_t getNextRun();
162
163 /**
164 * Compute the time this job will next run.
165 */
166 void calcNextRun();
167
168 /**
169 * Replace the current job timer with a new one, this will trigger
170 * a re-schedule.
171 */
172 void setTimer( const Timer &t );
173
174 /**
175 * Stop execution of this job, never execute this job again.
176 */
177 void stop();
178
179 /**
180 * Undo a previous stop. This will cause a job that has been
181 * stopped or even added with addJobOnce to be set for repeated
182 * scheduling.
183 */
184 void resume();
185
186 /**
187 * Get the unique ID of this job.
188 */
189 JobId getId();
190
191 /**
192 * Get the timestamp this job was created.
193 */
194 time_t getTimeCreated();
195
196 /**
197 * Get the current run count of this job, how many times it has been
198 * executed. This is incremented before the slot is signaled.
199 */
200 int getRunCount();
201
202 private:
203 CronSignal sigJob;
204 time_t tNextRun;
205 Timer *pTimer;
206 bool bContinue;
207 JobId jid;
208 time_t tAdded;
209 int iRunCount;
210 };
211
212 private:
213 struct JobPtrCmp
214 {
215 bool operator()( const Job *pLeft, const Job *pRight )
216 {
217 return pLeft->tNextRun < pRight->tNextRun;
218 }
219 };
220 typedef Bu::Heap<Job *, JobPtrCmp> JobHeap;
221 JobHeap hJobs;
222 JobId jidNext;
223 };
224};
225
226#endif
diff --git a/src/tests/minicron.cpp b/src/tests/minicron.cpp
new file mode 100644
index 0000000..715a74d
--- /dev/null
+++ b/src/tests/minicron.cpp
@@ -0,0 +1,53 @@
1#include "bu/minicron.h"
2#include "bu/sio.h"
3
4#include <unistd.h>
5
6using namespace Bu;
7
8Bu::MiniCron mCron;
9
10void job0( Bu::MiniCron::Job &job )
11{
12 sio << time( NULL ) << ": job0( id = " << job.getId() << ", count = "
13 << job.getRunCount() << ")" << sio.nl;
14}
15
16void job1( Bu::MiniCron::Job &job )
17{
18 sio << time( NULL ) << ": job1( id = " << job.getId() << ", count = "
19 << job.getRunCount() << ")" << sio.nl;
20 mCron.removeJob( 4 );
21}
22
23void job2( Bu::MiniCron::Job &job )
24{
25 sio << time( NULL ) << ": job2( id = " << job.getId() << ", count = "
26 << job.getRunCount() << ")" << sio.nl;
27}
28
29void job3( Bu::MiniCron::Job &job )
30{
31 sio << time( NULL ) << ": job3( id = " << job.getId() << ", count = "
32 << job.getRunCount() << ")" << sio.nl;
33}
34
35int main()
36{
37
38 mCron.addJob( slot( &job0 ), MiniCron::TimerInterval( time(NULL)+3, 5 ) );
39 mCron.addJob( slot( &job1 ), MiniCron::TimerInterval( time(NULL)+10, 8 ) );
40 mCron.addJob( slot( &job2 ), MiniCron::TimerBasic("weekly wed 17") );
41 mCron.addJob( slot( &job3 ), MiniCron::TimerInterval( time(NULL)+1, 2 ) );
42
43 sio << time( NULL ) << ": Program started." << sio.nl;
44
45 for(;;)
46 {
47 usleep( 50000 );
48 mCron.poll();
49 }
50
51 return 0;
52}
53