rufus-scheduler 2.0.24 → 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 +6 -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/scheduler.rb +502 -26
  7. data/lib/rufus/{sc → scheduler}/cronline.rb +46 -17
  8. data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
  9. data/lib/rufus/scheduler/jobs.rb +548 -0
  10. data/lib/rufus/scheduler/util.rb +318 -0
  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 -59
  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
@@ -0,0 +1,308 @@
1
+
2
+ #
3
+ # Specifying rufus-scheduler
4
+ #
5
+ # Wed Apr 17 06:00:59 JST 2013
6
+ #
7
+
8
+ require 'spec_helper'
9
+
10
+
11
+ describe Rufus::Scheduler::RepeatJob do
12
+
13
+ before :each do
14
+ @scheduler = Rufus::Scheduler.new
15
+ end
16
+ after :each do
17
+ @scheduler.shutdown
18
+ end
19
+
20
+ describe '#pause' do
21
+
22
+ it 'pauses the job' do
23
+
24
+ counter = 0
25
+
26
+ job =
27
+ @scheduler.schedule_every('0.5s') do
28
+ counter += 1
29
+ end
30
+
31
+ counter.should == 0
32
+
33
+ while counter < 1; sleep(0.1); end
34
+
35
+ job.pause
36
+
37
+ sleep(1)
38
+
39
+ counter.should == 1
40
+ end
41
+ end
42
+
43
+ describe '#paused?' do
44
+
45
+ it 'returns true if the job is paused' do
46
+
47
+ job = @scheduler.schedule_every('10s') do; end
48
+
49
+ job.pause
50
+
51
+ job.paused?.should == true
52
+ end
53
+
54
+ it 'returns false if the job is not paused' do
55
+
56
+ job = @scheduler.schedule_every('10s') do; end
57
+
58
+ job.paused?.should == false
59
+ end
60
+ end
61
+
62
+ describe '#resume' do
63
+
64
+ it 'resumes a paused job' do
65
+
66
+ counter = 0
67
+
68
+ job =
69
+ @scheduler.schedule_every('0.5s') do
70
+ counter += 1
71
+ end
72
+
73
+ job.pause
74
+ job.resume
75
+
76
+ sleep(1.5)
77
+
78
+ counter.should > 1
79
+ end
80
+
81
+ it 'has no effect on a not paused job' do
82
+
83
+ job = @scheduler.schedule_every('10s') do; end
84
+
85
+ job.resume
86
+
87
+ job.paused?.should == false
88
+ end
89
+ end
90
+
91
+ describe ':times => i' do
92
+
93
+ it 'lets a job unschedule itself after i times' do
94
+
95
+ counter = 0
96
+
97
+ job =
98
+ @scheduler.schedule_every '0.5s', :times => 3 do
99
+ counter = counter + 1
100
+ end
101
+
102
+ sleep(2.6)
103
+
104
+ counter.should == 3
105
+ end
106
+
107
+ it 'is OK when passed a nil instead of an integer' do
108
+
109
+ counter = 0
110
+
111
+ job =
112
+ @scheduler.schedule_every '0.5s', :times => nil do
113
+ counter = counter + 1
114
+ end
115
+
116
+ sleep(2.5)
117
+
118
+ counter.should > 3
119
+ end
120
+
121
+ it 'raises when passed something else than nil or an integer' do
122
+
123
+ lambda {
124
+ @scheduler.schedule_every '0.5s', :times => 'nada' do; end
125
+ }.should raise_error(ArgumentError)
126
+ end
127
+ end
128
+
129
+ describe ':first/:first_in/:first_at => point in time' do
130
+
131
+ it 'accepts a Time instance' do
132
+
133
+ t = Time.now + 10
134
+
135
+ job = @scheduler.schedule_every '0.5s', :first => t do; end
136
+
137
+ job.first_at.should == t
138
+ end
139
+
140
+ it 'accepts a time string' do
141
+
142
+ t = Time.now + 10
143
+
144
+ job = @scheduler.schedule_every '0.5s', :first => t.to_s do; end
145
+
146
+ job.first_at.to_s.should == t.to_s
147
+ job.first_at.zone.should == t.zone
148
+ end
149
+
150
+ it 'only lets the job trigger after the :first' do
151
+
152
+ t = Time.now + 1.4
153
+ counter = 0
154
+
155
+ job =
156
+ @scheduler.schedule_every '0.5s', :first => t do
157
+ counter = counter + 1
158
+ end
159
+
160
+ sleep(1)
161
+
162
+ counter.should == 0
163
+
164
+ sleep(1)
165
+
166
+ counter.should > 0
167
+ end
168
+
169
+ it 'raises on points in the past' do
170
+
171
+ lambda {
172
+
173
+ @scheduler.schedule_every '0.5s', :first => Time.now - 60 do; end
174
+
175
+ }.should raise_error(ArgumentError)
176
+ end
177
+ end
178
+
179
+ describe ':first/:first_in/:first_at => duration' do
180
+
181
+ it 'accepts a duration string' do
182
+
183
+ t = Time.now
184
+
185
+ job = @scheduler.schedule_every '0.5s', :first => '1h' do; end
186
+
187
+ job.first_at.should >= t + 3600
188
+ job.first_at.should < t + 3601
189
+ end
190
+
191
+ it 'accepts a duration in seconds (integer)' do
192
+
193
+ t = Time.now
194
+
195
+ job = @scheduler.schedule_every '0.5s', :first => 3600 do; end
196
+
197
+ job.first_at.should >= t + 3600
198
+ job.first_at.should < t + 3601
199
+ end
200
+
201
+ it 'raises if the argument cannot be used' do
202
+
203
+ lambda {
204
+ @scheduler.every '0.5s', :first => :nada do; end
205
+ }.should raise_error(ArgumentError)
206
+ end
207
+ end
208
+
209
+ describe '#first_at=' do
210
+
211
+ it 'can be used to set first_at directly' do
212
+
213
+ job = @scheduler.schedule_every '0.5s', :first => 3600 do; end
214
+ job.first_at = '2030-12-12 12:00:30'
215
+
216
+ job.first_at.strftime('%c').should == 'Thu Dec 12 12:00:30 2030'
217
+ end
218
+ end
219
+
220
+ describe ':last/:last_in/:last_at => point in time' do
221
+
222
+ it 'accepts a Time instance' do
223
+
224
+ t = Time.now + 10
225
+
226
+ job = @scheduler.schedule_every '0.5s', :last => t do; end
227
+
228
+ job.last_at.should == t
229
+ end
230
+
231
+ it 'unschedules the job after the last_at time' do
232
+
233
+ t = Time.now + 2
234
+ counter = 0
235
+
236
+ job =
237
+ @scheduler.schedule_every '0.5s', :last => t do
238
+ counter = counter + 1
239
+ end
240
+
241
+ sleep 3
242
+
243
+ counter.should == 3
244
+ @scheduler.jobs.should_not include(job)
245
+ end
246
+
247
+ it 'accepts a time string' do
248
+
249
+ t = Time.now + 10
250
+
251
+ job = @scheduler.schedule_every '0.5s', :last => t.to_s do; end
252
+
253
+ job.last_at.to_s.should == t.to_s
254
+ job.last_at.zone.should == t.zone
255
+ end
256
+
257
+ it 'raises on a point in the past' do
258
+
259
+ lambda {
260
+
261
+ @scheduler.every '0.5s', :last => Time.now - 60 do; end
262
+
263
+ }.should raise_error(ArgumentError)
264
+ end
265
+ end
266
+
267
+ describe ':last/:last_in/:last_at => duration' do
268
+
269
+ it 'accepts a duration string' do
270
+
271
+ t = Time.now
272
+
273
+ job = @scheduler.schedule_every '0.5s', :last_in => '2s' do; end
274
+
275
+ job.last_at.should >= t + 2
276
+ job.last_at.should < t + 2.5
277
+ end
278
+
279
+ it 'accepts a duration in seconds (integer)' do
280
+
281
+ t = Time.now
282
+
283
+ job = @scheduler.schedule_every '0.5s', :last_in => 2.0 do; end
284
+
285
+ job.last_at.should >= t + 2
286
+ job.last_at.should < t + 2.5
287
+ end
288
+
289
+ it 'raises if the argument is worthless' do
290
+
291
+ lambda {
292
+ @scheduler.every '0.5s', :last => :nada do; end
293
+ }.should raise_error(ArgumentError)
294
+ end
295
+ end
296
+
297
+ describe '#last_at=' do
298
+
299
+ it 'can be used to set last_at directly' do
300
+
301
+ job = @scheduler.schedule_every '0.5s', :last_in => 10.0 do; end
302
+ job.last_at = '2030-12-12 12:00:30'
303
+
304
+ job.last_at.strftime('%c').should == 'Thu Dec 12 12:00:30 2030'
305
+ end
306
+ end
307
+ end
308
+
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