uv-rays 2.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,409 +1,409 @@
1
- # frozen_string_literal: true
2
-
3
- module UV
4
-
5
- class ScheduledEvent < ::Libuv::Q::DeferredPromise
6
- # Note:: Comparable should not effect Hashes
7
- # it will however effect arrays
8
- include Comparable
9
-
10
- attr_reader :created
11
- attr_reader :last_scheduled
12
- attr_reader :next_scheduled
13
- attr_reader :trigger_count
14
-
15
- def initialize(scheduler)
16
- # Create a dummy deferrable
17
- reactor = scheduler.reactor
18
- defer = reactor.defer
19
-
20
- # Record a backtrace of where the schedule was created
21
- @trace = caller
22
-
23
- # Setup common event variables
24
- @scheduler = scheduler
25
- @created = reactor.now
26
- @last_scheduled = @created
27
- @trigger_count = 0
28
-
29
- # init the promise
30
- super(reactor, defer)
31
- end
32
-
33
- # Provide relevant inspect information
34
- def inspect
35
- insp = String.new("#<#{self.class}:#{"0x00%x" % (self.__id__ << 1)} ")
36
- insp << "trigger_count=#{@trigger_count} "
37
- insp << "config=#{info} " if self.respond_to?(:info, true)
38
- insp << "next_scheduled=#{to_time(@next_scheduled)} "
39
- insp << "last_scheduled=#{to_time(@last_scheduled)} created=#{to_time(@created)}>"
40
- insp
41
- end
42
- alias_method :to_s, :inspect
43
-
44
- def to_time(internal_time)
45
- if internal_time
46
- ((internal_time + @scheduler.time_diff) / 1000).to_i
47
- else
48
- internal_time
49
- end
50
- end
51
-
52
-
53
- # required for comparable
54
- def <=>(anOther)
55
- @next_scheduled <=> anOther.next_scheduled
56
- end
57
-
58
- # reject the promise
59
- def cancel
60
- @defer.reject(:cancelled)
61
- end
62
-
63
- # notify listeners of the event
64
- def trigger
65
- @trigger_count += 1
66
- @defer.notify(@reactor.now, self)
67
- end
68
- end
69
-
70
- class OneShot < ScheduledEvent
71
- def initialize(scheduler, at)
72
- super(scheduler)
73
-
74
- @next_scheduled = at
75
- end
76
-
77
- # Updates the scheduled time
78
- def update(time)
79
- @last_scheduled = @reactor.now
80
-
81
- parsed_time = Scheduler.parse_in(time, :quiet)
82
- if parsed_time.nil?
83
- # Parse at will throw an error if time is invalid
84
- parsed_time = Scheduler.parse_at(time) - @scheduler.time_diff
85
- else
86
- parsed_time += @last_scheduled
87
- end
88
-
89
- @next_scheduled = parsed_time
90
- @scheduler.reschedule(self)
91
- end
92
-
93
- # Runs the event and cancels the schedule
94
- def trigger
95
- super()
96
- @defer.resolve(:triggered)
97
- end
98
- end
99
-
100
- class Repeat < ScheduledEvent
101
- def initialize(scheduler, every)
102
- super(scheduler)
103
-
104
- @every = every
105
- next_time
106
- end
107
-
108
- # Update the time period of the repeating event
109
- #
110
- # @param schedule [String] a standard CRON job line or a human readable string representing a time period.
111
- def update(every, timezone: nil)
112
- time = Scheduler.parse_in(every, :quiet) || Scheduler.parse_cron(every, :quiet, timezone: timezone)
113
- raise ArgumentError.new("couldn't parse \"#{o}\"") if time.nil?
114
-
115
- @every = time
116
- reschedule
117
- end
118
-
119
- # removes the event from the schedule
120
- def pause
121
- @paused = true
122
- @scheduler.unschedule(self)
123
- end
124
-
125
- # reschedules the event to the next time period
126
- # can be used to reset a repeating timer
127
- def resume
128
- @paused = false
129
- @last_scheduled = @reactor.now
130
- reschedule
131
- end
132
-
133
- # Runs the event and reschedules
134
- def trigger
135
- super()
136
- @reactor.next_tick do
137
- # Do this next tick to avoid needless scheduling
138
- # if the event is stopped in the callback
139
- reschedule
140
- end
141
- end
142
-
143
-
144
- protected
145
-
146
-
147
- def next_time
148
- @last_scheduled = @reactor.now
149
- if @every.is_a? Integer
150
- @next_scheduled = @last_scheduled + @every
151
- else
152
- # must be a cron
153
- @next_scheduled = (@every.next.to_f * 1000).to_i - @scheduler.time_diff
154
- end
155
- end
156
-
157
- def reschedule
158
- unless @paused
159
- next_time
160
- @scheduler.reschedule(self)
161
- end
162
- end
163
-
164
- def info
165
- "repeat:#{@every.inspect}"
166
- end
167
- end
168
-
169
-
170
- class Scheduler
171
- attr_reader :reactor
172
- attr_reader :time_diff
173
- attr_reader :next
174
-
175
-
176
- def initialize(reactor)
177
- @reactor = reactor
178
- @schedules = Set.new
179
- @scheduled = []
180
- @next = nil # Next schedule time
181
- @timer = nil # Reference to the timer
182
- @timer_callback = method(:on_timer)
183
-
184
- # Not really required when used correctly
185
- @critical = Mutex.new
186
-
187
- # Every hour we should re-calibrate this (just in case)
188
- calibrate_time
189
-
190
- @calibrate = @reactor.timer do
191
- calibrate_time
192
- @calibrate.start(3600000)
193
- end
194
- @calibrate.start(3600000)
195
- @calibrate.unref
196
- end
197
-
198
-
199
- # As the libuv time is taken from an arbitrary point in time we
200
- # need to roughly synchronize between it and ruby's Time.now
201
- def calibrate_time
202
- @reactor.update_time
203
- @time_diff = (Time.now.to_f * 1000).to_i - @reactor.now
204
- end
205
-
206
- # Create a repeating event that occurs each time period
207
- #
208
- # @param time [String] a human readable string representing the time period. 3w2d4h1m2s for example.
209
- # @param callback [Proc] a block or method to execute when the event triggers
210
- # @return [::UV::Repeat]
211
- def every(time, callback = nil, &block)
212
- callback ||= block
213
- ms = Scheduler.parse_in(time)
214
- event = Repeat.new(self, ms)
215
-
216
- if callback.respond_to? :call
217
- event.progress callback
218
- end
219
- schedule(event)
220
- event
221
- end
222
-
223
- # Create a one off event that occurs after the time period
224
- #
225
- # @param time [String] a human readable string representing the time period. 3w2d4h1m2s for example.
226
- # @param callback [Proc] a block or method to execute when the event triggers
227
- # @return [::UV::OneShot]
228
- def in(time, callback = nil, &block)
229
- callback ||= block
230
- ms = @reactor.now + Scheduler.parse_in(time)
231
- event = OneShot.new(self, ms)
232
-
233
- if callback.respond_to? :call
234
- event.progress callback
235
- end
236
- schedule(event)
237
- event
238
- end
239
-
240
- # Create a one off event that occurs at a particular date and time
241
- #
242
- # @param time [String, Time] a representation of a date and time that can be parsed
243
- # @param callback [Proc] a block or method to execute when the event triggers
244
- # @return [::UV::OneShot]
245
- def at(time, callback = nil, &block)
246
- callback ||= block
247
- ms = Scheduler.parse_at(time) - @time_diff
248
- event = OneShot.new(self, ms)
249
-
250
- if callback.respond_to? :call
251
- event.progress callback
252
- end
253
- schedule(event)
254
- event
255
- end
256
-
257
- # Create a repeating event that uses a CRON line to determine the trigger time
258
- #
259
- # @param schedule [String] a standard CRON job line.
260
- # @param callback [Proc] a block or method to execute when the event triggers
261
- # @return [::UV::Repeat]
262
- def cron(schedule, callback = nil, timezone: nil, &block)
263
- callback ||= block
264
- ms = Scheduler.parse_cron(schedule, timezone: timezone)
265
- event = Repeat.new(self, ms)
266
-
267
- if callback.respond_to? :call
268
- event.progress callback
269
- end
270
- schedule(event)
271
- event
272
- end
273
-
274
- # Schedules an event for execution
275
- #
276
- # @param event [ScheduledEvent]
277
- def reschedule(event)
278
- # Check promise is not resolved
279
- return if event.resolved?
280
-
281
- @critical.synchronize {
282
- # Remove the event from the scheduled list and ensure it is in the schedules set
283
- if @schedules.include?(event)
284
- remove(event)
285
- else
286
- @schedules << event
287
- end
288
-
289
- # optimal algorithm for inserting into an already sorted list
290
- Bisect.insort(@scheduled, event)
291
-
292
- # Update the timer
293
- check_timer
294
- }
295
- end
296
-
297
- # Removes an event from the schedule
298
- #
299
- # @param event [ScheduledEvent]
300
- def unschedule(event)
301
- @critical.synchronize {
302
- # Only call delete and update the timer when required
303
- if @schedules.include?(event)
304
- @schedules.delete(event)
305
- remove(event)
306
- check_timer
307
- end
308
- }
309
- end
310
-
311
-
312
- private
313
-
314
-
315
- # Remove an element from the array
316
- def remove(obj)
317
- position = nil
318
-
319
- @scheduled.each_index do |i|
320
- # object level comparison
321
- if obj.equal? @scheduled[i]
322
- position = i
323
- break
324
- end
325
- end
326
-
327
- @scheduled.slice!(position) unless position.nil?
328
- end
329
-
330
- # First time schedule we want to bind to the promise
331
- def schedule(event)
332
- reschedule(event)
333
-
334
- event.finally do
335
- unschedule event
336
- end
337
- end
338
-
339
- # Ensures the current timer, if any, is still
340
- # accurate by checking the head of the schedule
341
- def check_timer
342
- @reactor.update_time
343
-
344
- existing = @next
345
- schedule = @scheduled.first
346
- @next = schedule.nil? ? nil : schedule.next_scheduled
347
-
348
- if existing != @next
349
- # lazy load the timer
350
- if @timer.nil?
351
- new_timer
352
- else
353
- @timer.stop
354
- end
355
-
356
- if not @next.nil?
357
- in_time = @next - @reactor.now
358
-
359
- # Ensure there are never negative start times
360
- if in_time > 3
361
- @timer.start(in_time)
362
- else
363
- # Effectively next tick
364
- @timer.start(0)
365
- end
366
- end
367
- end
368
- end
369
-
370
- # Is called when the libuv timer fires
371
- def on_timer
372
- @critical.synchronize {
373
- schedule = @scheduled.shift
374
- @schedules.delete(schedule)
375
- schedule.trigger
376
-
377
- # execute schedules that are within 3ms of this event
378
- # Basic timer coalescing..
379
- now = @reactor.now + 3
380
- while @scheduled.first && @scheduled.first.next_scheduled <= now
381
- schedule = @scheduled.shift
382
- @schedules.delete(schedule)
383
- schedule.trigger
384
- end
385
- check_timer
386
- }
387
- end
388
-
389
- # Provide some assurances on timer failure
390
- def new_timer
391
- @timer = @reactor.timer @timer_callback
392
- @timer.finally do
393
- new_timer
394
- unless @next.nil?
395
- @timer.start(@next)
396
- end
397
- end
398
- end
399
- end
400
- end
401
-
402
- module Libuv
403
- class Reactor
404
- def scheduler
405
- @scheduler ||= UV::Scheduler.new(@reactor)
406
- @scheduler
407
- end
408
- end
409
- end
1
+ # frozen_string_literal: true
2
+
3
+ module UV
4
+
5
+ class ScheduledEvent < ::Libuv::Q::DeferredPromise
6
+ # Note:: Comparable should not effect Hashes
7
+ # it will however effect arrays
8
+ include Comparable
9
+
10
+ attr_reader :created
11
+ attr_reader :last_scheduled
12
+ attr_reader :next_scheduled
13
+ attr_reader :trigger_count
14
+
15
+ def initialize(scheduler)
16
+ # Create a dummy deferrable
17
+ reactor = scheduler.reactor
18
+ defer = reactor.defer
19
+
20
+ # Record a backtrace of where the schedule was created
21
+ @trace = caller
22
+
23
+ # Setup common event variables
24
+ @scheduler = scheduler
25
+ @created = reactor.now
26
+ @last_scheduled = @created
27
+ @trigger_count = 0
28
+
29
+ # init the promise
30
+ super(reactor, defer)
31
+ end
32
+
33
+ # Provide relevant inspect information
34
+ def inspect
35
+ insp = String.new("#<#{self.class}:#{"0x00%x" % (self.__id__ << 1)} ")
36
+ insp << "trigger_count=#{@trigger_count} "
37
+ insp << "config=#{info} " if self.respond_to?(:info, true)
38
+ insp << "next_scheduled=#{to_time(@next_scheduled)} "
39
+ insp << "last_scheduled=#{to_time(@last_scheduled)} created=#{to_time(@created)}>"
40
+ insp
41
+ end
42
+ alias_method :to_s, :inspect
43
+
44
+ def to_time(internal_time)
45
+ if internal_time
46
+ ((internal_time + @scheduler.time_diff) / 1000).to_i
47
+ else
48
+ internal_time
49
+ end
50
+ end
51
+
52
+
53
+ # required for comparable
54
+ def <=>(anOther)
55
+ @next_scheduled <=> anOther.next_scheduled
56
+ end
57
+
58
+ # reject the promise
59
+ def cancel
60
+ @defer.reject(:cancelled)
61
+ end
62
+
63
+ # notify listeners of the event
64
+ def trigger
65
+ @trigger_count += 1
66
+ @defer.notify(@reactor.now, self)
67
+ end
68
+ end
69
+
70
+ class OneShot < ScheduledEvent
71
+ def initialize(scheduler, at)
72
+ super(scheduler)
73
+
74
+ @next_scheduled = at
75
+ end
76
+
77
+ # Updates the scheduled time
78
+ def update(time)
79
+ @last_scheduled = @reactor.now
80
+
81
+ parsed_time = Scheduler.parse_in(time, :quiet)
82
+ if parsed_time.nil?
83
+ # Parse at will throw an error if time is invalid
84
+ parsed_time = Scheduler.parse_at(time) - @scheduler.time_diff
85
+ else
86
+ parsed_time += @last_scheduled
87
+ end
88
+
89
+ @next_scheduled = parsed_time
90
+ @scheduler.reschedule(self)
91
+ end
92
+
93
+ # Runs the event and cancels the schedule
94
+ def trigger
95
+ super()
96
+ @defer.resolve(:triggered)
97
+ end
98
+ end
99
+
100
+ class Repeat < ScheduledEvent
101
+ def initialize(scheduler, every)
102
+ super(scheduler)
103
+
104
+ @every = every
105
+ next_time
106
+ end
107
+
108
+ # Update the time period of the repeating event
109
+ #
110
+ # @param schedule [String] a standard CRON job line or a human readable string representing a time period.
111
+ def update(every, timezone: nil)
112
+ time = Scheduler.parse_in(every, :quiet) || Scheduler.parse_cron(every, :quiet, timezone: timezone)
113
+ raise ArgumentError.new("couldn't parse \"#{o}\"") if time.nil?
114
+
115
+ @every = time
116
+ reschedule
117
+ end
118
+
119
+ # removes the event from the schedule
120
+ def pause
121
+ @paused = true
122
+ @scheduler.unschedule(self)
123
+ end
124
+
125
+ # reschedules the event to the next time period
126
+ # can be used to reset a repeating timer
127
+ def resume
128
+ @paused = false
129
+ @last_scheduled = @reactor.now
130
+ reschedule
131
+ end
132
+
133
+ # Runs the event and reschedules
134
+ def trigger
135
+ super()
136
+ @reactor.next_tick do
137
+ # Do this next tick to avoid needless scheduling
138
+ # if the event is stopped in the callback
139
+ reschedule
140
+ end
141
+ end
142
+
143
+
144
+ protected
145
+
146
+
147
+ def next_time
148
+ @last_scheduled = @reactor.now
149
+ if @every.is_a? Integer
150
+ @next_scheduled = @last_scheduled + @every
151
+ else
152
+ # must be a cron
153
+ @next_scheduled = (@every.next.to_f * 1000).to_i - @scheduler.time_diff
154
+ end
155
+ end
156
+
157
+ def reschedule
158
+ unless @paused
159
+ next_time
160
+ @scheduler.reschedule(self)
161
+ end
162
+ end
163
+
164
+ def info
165
+ "repeat:#{@every.inspect}"
166
+ end
167
+ end
168
+
169
+
170
+ class Scheduler
171
+ attr_reader :reactor
172
+ attr_reader :time_diff
173
+ attr_reader :next
174
+
175
+
176
+ def initialize(reactor)
177
+ @reactor = reactor
178
+ @schedules = Set.new
179
+ @scheduled = []
180
+ @next = nil # Next schedule time
181
+ @timer = nil # Reference to the timer
182
+ @timer_callback = method(:on_timer)
183
+
184
+ # Not really required when used correctly
185
+ @critical = Mutex.new
186
+
187
+ # Every hour we should re-calibrate this (just in case)
188
+ calibrate_time
189
+
190
+ @calibrate = @reactor.timer do
191
+ calibrate_time
192
+ @calibrate.start(3600000)
193
+ end
194
+ @calibrate.start(3600000)
195
+ @calibrate.unref
196
+ end
197
+
198
+
199
+ # As the libuv time is taken from an arbitrary point in time we
200
+ # need to roughly synchronize between it and ruby's Time.now
201
+ def calibrate_time
202
+ @reactor.update_time
203
+ @time_diff = (Time.now.to_f * 1000).to_i - @reactor.now
204
+ end
205
+
206
+ # Create a repeating event that occurs each time period
207
+ #
208
+ # @param time [String] a human readable string representing the time period. 3w2d4h1m2s for example.
209
+ # @param callback [Proc] a block or method to execute when the event triggers
210
+ # @return [::UV::Repeat]
211
+ def every(time, callback = nil, &block)
212
+ callback ||= block
213
+ ms = Scheduler.parse_in(time)
214
+ event = Repeat.new(self, ms)
215
+
216
+ if callback.respond_to? :call
217
+ event.progress callback
218
+ end
219
+ schedule(event)
220
+ event
221
+ end
222
+
223
+ # Create a one off event that occurs after the time period
224
+ #
225
+ # @param time [String] a human readable string representing the time period. 3w2d4h1m2s for example.
226
+ # @param callback [Proc] a block or method to execute when the event triggers
227
+ # @return [::UV::OneShot]
228
+ def in(time, callback = nil, &block)
229
+ callback ||= block
230
+ ms = @reactor.now + Scheduler.parse_in(time)
231
+ event = OneShot.new(self, ms)
232
+
233
+ if callback.respond_to? :call
234
+ event.progress callback
235
+ end
236
+ schedule(event)
237
+ event
238
+ end
239
+
240
+ # Create a one off event that occurs at a particular date and time
241
+ #
242
+ # @param time [String, Time] a representation of a date and time that can be parsed
243
+ # @param callback [Proc] a block or method to execute when the event triggers
244
+ # @return [::UV::OneShot]
245
+ def at(time, callback = nil, &block)
246
+ callback ||= block
247
+ ms = Scheduler.parse_at(time) - @time_diff
248
+ event = OneShot.new(self, ms)
249
+
250
+ if callback.respond_to? :call
251
+ event.progress callback
252
+ end
253
+ schedule(event)
254
+ event
255
+ end
256
+
257
+ # Create a repeating event that uses a CRON line to determine the trigger time
258
+ #
259
+ # @param schedule [String] a standard CRON job line.
260
+ # @param callback [Proc] a block or method to execute when the event triggers
261
+ # @return [::UV::Repeat]
262
+ def cron(schedule, callback = nil, timezone: nil, &block)
263
+ callback ||= block
264
+ ms = Scheduler.parse_cron(schedule, timezone: timezone)
265
+ event = Repeat.new(self, ms)
266
+
267
+ if callback.respond_to? :call
268
+ event.progress callback
269
+ end
270
+ schedule(event)
271
+ event
272
+ end
273
+
274
+ # Schedules an event for execution
275
+ #
276
+ # @param event [ScheduledEvent]
277
+ def reschedule(event)
278
+ # Check promise is not resolved
279
+ return if event.resolved?
280
+
281
+ @critical.synchronize {
282
+ # Remove the event from the scheduled list and ensure it is in the schedules set
283
+ if @schedules.include?(event)
284
+ remove(event)
285
+ else
286
+ @schedules << event
287
+ end
288
+
289
+ # optimal algorithm for inserting into an already sorted list
290
+ Bisect.insort(@scheduled, event)
291
+
292
+ # Update the timer
293
+ check_timer
294
+ }
295
+ end
296
+
297
+ # Removes an event from the schedule
298
+ #
299
+ # @param event [ScheduledEvent]
300
+ def unschedule(event)
301
+ @critical.synchronize {
302
+ # Only call delete and update the timer when required
303
+ if @schedules.include?(event)
304
+ @schedules.delete(event)
305
+ remove(event)
306
+ check_timer
307
+ end
308
+ }
309
+ end
310
+
311
+
312
+ private
313
+
314
+
315
+ # Remove an element from the array
316
+ def remove(obj)
317
+ position = nil
318
+
319
+ @scheduled.each_index do |i|
320
+ # object level comparison
321
+ if obj.equal? @scheduled[i]
322
+ position = i
323
+ break
324
+ end
325
+ end
326
+
327
+ @scheduled.slice!(position) unless position.nil?
328
+ end
329
+
330
+ # First time schedule we want to bind to the promise
331
+ def schedule(event)
332
+ reschedule(event)
333
+
334
+ event.finally do
335
+ unschedule event
336
+ end
337
+ end
338
+
339
+ # Ensures the current timer, if any, is still
340
+ # accurate by checking the head of the schedule
341
+ def check_timer
342
+ @reactor.update_time
343
+
344
+ existing = @next
345
+ schedule = @scheduled.first
346
+ @next = schedule.nil? ? nil : schedule.next_scheduled
347
+
348
+ if existing != @next
349
+ # lazy load the timer
350
+ if @timer.nil?
351
+ new_timer
352
+ else
353
+ @timer.stop
354
+ end
355
+
356
+ if not @next.nil?
357
+ in_time = @next - @reactor.now
358
+
359
+ # Ensure there are never negative start times
360
+ if in_time > 3
361
+ @timer.start(in_time)
362
+ else
363
+ # Effectively next tick
364
+ @timer.start(0)
365
+ end
366
+ end
367
+ end
368
+ end
369
+
370
+ # Is called when the libuv timer fires
371
+ def on_timer
372
+ @critical.synchronize {
373
+ schedule = @scheduled.shift
374
+ @schedules.delete(schedule)
375
+ schedule.trigger
376
+
377
+ # execute schedules that are within 3ms of this event
378
+ # Basic timer coalescing..
379
+ now = @reactor.now + 3
380
+ while @scheduled.first && @scheduled.first.next_scheduled <= now
381
+ schedule = @scheduled.shift
382
+ @schedules.delete(schedule)
383
+ schedule.trigger
384
+ end
385
+ check_timer
386
+ }
387
+ end
388
+
389
+ # Provide some assurances on timer failure
390
+ def new_timer
391
+ @timer = @reactor.timer @timer_callback
392
+ @timer.finally do
393
+ new_timer
394
+ unless @next.nil?
395
+ @timer.start(@next)
396
+ end
397
+ end
398
+ end
399
+ end
400
+ end
401
+
402
+ module Libuv
403
+ class Reactor
404
+ def scheduler
405
+ @scheduler ||= UV::Scheduler.new(@reactor)
406
+ @scheduler
407
+ end
408
+ end
409
+ end