rufus-scheduler 2.0.23 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/CHANGELOG.txt +12 -0
  2. data/CREDITS.txt +4 -0
  3. data/README.md +1064 -0
  4. data/Rakefile +1 -4
  5. data/TODO.txt +145 -55
  6. data/lib/rufus/{sc → scheduler}/cronline.rb +46 -17
  7. data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
  8. data/lib/rufus/scheduler/jobs.rb +548 -0
  9. data/lib/rufus/scheduler/util.rb +318 -0
  10. data/lib/rufus/scheduler.rb +502 -26
  11. data/rufus-scheduler.gemspec +30 -4
  12. data/spec/cronline_spec.rb +29 -8
  13. data/spec/error_spec.rb +116 -0
  14. data/spec/job_array_spec.rb +39 -0
  15. data/spec/job_at_spec.rb +58 -0
  16. data/spec/job_cron_spec.rb +67 -0
  17. data/spec/job_every_spec.rb +71 -0
  18. data/spec/job_in_spec.rb +20 -0
  19. data/spec/job_interval_spec.rb +68 -0
  20. data/spec/job_repeat_spec.rb +308 -0
  21. data/spec/job_spec.rb +387 -115
  22. data/spec/lockfile_spec.rb +61 -0
  23. data/spec/parse_spec.rb +203 -0
  24. data/spec/schedule_at_spec.rb +129 -0
  25. data/spec/schedule_cron_spec.rb +66 -0
  26. data/spec/schedule_every_spec.rb +109 -0
  27. data/spec/schedule_in_spec.rb +80 -0
  28. data/spec/schedule_interval_spec.rb +128 -0
  29. data/spec/scheduler_spec.rb +831 -124
  30. data/spec/spec_helper.rb +65 -0
  31. data/spec/threads_spec.rb +75 -0
  32. metadata +64 -65
  33. data/README.rdoc +0 -661
  34. data/lib/rufus/otime.rb +0 -3
  35. data/lib/rufus/sc/jobqueues.rb +0 -160
  36. data/lib/rufus/sc/jobs.rb +0 -471
  37. data/lib/rufus/sc/rtime.rb +0 -363
  38. data/lib/rufus/sc/scheduler.rb +0 -636
  39. data/spec/at_in_spec.rb +0 -47
  40. data/spec/at_spec.rb +0 -125
  41. data/spec/blocking_spec.rb +0 -64
  42. data/spec/cron_spec.rb +0 -134
  43. data/spec/every_spec.rb +0 -304
  44. data/spec/exception_spec.rb +0 -113
  45. data/spec/in_spec.rb +0 -150
  46. data/spec/mutex_spec.rb +0 -159
  47. data/spec/rtime_spec.rb +0 -137
  48. data/spec/schedulable_spec.rb +0 -97
  49. data/spec/spec_base.rb +0 -87
  50. data/spec/stress_schedule_unschedule_spec.rb +0 -159
  51. data/spec/timeout_spec.rb +0 -148
  52. data/test/kjw.rb +0 -113
  53. data/test/t.rb +0 -20
data/spec/job_spec.rb CHANGED
@@ -2,240 +2,512 @@
2
2
  #
3
3
  # Specifying rufus-scheduler
4
4
  #
5
- # Wed Apr 27 00:51:07 JST 2011
5
+ # Wed Apr 17 06:00:59 JST 2013
6
6
  #
7
7
 
8
- require 'spec_base'
8
+ require 'spec_helper'
9
9
 
10
10
 
11
- describe 'job classes' do
11
+ describe Rufus::Scheduler::Job do
12
12
 
13
- before(:each) do
14
- @s = start_scheduler
13
+ # specify behaviours common to all job classes
14
+
15
+ before :each do
16
+
17
+ @taoe = Thread.abort_on_exception
18
+ Thread.abort_on_exception = false
19
+
20
+ @ose = $stderr
21
+ $stderr = StringIO.new
22
+
23
+ @scheduler = Rufus::Scheduler.new
15
24
  end
16
- after(:each) do
17
- stop_scheduler(@s)
25
+
26
+ after :each do
27
+
28
+ @scheduler.shutdown
29
+
30
+ Thread.abort_on_exception = @taoe
31
+
32
+ $stderr = @ose
18
33
  end
19
34
 
20
- describe Rufus::Scheduler::Job do
35
+ describe '#last_time' do
21
36
 
22
- describe '#running' do
37
+ it 'returns nil if the job never fired' do
23
38
 
24
- it 'returns false when the job is inactive' do
39
+ job = @scheduler.schedule_in '10d' do; end
25
40
 
26
- job = @s.in '2d' do
27
- end
41
+ job.last_time.should == nil
42
+ end
28
43
 
29
- job.running.should == false
30
- end
44
+ it 'returns the last time the job fired' do
31
45
 
32
- it 'returns true when the job is active' do
46
+ job = @scheduler.schedule_in '0s' do; end
33
47
 
34
- job = @s.in 0 do
35
- sleep(100)
36
- end
48
+ sleep 0.4
37
49
 
38
- wait_next_tick
50
+ job.last_time.should_not == nil
51
+ end
52
+ end
39
53
 
40
- job.running.should == true
41
- end
54
+ describe '#threads' do
42
55
 
43
- it 'returns false when the job hits some error' do
56
+ it 'returns an empty list when the job is not running' do
44
57
 
45
- $exception = nil
58
+ job = @scheduler.in('1d', :job => true) {}
46
59
 
47
- def @s.handle_exception(j, e)
48
- #p e
49
- $exception = e
50
- end
60
+ job.threads.size.should == 0
61
+ end
51
62
 
52
- job = @s.in 0 do
53
- raise "nada"
63
+ it 'returns an empty list after the job terminated' do
64
+
65
+ job = @scheduler.in('0s', :job => true) {}
66
+
67
+ sleep 0.8
68
+
69
+ job.threads.size.should == 0
70
+ end
71
+
72
+ it 'lists the threads the job currently runs in' do
73
+
74
+ job =
75
+ @scheduler.schedule_in('0s') do
76
+ sleep(1)
54
77
  end
55
78
 
56
- wait_next_tick
79
+ sleep 0.4
57
80
 
58
- $exception.should_not == nil
59
- job.running.should == false
60
- end
81
+ job.threads.size.should == 1
82
+
83
+ t = job.threads.first
84
+ t[:rufus_scheduler_job].should == job
61
85
  end
86
+ end
62
87
 
63
- describe '#running?' do
88
+ describe '#kill' do
64
89
 
65
- it 'is an alias for #running' do
90
+ it 'has no effect if the job is not running' do
66
91
 
67
- job = @s.in 0 do
68
- sleep(100)
92
+ job = @scheduler.schedule_in '10d' do; end
93
+
94
+ tls = Thread.list.size
95
+
96
+ job.kill
97
+
98
+ Thread.list.size.should == tls
99
+ end
100
+
101
+ it 'makes the threads vacant' do
102
+
103
+ counter = 0
104
+
105
+ job =
106
+ @scheduler.schedule_in '0s' do
107
+ sleep 2
108
+ counter = counter + 1
69
109
  end
70
110
 
71
- wait_next_tick
111
+ sleep 1
72
112
 
73
- job.running?.should == true
74
- end
113
+ v0 = @scheduler.work_threads(:vacant).size
114
+ a0 = @scheduler.work_threads(:active).size
115
+
116
+ job.kill
117
+
118
+ sleep 2
119
+
120
+ v1 = @scheduler.work_threads(:vacant).size
121
+ a1 = @scheduler.work_threads(:active).size
122
+
123
+ counter.should == 0
124
+
125
+ v0.should == 0
126
+ a0.should == 1
127
+
128
+ v1.should == 1
129
+ a1.should == 0
75
130
  end
76
131
  end
77
132
 
78
- describe Rufus::Scheduler::AtJob do
133
+ describe '#running?' do
79
134
 
80
- describe '#unschedule' do
135
+ it 'returns false when the job is not running in any thread' do
81
136
 
82
- it 'removes the job from the scheduler' do
137
+ job = @scheduler.in('1d', :job => true) {}
83
138
 
84
- job = @s.at Time.now + 3 * 3600 do
85
- end
139
+ job.running?.should == false
140
+ end
86
141
 
87
- wait_next_tick
142
+ it 'returns true when the job is running in at least one thread' do
88
143
 
89
- job.unschedule
144
+ job = @scheduler.in('0s', :job => true) { sleep(1) }
90
145
 
91
- @s.jobs.size.should == 0
92
- end
146
+ sleep 0.4
147
+
148
+ job.running?.should == true
93
149
  end
150
+ end
94
151
 
95
- describe '#next_time' do
152
+ describe '#scheduled?' do
96
153
 
97
- it 'returns the time when the job will trigger' do
154
+ it 'returns true when the job is scheduled' do
98
155
 
99
- t = Time.now + 3 * 3600
156
+ job = @scheduler.schedule_in('1d') {}
100
157
 
101
- job = @s.at Time.now + 3 * 3600 do
102
- end
158
+ job.scheduled?.should == true
159
+ end
103
160
 
104
- job.next_time.class.should == Time
105
- job.next_time.to_i.should == t.to_i
106
- end
161
+ it 'returns false when the job is not scheduled' do
162
+
163
+ job = @scheduler.schedule_in('0.1s') {}
164
+
165
+ sleep 0.4
166
+
167
+ job.scheduled?.should == false
168
+ end
169
+
170
+ it 'returns true for repeat jobs that are running' do
171
+
172
+ job = @scheduler.schedule_interval('0.4s') { sleep(10) }
173
+
174
+ sleep 1
175
+
176
+ job.running?.should == true
177
+ job.scheduled?.should == true
107
178
  end
108
179
  end
109
180
 
110
- describe Rufus::Scheduler::InJob do
181
+ context 'job-local variables' do
111
182
 
112
- describe '#unschedule' do
183
+ describe '#[]=' do
113
184
 
114
- it 'removes the job from the scheduler' do
185
+ it 'sets a job-local variable' do
115
186
 
116
- job = @s.in '2d' do
117
- end
187
+ job =
188
+ @scheduler.schedule_every '1s' do |job|
189
+ job[:counter] ||= 0
190
+ job[:counter] += 1
191
+ end
192
+
193
+ sleep 3
118
194
 
119
- wait_next_tick
195
+ job[:counter].should > 1
196
+ end
197
+ end
120
198
 
121
- job.unschedule
199
+ describe '#[]' do
122
200
 
123
- @s.jobs.size.should == 0
201
+ it 'returns nil if there is no such entry' do
202
+
203
+ job = @scheduler.schedule_in '1s' do; end
204
+
205
+ job[:nada].should == nil
206
+ end
207
+
208
+ it 'returns the value of a job-local variable' do
209
+
210
+ job = @scheduler.schedule_in '1s' do; end
211
+ job[:x] = :y
212
+
213
+ job[:x].should == :y
124
214
  end
125
215
  end
126
216
 
127
- describe '#next_time' do
217
+ describe '#key?' do
128
218
 
129
- it 'returns the time when the job will trigger' do
219
+ it 'returns true if there is an entry with the given key' do
130
220
 
131
- t = Time.now + 3 * 3600
221
+ job = @scheduler.schedule_in '1s' do; end
222
+ job[:x] = :y
132
223
 
133
- job = @s.in '3h' do
134
- end
224
+ job.key?(:x).should == true
225
+ end
226
+ end
227
+
228
+ describe '#keys' do
229
+
230
+ it 'returns the array of keys of the job-local variables' do
135
231
 
136
- job.next_time.class.should == Time
137
- job.next_time.to_i.should == t.to_i
232
+ job = @scheduler.schedule_in '1s' do; end
233
+ job[:x] = :y
234
+ job['hello'] = :z
235
+ job[123] = {}
236
+
237
+ job.keys.sort_by { |k| k.to_s }.should == [ 123, 'hello', :x ]
138
238
  end
139
239
  end
140
240
  end
141
241
 
142
- describe Rufus::Scheduler::EveryJob do
242
+ context ':tag / :tags => [ t0, t1 ]' do
143
243
 
144
- describe '#next_time' do
244
+ it 'accepts one tag' do
145
245
 
146
- it 'returns the time when the job will trigger' do
246
+ job = @scheduler.in '10d', :job => true, :tag => 't0' do; end
147
247
 
148
- t = Time.now + 3 * 3600
248
+ job.tags.should == %w[ t0 ]
249
+ end
250
+
251
+ it 'accepts an array of tags' do
252
+
253
+ job = @scheduler.in '10d', :job => true, :tag => %w[ t0 t1 ] do; end
254
+
255
+ job.tags.should == %w[ t0 t1 ]
256
+ end
149
257
 
150
- job = @s.every '3h' do
258
+ it 'turns tags into strings' do
259
+
260
+ job = @scheduler.in '10d', :job => true, :tags => [ 1, 2 ] do; end
261
+
262
+ job.tags.should == %w[ 1 2 ]
263
+ end
264
+ end
265
+
266
+ context ':blocking => true' do
267
+
268
+ it 'runs the job in the same thread as the scheduler thread' do
269
+
270
+ job =
271
+ @scheduler.in('0s', :job => true, :blocking => true) do
272
+ sleep(1)
151
273
  end
152
274
 
153
- job.next_time.class.should == Time
154
- job.next_time.to_i.should == t.to_i
155
- end
275
+ sleep 0.4
276
+
277
+ job.threads.first.should == @scheduler.thread
278
+
279
+ sleep 1.4
280
+
281
+ job.threads.size.should == 0
156
282
  end
283
+ end
157
284
 
158
- describe '#paused?' do
285
+ context 'default one thread per job behaviour' do
159
286
 
160
- it 'returns false initially' do
287
+ it 'runs the job in a dedicated thread' do
161
288
 
162
- job = @s.every '3h' do; end
289
+ job =
290
+ @scheduler.in('0s', :job => true) do
291
+ sleep(1)
292
+ end
163
293
 
164
- job.paused?.should == false
165
- end
294
+ sleep 0.4
295
+
296
+ job.threads.first.should_not == @scheduler.thread
297
+
298
+ sleep 1.4
299
+
300
+ job.threads.size.should == 0
166
301
  end
302
+ end
303
+
304
+ context ':allow_overlapping / :allow_overlap / :overlap' do
167
305
 
168
- describe '#pause' do
306
+ context 'default (:overlap => true)' do
169
307
 
170
- it 'pauses the job' do
308
+ it 'lets a job overlap itself' do
171
309
 
172
- job = @s.every '3h' do; end
310
+ job =
311
+ @scheduler.every('0.3', :job => true) do
312
+ sleep(5)
313
+ end
173
314
 
174
- job.pause
315
+ sleep 3
175
316
 
176
- job.paused?.should == true
317
+ job.threads.size.should > 1
177
318
  end
178
319
  end
179
320
 
180
- describe '#resume' do
321
+ context 'when :overlap => false' do
181
322
 
182
- it 'resumes the job' do
323
+ it 'prevents a job from overlapping itself' do
183
324
 
184
- job = @s.every '3h' do; end
325
+ job =
326
+ @scheduler.every('0.3', :job => true, :overlap => false) do
327
+ sleep(5)
328
+ end
185
329
 
186
- job.resume
330
+ sleep 3
187
331
 
188
- job.paused?.should == false
332
+ job.threads.size.should == 1
189
333
  end
190
334
  end
191
335
  end
192
336
 
193
- describe Rufus::Scheduler::CronJob do
337
+ context ':mutex' do
338
+
339
+ context ':mutex => "mutex_name"' do
340
+
341
+ it 'prevents concurrent executions' do
342
+
343
+ j0 =
344
+ @scheduler.in('0s', :job => true, :mutex => 'vladivostok') do
345
+ sleep(3)
346
+ end
347
+ j1 =
348
+ @scheduler.in('0s', :job => true, :mutex => 'vladivostok') do
349
+ sleep(3)
350
+ end
351
+
352
+ sleep 0.7
353
+
354
+ if j0.threads.any?
355
+ j0.threads.size.should == 1
356
+ j1.threads.size.should == 0
357
+ else
358
+ j0.threads.size.should == 0
359
+ j1.threads.size.should == 1
360
+ end
361
+
362
+ @scheduler.mutexes.keys.should == %w[ vladivostok ]
363
+ end
364
+ end
365
+
366
+ context ':mutex => mutex_instance' do
367
+
368
+ it 'prevents concurrent executions' do
194
369
 
195
- describe '#next_time' do
370
+ m = Mutex.new
196
371
 
197
- it 'returns the time when the job will trigger' do
372
+ j0 = @scheduler.in('0s', :job => true, :mutex => m) { sleep(3) }
373
+ j1 = @scheduler.in('0s', :job => true, :mutex => m) { sleep(3) }
198
374
 
199
- job = @s.cron '* * * * *' do
375
+ sleep 0.7
376
+
377
+ if j0.threads.any?
378
+ j0.threads.size.should == 1
379
+ j1.threads.size.should == 0
380
+ else
381
+ j0.threads.size.should == 0
382
+ j1.threads.size.should == 1
200
383
  end
201
384
 
202
- job.next_time.class.should == Time
203
- (job.next_time.to_i - Time.now.to_i).should satisfy { |v| v < 60 }
385
+ @scheduler.mutexes.keys.should == []
204
386
  end
205
387
  end
206
388
 
207
- describe '#paused?' do
389
+ context ':mutex => [ array_of_mutex_names_or_instances ]' do
390
+
391
+ it 'prevents concurrent executions' do
208
392
 
209
- it 'returns false initially' do
393
+ j0 =
394
+ @scheduler.in('0s', :job => true, :mutex => %w[ a b ]) do
395
+ sleep(3)
396
+ end
397
+ j1 =
398
+ @scheduler.in('0s', :job => true, :mutex => %w[ a b ]) do
399
+ sleep(3)
400
+ end
210
401
 
211
- job = @s.cron '* * * * *' do; end
402
+ sleep 0.7
403
+
404
+ if j0.threads.any?
405
+ j0.threads.size.should == 1
406
+ j1.threads.size.should == 0
407
+ else
408
+ j0.threads.size.should == 0
409
+ j1.threads.size.should == 1
410
+ end
212
411
 
213
- job.paused?.should == false
412
+ @scheduler.mutexes.keys.sort.should == %w[ a b ]
214
413
  end
215
414
  end
415
+ end
416
+
417
+ context ':timeout => duration_or_point_in_time' do
418
+
419
+ it 'interrupts the job it is stashed to (duration)' do
420
+
421
+ counter = 0
422
+ toe = nil
423
+
424
+ job =
425
+ @scheduler.schedule_in '0s', :timeout => '1s' do
426
+ begin
427
+ counter = counter + 1
428
+ sleep 1.5
429
+ counter = counter + 1
430
+ rescue Rufus::Scheduler::TimeoutError => e
431
+ toe = e
432
+ end
433
+ end
434
+
435
+ sleep(3)
436
+
437
+ counter.should == 1
438
+ toe.class.should == Rufus::Scheduler::TimeoutError
439
+ end
440
+
441
+ it 'interrupts the job it is stashed to (point in time)' do
216
442
 
217
- describe '#pause' do
443
+ counter = 0
218
444
 
219
- it 'pauses the job' do
445
+ job =
446
+ @scheduler.schedule_in '0s', :timeout => Time.now + 1 do
447
+ begin
448
+ counter = counter + 1
449
+ sleep 1.5
450
+ counter = counter + 1
451
+ rescue Rufus::Scheduler::TimeoutError => e
452
+ end
453
+ end
220
454
 
221
- job = @s.cron '* * * * *' do; end
455
+ sleep(3)
456
+
457
+ counter.should == 1
458
+ end
222
459
 
223
- job.pause
460
+ it 'starts timing when the job enters successfully all its mutexes' do
224
461
 
225
- job.paused?.should == true
462
+ t0, t1, t2 = nil
463
+
464
+ @scheduler.schedule_in '0s', :mutex => 'a' do
465
+ sleep 1
466
+ t0 = Time.now
226
467
  end
468
+
469
+ job =
470
+ @scheduler.schedule_in '0.5s', :mutex => 'a', :timeout => '1s' do
471
+ begin
472
+ t1 = Time.now
473
+ sleep 2
474
+ rescue Rufus::Scheduler::TimeoutError => e
475
+ t2 = Time.now
476
+ end
477
+ end
478
+
479
+ sleep 3
480
+
481
+ t0.should <= t1
482
+
483
+ d = t2 - t1
484
+ d.should >= 1.0
485
+ d.should < 1.5
227
486
  end
228
487
 
229
- describe '#resume' do
488
+ it 'emits the timeout information to $stderr (default #on_error)' do
489
+
490
+ @scheduler.every('1s', :timeout => '0.5s') do
491
+ sleep 0.9
492
+ end
493
+
494
+ sleep 2
230
495
 
231
- it 'resumes the job' do
496
+ $stderr.string.should match(/Rufus::Scheduler::TimeoutError/)
497
+ end
232
498
 
233
- job = @s.cron '* * * * *' do; end
499
+ it 'does not prevent a repeat job from recurring' do
234
500
 
235
- job.resume
501
+ counter = 0
236
502
 
237
- job.paused?.should == false
503
+ @scheduler.every('1s', :timeout => '0.5s') do
504
+ counter = counter + 1
505
+ sleep 0.9
238
506
  end
507
+
508
+ sleep 3
509
+
510
+ counter.should > 1
239
511
  end
240
512
  end
241
513
  end
@@ -0,0 +1,61 @@
1
+
2
+ #
3
+ # Specifying rufus-scheduler
4
+ #
5
+ # Tue Aug 13 05:58:48 JST 2013
6
+ #
7
+
8
+ require 'spec_helper'
9
+
10
+
11
+ describe Rufus::Scheduler do
12
+
13
+ after :each do
14
+
15
+ FileUtils.rm_f('.rufus-scheduler.lock')
16
+ FileUtils.rm_f('lock.txt')
17
+ end
18
+
19
+ context ':lockfile => ".rufus-scheduler.lock"' do
20
+
21
+ it 'writes down a .rufus-scheduler.lock file' do
22
+
23
+ s = Rufus::Scheduler.new :lockfile => '.rufus-scheduler.lock'
24
+
25
+ line = File.read('.rufus-scheduler.lock')
26
+
27
+ #p line
28
+ line.should match(/pid: #{$$}/)
29
+ end
30
+
31
+ it '"flocks" the lock file' do
32
+
33
+ s = Rufus::Scheduler.new :lockfile => '.rufus-scheduler.lock'
34
+
35
+ f = File.new('.rufus-scheduler.lock', 'a')
36
+
37
+ f.flock(File::LOCK_NB | File::LOCK_EX).should == false
38
+ end
39
+
40
+ it 'prevents newer schedulers from starting' do
41
+
42
+ s0 = Rufus::Scheduler.new :lockfile => '.rufus-scheduler.lock'
43
+ s1 = Rufus::Scheduler.new :lockfile => '.rufus-scheduler.lock'
44
+
45
+ s0.started_at.should_not == nil
46
+ s1.started_at.should == nil
47
+ end
48
+
49
+ it 'releases the lockfile when shutting down' do
50
+
51
+ s = Rufus::Scheduler.new :lockfile => '.rufus-scheduler.lock'
52
+
53
+ s.shutdown(:kill)
54
+
55
+ f = File.new('.rufus-scheduler.lock', 'a')
56
+
57
+ f.flock(File::LOCK_NB | File::LOCK_EX).should == 0
58
+ end
59
+ end
60
+ end
61
+