rufus-scheduler 3.6.0 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 02a3bef52f292ce97e4918c53add3f0f6ba7a10c
4
- data.tar.gz: '059fec2bf86103a66eb30be091fcd96f4b21abaf'
2
+ SHA256:
3
+ metadata.gz: c0572c41e8ad285c2de6828cc86ef489122cea65c412514566c08c35713a0523
4
+ data.tar.gz: 327a0c5e462acc3df68d6c55366a956c9e16b4b4adf208b103568cce4f943dd6
5
5
  SHA512:
6
- metadata.gz: 8282e8998f735291b30a5dfd8fc0b03ce5b2a5f19a38d747b197ae46b2b8324076af6583eb3c29a49273a066899871d2edd61f1628d6b9df583144a969095afd
7
- data.tar.gz: 0d272804d8de32ba93f96b25e9d7be1a6493166c33865a9bfe6ca38ea848db43a89266818d9c0fec3cc37e4a8e83623f88c56566bdb59f2afd85f6c484b0dea6
6
+ metadata.gz: e3153aa15933ee24cf3a11657fb0b8bd3d58d87e054b20d9d759b52cdd924a418fcf95ef1e14b980b459a501ccce91b285cd85c826e0fa4207fac85f69c77667
7
+ data.tar.gz: e7af296c9864ffc392ef44eec1bb6ed0331065658b9d9d1225cd2c1f62d1d67302db7075540b0c39a99dcf5f741471f3c38666cb8870165cca23a34f52f1c7df
@@ -2,6 +2,19 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ### rufus-scheduler 3.7.0 - released 2020-12-31
6
+
7
+ * Implement Job name:/n: #name and #name=, gh-309
8
+ * Add Job #has_key?, #value, and #entries
9
+ * Add #locals access to Job
10
+ * Implement Scheduler#around_trigger, @jjb, gh-310
11
+ * Accept max_worker_threads: for max_work_threads:
12
+ * Clean up Scheduler#shutdwon, thanks @itsaphel and @jjb, gh-304, gh-315
13
+
14
+ * f59df40 Bring in discard_past: for every jobs, gh-290
15
+ * 7613277 Introduce :discard_past = false for cron, gh-305
16
+
17
+
5
18
  ### rufus-scheduler 3.6.0 - released 2019-04-22
6
19
 
7
20
  * Let Scheduler#cron fail if the cron string is invalid, gh-289
data/CREDITS.md CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  ## Contributors
6
6
 
7
+ * John Bachir https://github.com/jjb gh-310
8
+ * Daniel Berger https://github.com/djberg96 gh-300
9
+ * Ceyhun Onur https://github.com/ceyonur parse_cron no_error: true
7
10
  * Darwin Wu https://github.com/dwaxe Rails initializer vs tests change
8
11
  * Mike Karolow https://github.com/mike-yesware update Travis target Rubies
9
12
  * Jack-Nie https://github.com/jack-nie gh-285 fix broken comment link
@@ -64,6 +67,8 @@
64
67
 
65
68
  ## Feedback
66
69
 
70
+ * aphel (https://github.com/itsaphel) #shutdown vs current thread, gh-304
71
+ * aesyondu (https://github.com/aesyondu) Rails console 4.2.x, gh-186
67
72
  * Sasha Hoellger (https://github.com/mitnal) parse_cron and readme, gh-270
68
73
  * Gian (https://github.com/snmgian) cron vs :first clarification, gh-266
69
74
  * Vito Laurenza (https://github.com/veetow) help debugging tz issues, gh-240
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2005-2019, John Mettraux, jmettraux@gmail.com
2
+ Copyright (c) 2005-2020, John Mettraux, jmettraux@gmail.com
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
data/Makefile CHANGED
@@ -23,5 +23,5 @@ build: gemspec_validate
23
23
  mv $(NAME)-$(VERSION).gem pkg/
24
24
 
25
25
  push: build
26
- gem push pkg/$(NAME)-$(VERSION).gem
26
+ gem push --otp "$(OTP)" pkg/$(NAME)-$(VERSION).gem
27
27
 
data/README.md CHANGED
@@ -24,7 +24,11 @@ scheduler.in '3s' do
24
24
  end
25
25
 
26
26
  scheduler.join
27
+ #
27
28
  # let the current thread join the scheduler thread
29
+ #
30
+ # (please note that this join should be removed when scheduling
31
+ # in a web application (Rails and friends) initializer)
28
32
  ```
29
33
  (run with `ruby quickstart.rb`)
30
34
 
@@ -73,6 +77,7 @@ A rufus-scheduler instance will go on scheduling while it is present among the o
73
77
  ## related and similar gems
74
78
 
75
79
  * [Whenever](https://github.com/javan/whenever) - let cron call back your Ruby code, trusted and reliable cron drives your schedule
80
+ * [ruby-clock](https://github.com/jjb/ruby-clock) - a clock process / job scheduler for Ruby
76
81
  * [Clockwork](https://github.com/Rykian/clockwork) - rufus-scheduler inspired gem
77
82
  * [Crono](https://github.com/plashchynski/crono) - an in-Rails cron scheduler
78
83
  * [PerfectSched](https://github.com/treasure-data/perfectsched) - highly available distributed cron built on [Sequel](https://sequel.jeremyevans.net) and more
@@ -100,8 +105,8 @@ I'll drive you right to the [tracks](#so-rails).
100
105
  * The error_handler is [#on_error](#rufusscheduleron_errorjob-error) (instead of #on_exception), by default it now prints the details of the error to $stderr (used to be $stdout)
101
106
  * Rufus::Scheduler::TimeOutError renamed to Rufus::Scheduler::TimeoutError
102
107
  * Introduction of "interval" jobs. Whereas "every" jobs are like "every 10 minutes, do this", interval jobs are like "do that, then wait for 10 minutes, then do that again, and so on"
103
- * Introduction of a :lockfile => true/filename mechanism to prevent multiple schedulers from executing
104
- * "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a ```every '10m'``` job is on, it will trigger once at wakeup, not 6 times (discard_past was false by default in rufus-scheduler 2.x). No intention to re-introduce ```:discard_past => false``` in 3.0 for now.
108
+ * Introduction of a lockfile: true/filename mechanism to prevent multiple schedulers from executing
109
+ * "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a `every '10m'` job is on, it will trigger once at wakeup, not 6 times (discard_past was false by default in rufus-scheduler 2.x). No intention to re-introduce `discard_past: false` in 3.0 for now.
105
110
  * Introduction of Scheduler #on_pre_trigger and #on_post_trigger callback points
106
111
 
107
112
 
@@ -373,6 +378,23 @@ While paused, the scheduler still accepts schedules, but no schedule will get tr
373
378
 
374
379
  ## job options
375
380
 
381
+ ### :name => string
382
+
383
+ Sets the name of the job.
384
+
385
+ ```ruby
386
+ scheduler.cron '*/15 8 * * *', name: 'Robert' do |job|
387
+ puts "A, it's #{Time.now} and my name is #{job.name}"
388
+ end
389
+
390
+ job1 =
391
+ scheduler.schedule_cron '*/30 9 * * *', n: 'temporary' do |job|
392
+ puts "B, it's #{Time.now} and my name is #{job.name}"
393
+ end
394
+ # ...
395
+ job1.name = 'Beowulf'
396
+ ```
397
+
376
398
  ### :blocking => true
377
399
 
378
400
  By default, jobs are triggered in their own, new threads. When `:blocking => true`, the job is triggered in the scheduler thread (a new thread is not created). Yes, while a blocking job is running, the scheduler is not scheduling.
@@ -460,7 +482,6 @@ s.every '3s', :first => :now do
460
482
  end
461
483
 
462
484
  s.join
463
-
464
485
  ```
465
486
 
466
487
  that'll output something like:
@@ -757,9 +778,9 @@ job.tags
757
778
  # => [ 'hello' ]
758
779
  ```
759
780
 
760
- ### []=, [], key? and keys
781
+ ### []=, [], key?, has_key?, keys, values, and entries
761
782
 
762
- Threads have thread-local variables. Rufus-scheduler jobs have job-local variables.
783
+ Threads have thread-local variables, similarly Rufus-scheduler jobs have job-local variables. Those are more like a dict with thread-safe access.
763
784
 
764
785
  ```ruby
765
786
  job =
@@ -774,13 +795,29 @@ sleep 3.6
774
795
  job[:counter]
775
796
  # => 3
776
797
 
777
- job.key?(:timestamp)
778
- # => true
779
- job.keys
780
- # => [ :timestamp, :counter ]
798
+ job.key?(:timestamp) # => true
799
+ job.has_key?(:timestamp) # => true
800
+ job.keys # => [ :timestamp, :counter ]
801
+ ```
802
+
803
+ Locals can be set at schedule time:
804
+ ```ruby
805
+ job0 =
806
+ @scheduler.schedule_cron '*/15 12 * * *', locals: { a: 0 } do
807
+ # ...
808
+ end
809
+ job1 =
810
+ @scheduler.schedule_cron '*/15 13 * * *', l: { a: 1 } do
811
+ # ...
812
+ end
781
813
  ```
782
814
 
783
- Job-local variables are thread-safe.
815
+ One can fetch the Hash directly with `Job#locals`. Of course, direct manipulation is not thread-safe.
816
+ ```ruby
817
+ job.locals.entries do |k, v|
818
+ p "#{k}: #{v}"
819
+ end
820
+ ```
784
821
 
785
822
  ### call
786
823
 
@@ -963,6 +1000,10 @@ Shuts down the scheduler, ceases any scheduler/triggering activity.
963
1000
 
964
1001
  Shuts down the scheduler, waits (blocks) until all the jobs cease running.
965
1002
 
1003
+ ### Scheduler#shutdown(wait: n)
1004
+
1005
+ Shuts down the scheduler, waits (blocks) at most n seconds until all the jobs cease running. (Jobs are killed after n seconds have elapsed).
1006
+
966
1007
  ### Scheduler#shutdown(:kill)
967
1008
 
968
1009
  Kills all the job (threads) and then shuts the scheduler down. Radical.
@@ -985,6 +1026,8 @@ Returns since the count of seconds for which the scheduler has been running.
985
1026
 
986
1027
  Lets the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
987
1028
 
1029
+ `#join` is mostly used in standalone scheduling script (or tiny one file examples). Calling `#join` from a web application initializer will probably hijack the main thread and prevent the web application from being served. Do not put a `#join` in such a web application initializer file.
1030
+
988
1031
  ### Scheduler#threads
989
1032
 
990
1033
  Returns all the threads associated with the scheduler, including the scheduler thread itself.
@@ -1097,7 +1140,9 @@ def scheduler.on_error(job, error)
1097
1140
  end
1098
1141
  ```
1099
1142
 
1100
- ## Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
1143
+ ## Callbacks
1144
+
1145
+ ### Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
1101
1146
 
1102
1147
  One can bind callbacks before and after jobs trigger:
1103
1148
 
@@ -1121,6 +1166,21 @@ The ```trigger_time``` is the time at which the job triggers. It might be a bit
1121
1166
 
1122
1167
  Warning: these two callbacks are executed in the scheduler thread, not in the work threads (the threads where the job execution really happens).
1123
1168
 
1169
+ ### Rufus::Scheduler#around_trigger
1170
+
1171
+ One can create an around callback which will wrap a job:
1172
+
1173
+ ```ruby
1174
+ def s.around_trigger(job)
1175
+ t = Time.now
1176
+ puts "Starting job #{job.id}..."
1177
+ yield
1178
+ puts "job #{job.id} finished in #{Time.now-t} seconds."
1179
+ end
1180
+ ```
1181
+
1182
+ The around callback is executed in the thread.
1183
+
1124
1184
  ### Rufus::Scheduler#on_pre_trigger as a guard
1125
1185
 
1126
1186
  Returning ```false``` in on_pre_trigger will prevent the job from triggering. Returning anything else (nil, -1, true, ...) will let the job trigger.
@@ -1574,6 +1634,8 @@ require 'rufus-scheduler'
1574
1634
  s = Rufus::Scheduler.singleton
1575
1635
 
1576
1636
  return if defined?(Rails::Console) || Rails.env.test? || File.split($0).last == 'rake'
1637
+ # return if $PROGRAM_NAME.include?('spring')
1638
+ # see https://github.com/jmettraux/rufus-scheduler/issues/186
1577
1639
 
1578
1640
  # do not schedule when Rails is run from its console, for a test/spec, or
1579
1641
  # from a Rake task
@@ -1,629 +1,722 @@
1
1
 
2
- require 'set'
3
2
  require 'date' if RUBY_VERSION < '1.9.0'
4
- require 'time'
5
3
  require 'thread'
6
4
 
7
5
  require 'fugit'
8
6
 
9
7
 
10
- module Rufus
8
+ module Rufus; end
11
9
 
12
- class Scheduler
10
+ class Rufus::Scheduler
13
11
 
14
- VERSION = '3.6.0'
12
+ VERSION = '3.7.0'
15
13
 
16
- EoTime = ::EtOrbi::EoTime
14
+ EoTime = ::EtOrbi::EoTime
17
15
 
18
- require 'rufus/scheduler/util'
19
- require 'rufus/scheduler/jobs'
20
- require 'rufus/scheduler/job_array'
21
- require 'rufus/scheduler/locks'
16
+ require 'rufus/scheduler/util'
17
+ require 'rufus/scheduler/jobs_core'
18
+ require 'rufus/scheduler/jobs_one_time'
19
+ require 'rufus/scheduler/jobs_repeat'
20
+ require 'rufus/scheduler/job_array'
21
+ require 'rufus/scheduler/locks'
22
22
 
23
- #
24
- # A common error class for rufus-scheduler
25
- #
26
- class Error < StandardError; end
23
+ #
24
+ # A common error class for rufus-scheduler
25
+ #
26
+ class Error < StandardError; end
27
27
 
28
- #
29
- # This error is thrown when the :timeout attribute triggers
30
- #
31
- class TimeoutError < Error; end
28
+ #
29
+ # This error is thrown when the :timeout attribute triggers
30
+ #
31
+ class TimeoutError < Error; end
32
32
 
33
- #
34
- # For when the scheduler is not running
35
- # (it got shut down or didn't start because of a lock)
36
- #
37
- class NotRunningError < Error; end
33
+ #
34
+ # For when the scheduler is not running
35
+ # (it got shut down or didn't start because of a lock)
36
+ #
37
+ class NotRunningError < Error; end
38
38
 
39
- #MIN_WORK_THREADS = 3
40
- MAX_WORK_THREADS = 28
39
+ #MIN_WORK_THREADS = 3
40
+ MAX_WORK_THREADS = 28
41
41
 
42
- attr_accessor :frequency
43
- attr_reader :started_at
44
- attr_reader :thread
45
- attr_reader :thread_key
46
- attr_reader :mutexes
42
+ attr_accessor :frequency
43
+ attr_accessor :discard_past
47
44
 
48
- #attr_accessor :min_work_threads
49
- attr_accessor :max_work_threads
45
+ attr_reader :started_at
46
+ attr_reader :paused_at
47
+ attr_reader :thread
48
+ attr_reader :thread_key
49
+ attr_reader :mutexes
50
50
 
51
- attr_accessor :stderr
51
+ #attr_accessor :min_work_threads
52
+ attr_accessor :max_work_threads
52
53
 
53
- attr_reader :work_queue
54
+ attr_accessor :stderr
54
55
 
55
- def initialize(opts={})
56
+ attr_reader :work_queue
56
57
 
57
- @opts = opts
58
+ def initialize(opts={})
58
59
 
59
- @started_at = nil
60
- @paused = false
60
+ @opts = opts
61
61
 
62
- @jobs = JobArray.new
62
+ @started_at = nil
63
+ @paused_at = nil
63
64
 
64
- @frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
65
- @mutexes = {}
65
+ @jobs = JobArray.new
66
66
 
67
- @work_queue = Queue.new
67
+ @frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
68
+ @discard_past = opts.has_key?(:discard_past) ? opts[:discard_past] : true
68
69
 
69
- #@min_work_threads = opts[:min_work_threads] || MIN_WORK_THREADS
70
- @max_work_threads = opts[:max_work_threads] || MAX_WORK_THREADS
70
+ @mutexes = {}
71
71
 
72
- @stderr = $stderr
72
+ @work_queue = Queue.new
73
+ @join_queue = Queue.new
73
74
 
74
- @thread_key = "rufus_scheduler_#{self.object_id}"
75
+ #@min_work_threads =
76
+ # opts[:min_work_threads] || opts[:min_worker_threads] ||
77
+ # MIN_WORK_THREADS
78
+ @max_work_threads =
79
+ opts[:max_work_threads] || opts[:max_worker_threads] ||
80
+ MAX_WORK_THREADS
75
81
 
76
- @scheduler_lock =
77
- if lockfile = opts[:lockfile]
78
- Rufus::Scheduler::FileLock.new(lockfile)
79
- else
80
- opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
81
- end
82
+ @stderr = $stderr
82
83
 
83
- @trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new
84
+ @thread_key = "rufus_scheduler_#{self.object_id}"
84
85
 
85
- # If we can't grab the @scheduler_lock, don't run.
86
- lock || return
86
+ @scheduler_lock =
87
+ if lockfile = opts[:lockfile]
88
+ Rufus::Scheduler::FileLock.new(lockfile)
89
+ else
90
+ opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
91
+ end
87
92
 
88
- start
89
- end
93
+ @trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new
90
94
 
91
- # Returns a singleton Rufus::Scheduler instance
92
- #
93
- def self.singleton(opts={})
95
+ # If we can't grab the @scheduler_lock, don't run.
96
+ lock || return
94
97
 
95
- @singleton ||= Rufus::Scheduler.new(opts)
96
- end
98
+ start
99
+ end
97
100
 
98
- # Alias for Rufus::Scheduler.singleton
99
- #
100
- def self.s(opts={}); singleton(opts); end
101
+ # Returns a singleton Rufus::Scheduler instance
102
+ #
103
+ def self.singleton(opts={})
101
104
 
102
- # Releasing the gem would probably require redirecting .start_new to
103
- # .new and emit a simple deprecation message.
104
- #
105
- # For now, let's assume the people pointing at rufus-scheduler/master
106
- # on GitHub know what they do...
107
- #
108
- def self.start_new
105
+ @singleton ||= Rufus::Scheduler.new(opts)
106
+ end
109
107
 
110
- fail "this is rufus-scheduler 3.x, use .new instead of .start_new"
111
- end
108
+ # Alias for Rufus::Scheduler.singleton
109
+ #
110
+ def self.s(opts={}); singleton(opts); end
112
111
 
113
- def shutdown(opt=nil)
112
+ # Releasing the gem would probably require redirecting .start_new to
113
+ # .new and emit a simple deprecation message.
114
+ #
115
+ # For now, let's assume the people pointing at rufus-scheduler/master
116
+ # on GitHub know what they do...
117
+ #
118
+ def self.start_new
114
119
 
115
- @started_at = nil
120
+ fail 'this is rufus-scheduler 3.x, use .new instead of .start_new'
121
+ end
116
122
 
117
- #jobs.each { |j| j.unschedule }
118
- #
119
- # which provokes https://github.com/jmettraux/rufus-scheduler/issues/98
120
- # using the following instead:
121
- #
122
- @jobs.array.each { |j| j.unschedule }
123
+ def uptime
123
124
 
124
- @work_queue.clear
125
+ @started_at ? EoTime.now - @started_at : nil
126
+ end
125
127
 
126
- if opt == :wait
127
- join_all_work_threads
128
- elsif opt == :kill
129
- kill_all_work_threads
130
- end
128
+ def around_trigger(job)
131
129
 
132
- unlock
133
- end
130
+ yield
131
+ end
134
132
 
135
- alias stop shutdown
133
+ def uptime_s
136
134
 
137
- def uptime
135
+ uptime ? self.class.to_duration(uptime) : ''
136
+ end
138
137
 
139
- @started_at ? EoTime.now - @started_at : nil
140
- end
138
+ def join(time_limit=nil)
141
139
 
142
- def uptime_s
140
+ fail NotRunningError.new('cannot join scheduler that is not running') \
141
+ unless @thread
142
+ fail ThreadError.new('scheduler thread cannot join itself') \
143
+ if @thread == Thread.current
143
144
 
144
- uptime ? self.class.to_duration(uptime) : ''
145
+ if time_limit
146
+ time_limit_join(time_limit)
147
+ else
148
+ no_time_limit_join
145
149
  end
150
+ end
146
151
 
147
- def join
152
+ def down?
148
153
 
149
- fail NotRunningError.new(
150
- 'cannot join scheduler that is not running'
151
- ) unless @thread
154
+ ! @started_at
155
+ end
152
156
 
153
- @thread.join
154
- end
157
+ def up?
155
158
 
156
- def down?
159
+ !! @started_at
160
+ end
157
161
 
158
- ! @started_at
159
- end
162
+ def paused?
160
163
 
161
- def up?
164
+ !! @paused_at
165
+ end
162
166
 
163
- !! @started_at
164
- end
167
+ def pause
165
168
 
166
- def paused?
169
+ @paused_at = EoTime.now
170
+ end
167
171
 
168
- @paused
169
- end
172
+ def resume(opts={})
170
173
 
171
- def pause
174
+ dp = opts[:discard_past]
175
+ jobs.each { |job| job.resume_discard_past = dp }
172
176
 
173
- @paused = true
174
- end
177
+ @paused_at = nil
178
+ end
175
179
 
176
- def resume
180
+ #--
181
+ # scheduling methods
182
+ #++
177
183
 
178
- @paused = false
179
- end
184
+ def at(time, callable=nil, opts={}, &block)
180
185
 
181
- #--
182
- # scheduling methods
183
- #++
186
+ do_schedule(:once, time, callable, opts, opts[:job], block)
187
+ end
184
188
 
185
- def at(time, callable=nil, opts={}, &block)
189
+ def schedule_at(time, callable=nil, opts={}, &block)
186
190
 
187
- do_schedule(:once, time, callable, opts, opts[:job], block)
188
- end
191
+ do_schedule(:once, time, callable, opts, true, block)
192
+ end
189
193
 
190
- def schedule_at(time, callable=nil, opts={}, &block)
194
+ def in(duration, callable=nil, opts={}, &block)
191
195
 
192
- do_schedule(:once, time, callable, opts, true, block)
193
- end
196
+ do_schedule(:once, duration, callable, opts, opts[:job], block)
197
+ end
194
198
 
195
- def in(duration, callable=nil, opts={}, &block)
199
+ def schedule_in(duration, callable=nil, opts={}, &block)
196
200
 
197
- do_schedule(:once, duration, callable, opts, opts[:job], block)
198
- end
201
+ do_schedule(:once, duration, callable, opts, true, block)
202
+ end
199
203
 
200
- def schedule_in(duration, callable=nil, opts={}, &block)
204
+ def every(duration, callable=nil, opts={}, &block)
201
205
 
202
- do_schedule(:once, duration, callable, opts, true, block)
203
- end
206
+ do_schedule(:every, duration, callable, opts, opts[:job], block)
207
+ end
204
208
 
205
- def every(duration, callable=nil, opts={}, &block)
209
+ def schedule_every(duration, callable=nil, opts={}, &block)
206
210
 
207
- do_schedule(:every, duration, callable, opts, opts[:job], block)
208
- end
211
+ do_schedule(:every, duration, callable, opts, true, block)
212
+ end
209
213
 
210
- def schedule_every(duration, callable=nil, opts={}, &block)
214
+ def interval(duration, callable=nil, opts={}, &block)
211
215
 
212
- do_schedule(:every, duration, callable, opts, true, block)
213
- end
216
+ do_schedule(:interval, duration, callable, opts, opts[:job], block)
217
+ end
214
218
 
215
- def interval(duration, callable=nil, opts={}, &block)
219
+ def schedule_interval(duration, callable=nil, opts={}, &block)
216
220
 
217
- do_schedule(:interval, duration, callable, opts, opts[:job], block)
218
- end
221
+ do_schedule(:interval, duration, callable, opts, true, block)
222
+ end
219
223
 
220
- def schedule_interval(duration, callable=nil, opts={}, &block)
224
+ def cron(cronline, callable=nil, opts={}, &block)
221
225
 
222
- do_schedule(:interval, duration, callable, opts, true, block)
223
- end
226
+ do_schedule(:cron, cronline, callable, opts, opts[:job], block)
227
+ end
224
228
 
225
- def cron(cronline, callable=nil, opts={}, &block)
229
+ def schedule_cron(cronline, callable=nil, opts={}, &block)
226
230
 
227
- do_schedule(:cron, cronline, callable, opts, opts[:job], block)
228
- end
231
+ do_schedule(:cron, cronline, callable, opts, true, block)
232
+ end
233
+
234
+ def schedule(arg, callable=nil, opts={}, &block)
229
235
 
230
- def schedule_cron(cronline, callable=nil, opts={}, &block)
236
+ callable, opts = nil, callable if callable.is_a?(Hash)
237
+ opts = opts.dup
231
238
 
232
- do_schedule(:cron, cronline, callable, opts, true, block)
239
+ opts[:_t] = Rufus::Scheduler.parse(arg, opts)
240
+
241
+ case opts[:_t]
242
+ when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
243
+ when ::EtOrbi::EoTime, Time then schedule_at(arg, callable, opts, &block)
244
+ else schedule_in(arg, callable, opts, &block)
233
245
  end
246
+ end
234
247
 
235
- def schedule(arg, callable=nil, opts={}, &block)
248
+ def repeat(arg, callable=nil, opts={}, &block)
236
249
 
237
- callable, opts = nil, callable if callable.is_a?(Hash)
238
- opts = opts.dup
250
+ callable, opts = nil, callable if callable.is_a?(Hash)
251
+ opts = opts.dup
239
252
 
240
- opts[:_t] = Scheduler.parse(arg, opts)
253
+ opts[:_t] = Rufus::Scheduler.parse(arg, opts)
241
254
 
242
- case opts[:_t]
243
- when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
244
- when ::EtOrbi::EoTime, Time then schedule_at(arg, callable, opts, &block)
245
- else schedule_in(arg, callable, opts, &block)
246
- end
255
+ case opts[:_t]
256
+ when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
257
+ else schedule_every(arg, callable, opts, &block)
247
258
  end
259
+ end
248
260
 
249
- def repeat(arg, callable=nil, opts={}, &block)
261
+ def unschedule(job_or_job_id)
250
262
 
251
- callable, opts = nil, callable if callable.is_a?(Hash)
252
- opts = opts.dup
263
+ job, job_id = fetch(job_or_job_id)
253
264
 
254
- opts[:_t] = Scheduler.parse(arg, opts)
265
+ fail ArgumentError.new("no job found with id '#{job_id}'") unless job
255
266
 
256
- case opts[:_t]
257
- when ::Fugit::Cron then schedule_cron(arg, callable, opts, &block)
258
- else schedule_every(arg, callable, opts, &block)
259
- end
260
- end
267
+ job.unschedule if job
268
+ end
269
+
270
+ #--
271
+ # jobs methods
272
+ #++
261
273
 
262
- def unschedule(job_or_job_id)
274
+ # Returns all the scheduled jobs
275
+ # (even those right before re-schedule).
276
+ #
277
+ def jobs(opts={})
263
278
 
264
- job, job_id = fetch(job_or_job_id)
279
+ opts = { opts => true } if opts.is_a?(Symbol)
265
280
 
266
- fail ArgumentError.new("no job found with id '#{job_id}'") unless job
281
+ jobs = @jobs.to_a
267
282
 
268
- job.unschedule if job
283
+ if opts[:running]
284
+ jobs = jobs.select { |j| j.running? }
285
+ elsif ! opts[:all]
286
+ jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
269
287
  end
270
288
 
271
- #--
272
- # jobs methods
273
- #++
289
+ tags = Array(opts[:tag] || opts[:tags]).collect(&:to_s)
290
+ jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
291
+
292
+ jobs
293
+ end
274
294
 
275
- # Returns all the scheduled jobs
276
- # (even those right before re-schedule).
277
- #
278
- def jobs(opts={})
295
+ def at_jobs(opts={})
279
296
 
280
- opts = { opts => true } if opts.is_a?(Symbol)
297
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
298
+ end
281
299
 
282
- jobs = @jobs.to_a
300
+ def in_jobs(opts={})
283
301
 
284
- if opts[:running]
285
- jobs = jobs.select { |j| j.running? }
286
- elsif ! opts[:all]
287
- jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
288
- end
302
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
303
+ end
289
304
 
290
- tags = Array(opts[:tag] || opts[:tags]).collect(&:to_s)
291
- jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
305
+ def every_jobs(opts={})
292
306
 
293
- jobs
294
- end
307
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
308
+ end
295
309
 
296
- def at_jobs(opts={})
310
+ def interval_jobs(opts={})
297
311
 
298
- jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
299
- end
312
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
313
+ end
300
314
 
301
- def in_jobs(opts={})
315
+ def cron_jobs(opts={})
302
316
 
303
- jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
304
- end
317
+ jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
318
+ end
305
319
 
306
- def every_jobs(opts={})
320
+ def job(job_id)
307
321
 
308
- jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
309
- end
322
+ @jobs[job_id]
323
+ end
310
324
 
311
- def interval_jobs(opts={})
325
+ # Returns true if the scheduler has acquired the [exclusive] lock and
326
+ # thus may run.
327
+ #
328
+ # Most of the time, a scheduler is run alone and this method should
329
+ # return true. It is useful in cases where among a group of applications
330
+ # only one of them should run the scheduler. For schedulers that should
331
+ # not run, the method should return false.
332
+ #
333
+ # Out of the box, rufus-scheduler proposes the
334
+ # :lockfile => 'path/to/lock/file' scheduler start option. It makes
335
+ # it easy for schedulers on the same machine to determine which should
336
+ # run (the first to write the lockfile and lock it). It uses "man 2 flock"
337
+ # so it probably won't work reliably on distributed file systems.
338
+ #
339
+ # If one needs to use a special/different locking mechanism, the scheduler
340
+ # accepts :scheduler_lock => lock_object. lock_object only needs to respond
341
+ # to #lock
342
+ # and #unlock, and both of these methods should be idempotent.
343
+ #
344
+ # Look at rufus/scheduler/locks.rb for an example.
345
+ #
346
+ def lock
347
+
348
+ @scheduler_lock.lock
349
+ end
312
350
 
313
- jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
314
- end
351
+ # Sister method to #lock, is called when the scheduler shuts down.
352
+ #
353
+ def unlock
315
354
 
316
- def cron_jobs(opts={})
355
+ @trigger_lock.unlock
356
+ @scheduler_lock.unlock
357
+ end
317
358
 
318
- jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
319
- end
359
+ # Callback called when a job is triggered. If the lock cannot be acquired,
360
+ # the job won't run (though it'll still be scheduled to run again if
361
+ # necessary).
362
+ #
363
+ def confirm_lock
320
364
 
321
- def job(job_id)
365
+ @trigger_lock.lock
366
+ end
322
367
 
323
- @jobs[job_id]
324
- end
368
+ # Returns true if this job is currently scheduled.
369
+ #
370
+ # Takes extra care to answer true if the job is a repeat job
371
+ # currently firing.
372
+ #
373
+ def scheduled?(job_or_job_id)
325
374
 
326
- # Returns true if the scheduler has acquired the [exclusive] lock and
327
- # thus may run.
328
- #
329
- # Most of the time, a scheduler is run alone and this method should
330
- # return true. It is useful in cases where among a group of applications
331
- # only one of them should run the scheduler. For schedulers that should
332
- # not run, the method should return false.
333
- #
334
- # Out of the box, rufus-scheduler proposes the
335
- # :lockfile => 'path/to/lock/file' scheduler start option. It makes
336
- # it easy for schedulers on the same machine to determine which should
337
- # run (the first to write the lockfile and lock it). It uses "man 2 flock"
338
- # so it probably won't work reliably on distributed file systems.
339
- #
340
- # If one needs to use a special/different locking mechanism, the scheduler
341
- # accepts :scheduler_lock => lock_object. lock_object only needs to respond
342
- # to #lock
343
- # and #unlock, and both of these methods should be idempotent.
344
- #
345
- # Look at rufus/scheduler/locks.rb for an example.
346
- #
347
- def lock
348
-
349
- @scheduler_lock.lock
350
- end
375
+ job, _ = fetch(job_or_job_id)
351
376
 
352
- # Sister method to #lock, is called when the scheduler shuts down.
353
- #
354
- def unlock
377
+ !! (job && job.unscheduled_at.nil? && job.next_time != nil)
378
+ end
355
379
 
356
- @trigger_lock.unlock
357
- @scheduler_lock.unlock
358
- end
380
+ # Lists all the threads associated with this scheduler.
381
+ #
382
+ def threads
359
383
 
360
- # Callback called when a job is triggered. If the lock cannot be acquired,
361
- # the job won't run (though it'll still be scheduled to run again if
362
- # necessary).
363
- #
364
- def confirm_lock
384
+ Thread.list.select { |t| t[thread_key] }
385
+ end
365
386
 
366
- @trigger_lock.lock
387
+ # Lists all the work threads (the ones actually running the scheduled
388
+ # block code)
389
+ #
390
+ # Accepts a query option, which can be set to:
391
+ # * :all (default), returns all the threads that are work threads
392
+ # or are currently running a job
393
+ # * :active, returns all threads that are currently running a job
394
+ # * :vacant, returns the threads that are not running a job
395
+ #
396
+ # If, thanks to :blocking => true, a job is scheduled to monopolize the
397
+ # main scheduler thread, that thread will get returned when :active or
398
+ # :all.
399
+ #
400
+ def work_threads(query=:all)
401
+
402
+ ts = threads.select { |t| t[:rufus_scheduler_work_thread] }
403
+
404
+ case query
405
+ when :active then ts.select { |t| t[:rufus_scheduler_job] }
406
+ when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
407
+ else ts
367
408
  end
409
+ end
368
410
 
369
- # Returns true if this job is currently scheduled.
370
- #
371
- # Takes extra care to answer true if the job is a repeat job
372
- # currently firing.
373
- #
374
- def scheduled?(job_or_job_id)
411
+ def running_jobs(opts={})
375
412
 
376
- job, _ = fetch(job_or_job_id)
413
+ jobs(opts.merge(:running => true))
414
+ end
377
415
 
378
- !! (job && job.unscheduled_at.nil? && job.next_time != nil)
379
- end
416
+ def occurrences(time0, time1, format=:per_job)
380
417
 
381
- # Lists all the threads associated with this scheduler.
382
- #
383
- def threads
418
+ h = {}
384
419
 
385
- Thread.list.select { |t| t[thread_key] }
420
+ jobs.each do |j|
421
+ os = j.occurrences(time0, time1)
422
+ h[j] = os if os.any?
386
423
  end
387
424
 
388
- # Lists all the work threads (the ones actually running the scheduled
389
- # block code)
390
- #
391
- # Accepts a query option, which can be set to:
392
- # * :all (default), returns all the threads that are work threads
393
- # or are currently running a job
394
- # * :active, returns all threads that are currently running a job
395
- # * :vacant, returns the threads that are not running a job
396
- #
397
- # If, thanks to :blocking => true, a job is scheduled to monopolize the
398
- # main scheduler thread, that thread will get returned when :active or
399
- # :all.
400
- #
401
- def work_threads(query=:all)
402
-
403
- ts = threads.select { |t| t[:rufus_scheduler_work_thread] }
404
-
405
- case query
406
- when :active then ts.select { |t| t[:rufus_scheduler_job] }
407
- when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
408
- else ts
409
- end
425
+ if format == :timeline
426
+ a = []
427
+ h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
428
+ a.sort_by { |(t, _)| t }
429
+ else
430
+ h
410
431
  end
432
+ end
411
433
 
412
- def running_jobs(opts={})
434
+ def timeline(time0, time1)
413
435
 
414
- jobs(opts.merge(:running => true))
415
- end
436
+ occurrences(time0, time1, :timeline)
437
+ end
416
438
 
417
- def occurrences(time0, time1, format=:per_job)
439
+ def on_error(job, err)
440
+
441
+ pre = err.object_id.to_s
442
+
443
+ ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }
444
+
445
+ stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
446
+ stderr.puts(" #{pre} job:")
447
+ stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
448
+ # TODO: eventually use a Job#detail or something like that
449
+ stderr.puts(" #{pre} error:")
450
+ stderr.puts(" #{pre} #{err.object_id}")
451
+ stderr.puts(" #{pre} #{err.class}")
452
+ stderr.puts(" #{pre} #{err}")
453
+ err.backtrace.each do |l|
454
+ stderr.puts(" #{pre} #{l}")
455
+ end
456
+ stderr.puts(" #{pre} tz:")
457
+ stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
458
+ stderr.puts(" #{pre} Time.now: #{Time.now}")
459
+ stderr.puts(" #{pre} local_tzone: #{EoTime.local_tzone.inspect}")
460
+ stderr.puts(" #{pre} et-orbi:")
461
+ stderr.puts(" #{pre} #{EoTime.platform_info}")
462
+ stderr.puts(" #{pre} scheduler:")
463
+ stderr.puts(" #{pre} object_id: #{object_id}")
464
+ stderr.puts(" #{pre} opts:")
465
+ stderr.puts(" #{pre} #{@opts.inspect}")
466
+ stderr.puts(" #{pre} frequency: #{self.frequency}")
467
+ stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
468
+ stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
469
+ stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
470
+ stderr.puts(" #{pre} down?: #{down?}")
471
+ stderr.puts(" #{pre} frequency: #{frequency.inspect}")
472
+ stderr.puts(" #{pre} discard_past: #{discard_past.inspect}")
473
+ stderr.puts(" #{pre} started_at: #{started_at.inspect}")
474
+ stderr.puts(" #{pre} paused_at: #{paused_at.inspect}")
475
+ stderr.puts(" #{pre} threads: #{self.threads.size}")
476
+ stderr.puts(" #{pre} thread: #{self.thread}")
477
+ stderr.puts(" #{pre} thread_key: #{self.thread_key}")
478
+ stderr.puts(" #{pre} work_threads: #{work_threads.size}")
479
+ stderr.puts(" #{pre} active: #{work_threads(:active).size}")
480
+ stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
481
+ stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
482
+ stderr.puts(" #{pre} mutexes: #{ms.inspect}")
483
+ stderr.puts(" #{pre} jobs: #{jobs.size}")
484
+ stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
485
+ stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
486
+ stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
487
+ stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
488
+ stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
489
+ stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
490
+ stderr.puts(" #{pre} work_queue:")
491
+ stderr.puts(" #{pre} size: #{@work_queue.size}")
492
+ stderr.puts(" #{pre} num_waiting: #{@work_queue.num_waiting}")
493
+ stderr.puts(" #{pre} join_queue:")
494
+ stderr.puts(" #{pre} size: #{@join_queue.size}")
495
+ stderr.puts(" #{pre} num_waiting: #{@join_queue.num_waiting}")
496
+ stderr.puts("} #{pre} .")
497
+
498
+ rescue => e
499
+
500
+ stderr.puts("failure in #on_error itself:")
501
+ stderr.puts(e.inspect)
502
+ stderr.puts(e.backtrace)
503
+
504
+ ensure
505
+
506
+ stderr.flush
507
+ end
418
508
 
419
- h = {}
509
+ def shutdown(opt=nil)
420
510
 
421
- jobs.each do |j|
422
- os = j.occurrences(time0, time1)
423
- h[j] = os if os.any?
511
+ opts =
512
+ case opt
513
+ when Symbol then { opt => true }
514
+ when Hash then opt
515
+ else {}
424
516
  end
425
517
 
426
- if format == :timeline
427
- a = []
428
- h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
429
- a.sort_by { |(t, _)| t }
430
- else
431
- h
432
- end
518
+ @jobs.unschedule_all
519
+
520
+ if opts[:wait] || opts[:join]
521
+ join_shutdown(opts)
522
+ elsif opts[:kill]
523
+ kill_shutdown(opts)
524
+ else
525
+ regular_shutdown(opts)
433
526
  end
434
527
 
435
- def timeline(time0, time1)
528
+ @work_queue.clear
436
529
 
437
- occurrences(time0, time1, :timeline)
438
- end
530
+ unlock
439
531
 
440
- def on_error(job, err)
532
+ @thread.join
533
+ end
534
+ alias stop shutdown
441
535
 
442
- pre = err.object_id.to_s
536
+ protected
443
537
 
444
- ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }
538
+ def join_shutdown(opts)
445
539
 
446
- stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
447
- stderr.puts(" #{pre} job:")
448
- stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
449
- # TODO: eventually use a Job#detail or something like that
450
- stderr.puts(" #{pre} error:")
451
- stderr.puts(" #{pre} #{err.object_id}")
452
- stderr.puts(" #{pre} #{err.class}")
453
- stderr.puts(" #{pre} #{err}")
454
- err.backtrace.each do |l|
455
- stderr.puts(" #{pre} #{l}")
456
- end
457
- stderr.puts(" #{pre} tz:")
458
- stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
459
- stderr.puts(" #{pre} Time.now: #{Time.now}")
460
- stderr.puts(" #{pre} local_tzone: #{EoTime.local_tzone.inspect}")
461
- stderr.puts(" #{pre} et-orbi:")
462
- stderr.puts(" #{pre} #{EoTime.platform_info}")
463
- stderr.puts(" #{pre} scheduler:")
464
- stderr.puts(" #{pre} object_id: #{object_id}")
465
- stderr.puts(" #{pre} opts:")
466
- stderr.puts(" #{pre} #{@opts.inspect}")
467
- stderr.puts(" #{pre} frequency: #{self.frequency}")
468
- stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
469
- stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
470
- stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
471
- stderr.puts(" #{pre} down?: #{down?}")
472
- stderr.puts(" #{pre} threads: #{self.threads.size}")
473
- stderr.puts(" #{pre} thread: #{self.thread}")
474
- stderr.puts(" #{pre} thread_key: #{self.thread_key}")
475
- stderr.puts(" #{pre} work_threads: #{work_threads.size}")
476
- stderr.puts(" #{pre} active: #{work_threads(:active).size}")
477
- stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
478
- stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
479
- stderr.puts(" #{pre} mutexes: #{ms.inspect}")
480
- stderr.puts(" #{pre} jobs: #{jobs.size}")
481
- stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
482
- stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
483
- stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
484
- stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
485
- stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
486
- stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
487
- stderr.puts(" #{pre} work_queue: #{work_queue.size}")
488
- stderr.puts("} #{pre} .")
489
-
490
- rescue => e
491
-
492
- stderr.puts("failure in #on_error itself:")
493
- stderr.puts(e.inspect)
494
- stderr.puts(e.backtrace)
495
-
496
- ensure
497
-
498
- stderr.flush
499
- end
540
+ limit = opts[:wait] || opts[:join]
541
+ limit = limit.is_a?(Numeric) ? limit : nil
500
542
 
501
- protected
543
+ #@started_at = nil
544
+ #
545
+ # when @started_at is nil, the scheduler thread exits, here
546
+ # we want it to exit when all the work threads have been joined
547
+ # hence it's set to nil later on
548
+ #
549
+ @paused_at = EoTime.now
502
550
 
503
- # Returns [ job, job_id ]
504
- #
505
- def fetch(job_or_job_id)
551
+ (work_threads.size * 2 + 1).times { @work_queue << :shutdown }
506
552
 
507
- if job_or_job_id.respond_to?(:job_id)
508
- [ job_or_job_id, job_or_job_id.job_id ]
509
- else
510
- [ job(job_or_job_id), job_or_job_id ]
511
- end
512
- end
553
+ work_threads
554
+ .collect { |wt|
555
+ wt == Thread.current ? nil : Thread.new { wt.join(limit); wt.kill } }
556
+ .each { |st|
557
+ st.join if st }
513
558
 
514
- def terminate_all_jobs
559
+ @started_at = nil
560
+ end
515
561
 
516
- jobs.each { |j| j.unschedule }
562
+ def kill_shutdown(opts)
517
563
 
518
- sleep 0.01 while running_jobs.size > 0
519
- end
564
+ @started_at = nil
565
+ work_threads.each(&:kill)
566
+ end
567
+
568
+ def regular_shutdown(opts)
569
+
570
+ @started_at = nil
571
+ end
520
572
 
521
- def join_all_work_threads
573
+ def time_limit_join(limit)
522
574
 
523
- work_threads.size.times { @work_queue << :sayonara }
575
+ fail ArgumentError.new("limit #{limit.inspect} should be > 0") \
576
+ unless limit.is_a?(Numeric) && limit > 0
524
577
 
525
- work_threads.each { |t| t.join }
578
+ t0 = monow
579
+ f = [ limit.to_f / 20, 0.100 ].min
526
580
 
527
- @work_queue.clear
581
+ while monow - t0 < limit
582
+ r =
583
+ begin
584
+ @join_queue.pop(true)
585
+ rescue ThreadError => e
586
+ # #<ThreadError: queue empty>
587
+ false
588
+ end
589
+ return r if r
590
+ sleep(f)
528
591
  end
529
592
 
530
- def kill_all_work_threads
593
+ nil
594
+ end
595
+
596
+ def no_time_limit_join
597
+
598
+ @join_queue.pop
599
+ end
600
+
601
+ # Returns [ job, job_id ]
602
+ #
603
+ def fetch(job_or_job_id)
531
604
 
532
- work_threads.each { |t| t.kill }
605
+ if job_or_job_id.respond_to?(:job_id)
606
+ [ job_or_job_id, job_or_job_id.job_id ]
607
+ else
608
+ [ job(job_or_job_id), job_or_job_id ]
533
609
  end
610
+ end
611
+
612
+ def terminate_all_jobs
534
613
 
535
- #def free_all_work_threads
536
- #
537
- # work_threads.each { |t| t.raise(KillSignal) }
538
- #end
614
+ jobs.each { |j| j.unschedule }
539
615
 
540
- def start
616
+ sleep 0.01 while running_jobs.size > 0
617
+ end
618
+
619
+ #def free_all_work_threads
620
+ #
621
+ # work_threads.each { |t| t.raise(KillSignal) }
622
+ #end
541
623
 
542
- @started_at = EoTime.now
624
+ def start
543
625
 
544
- @thread =
545
- Thread.new do
626
+ @started_at = EoTime.now
546
627
 
547
- while @started_at do
628
+ @thread =
629
+ Thread.new do
548
630
 
549
- unschedule_jobs
550
- trigger_jobs unless @paused
551
- timeout_jobs
631
+ while @started_at do
552
632
 
553
- sleep(@frequency)
554
- end
633
+ unschedule_jobs
634
+ trigger_jobs unless @paused_at
635
+ timeout_jobs
636
+
637
+ sleep(@frequency)
555
638
  end
556
639
 
557
- @thread[@thread_key] = true
558
- @thread[:rufus_scheduler] = self
559
- @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
560
- end
640
+ rejoin
641
+ end
642
+
643
+ @thread[@thread_key] = true
644
+ @thread[:rufus_scheduler] = self
645
+ @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
646
+ end
561
647
 
562
- def unschedule_jobs
648
+ def unschedule_jobs
563
649
 
564
- @jobs.delete_unscheduled
565
- end
650
+ @jobs.delete_unscheduled
651
+ end
566
652
 
567
- def trigger_jobs
653
+ def trigger_jobs
568
654
 
569
- now = EoTime.now
655
+ now = EoTime.now
570
656
 
571
- @jobs.each(now) do |job|
657
+ @jobs.each(now) do |job|
572
658
 
573
- job.trigger(now)
574
- end
659
+ job.trigger(now)
575
660
  end
661
+ end
576
662
 
577
- def timeout_jobs
663
+ def timeout_jobs
578
664
 
579
- work_threads(:active).each do |t|
665
+ work_threads(:active).each do |t|
580
666
 
581
- job = t[:rufus_scheduler_job]
582
- to = t[:rufus_scheduler_timeout]
583
- ts = t[:rufus_scheduler_time]
667
+ job = t[:rufus_scheduler_job]
668
+ to = t[:rufus_scheduler_timeout]
669
+ ts = t[:rufus_scheduler_time]
584
670
 
585
- next unless job && to && ts
586
- # thread might just have become inactive (job -> nil)
671
+ next unless job && to && ts
672
+ # thread might just have become inactive (job -> nil)
587
673
 
588
- to = ts + to unless to.is_a?(EoTime)
674
+ to = ts + to unless to.is_a?(EoTime)
589
675
 
590
- next if to > EoTime.now
676
+ next if to > EoTime.now
591
677
 
592
- t.raise(Rufus::Scheduler::TimeoutError)
593
- end
678
+ t.raise(Rufus::Scheduler::TimeoutError)
594
679
  end
680
+ end
681
+
682
+ def rejoin
595
683
 
596
- def do_schedule(job_type, t, callable, opts, return_job_instance, block)
684
+ (@join_queue.num_waiting * 2 + 1).times { @join_queue << @thread }
685
+ end
597
686
 
598
- fail NotRunningError.new(
599
- 'cannot schedule, scheduler is down or shutting down'
600
- ) if @started_at.nil?
687
+ def do_schedule(job_type, t, callable, opts, return_job_instance, block)
601
688
 
602
- callable, opts = nil, callable if callable.is_a?(Hash)
603
- opts = opts.dup unless opts.has_key?(:_t)
689
+ fail NotRunningError.new(
690
+ 'cannot schedule, scheduler is down or shutting down'
691
+ ) if @started_at.nil?
604
692
 
605
- return_job_instance ||= opts[:job]
693
+ callable, opts = nil, callable if callable.is_a?(Hash)
694
+ opts = opts.dup unless opts.has_key?(:_t)
606
695
 
607
- job_class =
608
- case job_type
609
- when :once
610
- opts[:_t] ||= Rufus::Scheduler.parse(t, opts)
611
- opts[:_t].is_a?(Numeric) ? InJob : AtJob
612
- when :every
613
- EveryJob
614
- when :interval
615
- IntervalJob
616
- when :cron
617
- CronJob
618
- end
696
+ return_job_instance ||= opts[:job]
619
697
 
620
- job = job_class.new(self, t, opts, block || callable)
621
- job.check_frequency
698
+ job_class =
699
+ case job_type
700
+ when :once
701
+ opts[:_t] ||= Rufus::Scheduler.parse(t, opts)
702
+ opts[:_t].is_a?(Numeric) ? InJob : AtJob
703
+ when :every
704
+ EveryJob
705
+ when :interval
706
+ IntervalJob
707
+ when :cron
708
+ CronJob
709
+ end
622
710
 
623
- @jobs.push(job)
711
+ job = job_class.new(self, t, opts, block || callable)
712
+ job.check_frequency
624
713
 
625
- return_job_instance ? job : job.job_id
626
- end
714
+ @jobs.push(job)
715
+
716
+ return_job_instance ? job : job.job_id
627
717
  end
718
+
719
+ def monow; self.class.monow; end
720
+ def ltstamp; self.class.ltstamp; end
628
721
  end
629
722