uv-rays 2.3.0 → 2.3.1
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.
- checksums.yaml +4 -4
- data/lib/uv-rays.rb +93 -94
- data/lib/uv-rays/abstract_tokenizer.rb +1 -1
- data/lib/uv-rays/buffered_tokenizer.rb +1 -1
- data/lib/uv-rays/connection.rb +188 -188
- data/lib/uv-rays/http_endpoint.rb +296 -296
- data/lib/uv-rays/scheduler.rb +409 -409
- data/lib/uv-rays/scheduler/time.rb +307 -308
- data/lib/uv-rays/tcp_server.rb +44 -44
- data/lib/uv-rays/version.rb +1 -1
- data/spec/scheduler_spec.rb +1 -1
- metadata +2 -2
data/lib/uv-rays/scheduler.rb
CHANGED
@@ -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
|