ztimer 0.6.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +14 -0
- data/lib/ztimer/slot.rb +7 -7
- data/lib/ztimer/sorted_store.rb +34 -37
- data/lib/ztimer/version.rb +3 -1
- data/lib/ztimer/watcher.rb +15 -14
- data/lib/ztimer.rb +93 -22
- data/ztimer.gemspec +17 -14
- metadata +20 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 328b728b6ba74021474c30ef9093a1bca987a368b2bd211d5545abc888447ef2
|
4
|
+
data.tar.gz: df0ff61ae1bbf3901a1ddc9fa0ac3416dce79eeb1c4e05f1d9a3f61abce722c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d482e9792d40f7083258a66cac5b1857624d02dbc6f4297a3ea608c48f8f50908dc3fe429e0666b54bd67b3b3e9b846f7869aa8ea253e980167bd2502cfd4626
|
7
|
+
data.tar.gz: d5b3ce5d4189739c8a32851383775afe84bd238d324dc26806e122f2b19fd9a4dd65509c9b04ded9aae399b3eeff19bb3a01377155408b680ed533261401b1a3
|
data/README.md
CHANGED
@@ -58,6 +58,20 @@ my_timer = Ztimer.new(concurrency: 5) # create a new Ztimer instance
|
|
58
58
|
end
|
59
59
|
```
|
60
60
|
|
61
|
+
| Method | Description |
|
62
|
+
|--------|-------------|
|
63
|
+
| `async(&block)` | Execute the block asynchronously. |
|
64
|
+
| `after(milliseconds, &block)` | Execute the block after the specified amount of milliseconds. |
|
65
|
+
| `at(datetime, &block)` | Execute the block at the specified timestamp. |
|
66
|
+
| `every(milliseconds, start_at: nil, &block)` | Execute the block at the specified interval of milliseconds. A custom `:start_at` param could be provided to specify an offset timestamp. |
|
67
|
+
| `secondly(seconds, offset: 0, &block)` | Executes the block every N seconds. An `:offset` of seconds could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `secondly(5)` will run at second `0`, `5`, `10`, `15`, etc. |
|
68
|
+
| `minutely(minutes, offset: 0, &block)` | Executes the block every N minutes. An `:offset` of minutes could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `minutely(5)` will run at minute `0`, `5`, `10`, `15`, etc. |
|
69
|
+
| `hourly(hours, offset: 0, &block)` | Executes the block every N hours. An `:offset` of hours could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `hourly(5)` will run at hour `0`, `5`, `10`, `15`, etc. |
|
70
|
+
| `daily(days, offset: 0, &block)` | Executes the block every N days. An `:offset` of days could be specified to shift the begining of the time slot. By default the block will be exected at the begining of the time slot. Example: `daily(5)` will run on day `0`, `5`, `10`, `15`, etc. |
|
71
|
+
| `day_of_week(day, &block)` | Execute the block only on the specified day of week. Valid days are: `"sun", "mon", "tue", "thu", "wen", "fri", "sat"`. |
|
72
|
+
| `days_of_week(days, &block)` | Execute the block on the specified days of week. |
|
73
|
+
|
74
|
+
|
61
75
|
By default **Ztimer** will run at maximum 20 jobs concurrently, so that if you have 100 jobs to be
|
62
76
|
executed at the same time, at most 20 of them will run concurrently. This is necessary in order to prevent uncontrolled threads spawn when many jobs have to be run at the same time.
|
63
77
|
|
data/lib/ztimer/slot.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
class Ztimer
|
4
|
+
# Implements a slot, which represents a block of code to be executed at specified time slot.
|
3
5
|
class Slot
|
4
6
|
attr_reader :enqueued_at, :expires_at, :recurrency, :callback
|
5
7
|
attr_accessor :started_at, :executed_at
|
6
8
|
|
7
|
-
def initialize(enqueued_at, expires_at,recurrency = -1, &callback)
|
9
|
+
def initialize(enqueued_at, expires_at, recurrency = -1, &callback)
|
8
10
|
@enqueued_at = enqueued_at
|
9
11
|
@expires_at = expires_at
|
10
12
|
@recurrency = recurrency
|
@@ -15,17 +17,15 @@ class Ztimer
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def recurrent?
|
18
|
-
|
20
|
+
@recurrency.positive?
|
19
21
|
end
|
20
22
|
|
21
23
|
def reset!
|
22
|
-
if recurrent?
|
23
|
-
@expires_at += recurrency
|
24
|
-
end
|
24
|
+
@expires_at += recurrency if recurrent?
|
25
25
|
end
|
26
26
|
|
27
27
|
def canceled?
|
28
|
-
|
28
|
+
@canceled
|
29
29
|
end
|
30
30
|
|
31
31
|
def cancel!
|
@@ -33,7 +33,7 @@ class Ztimer
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def <=>(other)
|
36
|
-
|
36
|
+
@expires_at <=> other.expires_at
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
data/lib/ztimer/sorted_store.rb
CHANGED
@@ -1,95 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
class Ztimer
|
4
|
+
# Implements a performant sorted store for time slots, which uses binary search to optimize
|
5
|
+
# new items insertion and items retrievement.
|
3
6
|
class SortedStore
|
4
|
-
|
5
7
|
def initialize
|
6
8
|
@store = []
|
7
9
|
end
|
8
10
|
|
9
11
|
def <<(value)
|
10
12
|
@store.insert(position_for(value), value)
|
11
|
-
|
13
|
+
|
14
|
+
self
|
12
15
|
end
|
13
16
|
|
14
17
|
def delete(value)
|
15
18
|
index = index_of(value)
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
return nil
|
20
|
-
end
|
19
|
+
|
20
|
+
index.nil? ? nil : @store.delete_at(index)
|
21
21
|
end
|
22
22
|
|
23
23
|
def [](index)
|
24
|
-
|
24
|
+
@store[index]
|
25
25
|
end
|
26
26
|
|
27
27
|
def first
|
28
|
-
|
28
|
+
@store.first
|
29
29
|
end
|
30
30
|
|
31
31
|
def last
|
32
|
-
|
32
|
+
@store.last
|
33
33
|
end
|
34
34
|
|
35
35
|
def shift
|
36
|
-
|
36
|
+
@store.shift
|
37
37
|
end
|
38
38
|
|
39
39
|
def pop
|
40
|
-
|
40
|
+
@store.pop
|
41
41
|
end
|
42
42
|
|
43
43
|
def index_of(value, start = 0, stop = [@store.count - 1, 0].max)
|
44
|
-
if start > stop
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
when 1 then return index_of(value, position + 1, stop)
|
54
|
-
end
|
44
|
+
return nil if start > stop
|
45
|
+
return value == @store[start] ? start : nil if start == stop
|
46
|
+
|
47
|
+
position = ((stop + start) / 2).to_i
|
48
|
+
|
49
|
+
case value <=> @store[position]
|
50
|
+
when -1 then index_of(value, start, position)
|
51
|
+
when 0 then position
|
52
|
+
when 1 then index_of(value, position + 1, stop)
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
56
|
def count
|
59
|
-
|
57
|
+
@store.count
|
60
58
|
end
|
61
59
|
|
62
60
|
def size
|
63
|
-
|
61
|
+
@store.size
|
64
62
|
end
|
65
63
|
|
66
64
|
def empty?
|
67
|
-
|
65
|
+
@store.empty?
|
68
66
|
end
|
69
67
|
|
70
68
|
def clear
|
71
|
-
|
69
|
+
@store.clear
|
72
70
|
end
|
73
71
|
|
74
72
|
def to_a
|
75
|
-
|
73
|
+
@store.dup
|
76
74
|
end
|
77
75
|
|
78
|
-
|
79
76
|
protected
|
80
77
|
|
81
78
|
def position_for(item, start = 0, stop = [@store.count - 1, 0].max)
|
82
|
-
if start > stop
|
83
|
-
|
84
|
-
|
79
|
+
raise "Invalid range (#{start}, #{stop})" if start > stop
|
80
|
+
|
81
|
+
if start == stop
|
85
82
|
element = @store[start]
|
86
|
-
|
83
|
+
element.nil? || ((item <=> element) <= 0) ? start : start + 1
|
87
84
|
else
|
88
|
-
position = ((stop + start)/ 2).to_i
|
85
|
+
position = ((stop + start) / 2).to_i
|
89
86
|
case item <=> @store[position]
|
90
|
-
when -1 then
|
91
|
-
when 0 then
|
92
|
-
when 1 then
|
87
|
+
when -1 then position_for(item, start, position)
|
88
|
+
when 0 then position
|
89
|
+
when 1 then position_for(item, position + 1, stop)
|
93
90
|
end
|
94
91
|
end
|
95
92
|
end
|
data/lib/ztimer/version.rb
CHANGED
data/lib/ztimer/watcher.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
class Ztimer
|
4
|
+
# Implements a watcher which allows to enqueue Ztimer::Slot items, that will be executed
|
5
|
+
# as soon as the time of Ztimer::Slot is reached.
|
3
6
|
class Watcher
|
4
|
-
|
5
7
|
def initialize(&callback)
|
6
8
|
@thread = nil
|
7
9
|
@slots = Ztimer::SortedStore.new
|
@@ -10,17 +12,15 @@ class Ztimer
|
|
10
12
|
@mutex = Mutex.new
|
11
13
|
end
|
12
14
|
|
13
|
-
def <<
|
15
|
+
def <<(slot)
|
14
16
|
@mutex.synchronize do
|
15
17
|
@slots << slot
|
16
|
-
if @slots.first == slot
|
17
|
-
run
|
18
|
-
end
|
18
|
+
run if @slots.first == slot
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def jobs
|
23
|
-
|
23
|
+
@slots.size
|
24
24
|
end
|
25
25
|
|
26
26
|
protected
|
@@ -37,10 +37,11 @@ class Ztimer
|
|
37
37
|
def start
|
38
38
|
@lock.synchronize do
|
39
39
|
return if @thread
|
40
|
+
|
40
41
|
@thread = Thread.new do
|
41
42
|
loop do
|
42
43
|
begin
|
43
|
-
delay =
|
44
|
+
delay = calculate_delay
|
44
45
|
if delay.nil?
|
45
46
|
Thread.stop
|
46
47
|
next
|
@@ -48,10 +49,10 @@ class Ztimer
|
|
48
49
|
|
49
50
|
select(nil, nil, nil, delay / 1_000_000.to_f) if delay > 1 # 1 microsecond of cranularity
|
50
51
|
|
51
|
-
while
|
52
|
+
while fetch_first_expired
|
52
53
|
end
|
53
|
-
rescue => e
|
54
|
-
puts e.inspect
|
54
|
+
rescue StandardError => e
|
55
|
+
puts "#{e.inspect}\n#{e.backtrace.join("\n")}"
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
@@ -59,11 +60,11 @@ class Ztimer
|
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
|
-
def
|
63
|
-
|
63
|
+
def calculate_delay
|
64
|
+
@mutex.synchronize { @slots.empty? ? nil : @slots.first.expires_at - utc_microseconds }
|
64
65
|
end
|
65
66
|
|
66
|
-
def
|
67
|
+
def fetch_first_expired
|
67
68
|
@mutex.synchronize do
|
68
69
|
slot = @slots.first
|
69
70
|
if slot && (slot.expires_at < utc_microseconds)
|
@@ -89,7 +90,7 @@ class Ztimer
|
|
89
90
|
end
|
90
91
|
|
91
92
|
def utc_microseconds
|
92
|
-
|
93
|
+
Time.now.to_f * 1_000_000
|
93
94
|
end
|
94
95
|
end
|
95
96
|
end
|
data/lib/ztimer.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
require "ztimer/slot"
|
3
|
-
require "ztimer/sorted_store"
|
4
|
-
require "ztimer/watcher"
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
3
|
+
require 'ztimer/version'
|
4
|
+
require 'ztimer/slot'
|
5
|
+
require 'ztimer/sorted_store'
|
6
|
+
require 'ztimer/watcher'
|
7
|
+
|
8
|
+
# Implements a timer which allows to execute a block with a delay, recurrently or asynchronously.
|
6
9
|
class Ztimer
|
7
10
|
@default_instance = nil
|
8
11
|
|
@@ -10,7 +13,7 @@ class Ztimer
|
|
10
13
|
|
11
14
|
def initialize(concurrency: 20)
|
12
15
|
@concurrency = concurrency
|
13
|
-
@watcher = Ztimer::Watcher.new
|
16
|
+
@watcher = Ztimer::Watcher.new { |slot| execute(slot) }
|
14
17
|
@workers_lock = Mutex.new
|
15
18
|
@count_lock = Mutex.new
|
16
19
|
@queue = Queue.new
|
@@ -18,6 +21,7 @@ class Ztimer
|
|
18
21
|
@count = 0
|
19
22
|
end
|
20
23
|
|
24
|
+
# Execute the code block asyncrhonously right now
|
21
25
|
def async(&callback)
|
22
26
|
enqueued_at = utc_microseconds
|
23
27
|
slot = Slot.new(enqueued_at, enqueued_at, -1, &callback)
|
@@ -25,9 +29,10 @@ class Ztimer
|
|
25
29
|
incr_counter!
|
26
30
|
execute(slot)
|
27
31
|
|
28
|
-
|
32
|
+
slot
|
29
33
|
end
|
30
34
|
|
35
|
+
# Execute the code block after the specified delay
|
31
36
|
def after(milliseconds, &callback)
|
32
37
|
enqueued_at = utc_microseconds
|
33
38
|
expires_at = enqueued_at + milliseconds * 1000
|
@@ -35,39 +40,104 @@ class Ztimer
|
|
35
40
|
|
36
41
|
add(slot)
|
37
42
|
|
38
|
-
|
43
|
+
slot
|
39
44
|
end
|
40
45
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
# Execute the code block at a specific datetime
|
47
|
+
def at(datetime, &callback)
|
48
|
+
enqueued_at = datetime.to_f * 1_000_000
|
49
|
+
|
50
|
+
slot = Slot.new(enqueued_at, enqueued_at, -1, &callback)
|
51
|
+
add(slot)
|
52
|
+
|
53
|
+
slot
|
54
|
+
end
|
55
|
+
|
56
|
+
# Execute the code block every N milliseconds.
|
57
|
+
# When :start_at is specified, the first execution will start at specified date/time
|
58
|
+
def every(milliseconds, start_at: nil, &callback)
|
59
|
+
enqueued_at = start_at ? start_at.to_f * 1_000_000 : utc_microseconds
|
60
|
+
expires_at = enqueued_at + milliseconds * 1000
|
61
|
+
slot = Slot.new(enqueued_at, expires_at, milliseconds * 1000, &callback)
|
45
62
|
|
46
63
|
add(slot)
|
47
64
|
|
48
|
-
|
65
|
+
slot
|
66
|
+
end
|
67
|
+
|
68
|
+
# Run ztimer every N seconds, starting with the nearest time slot (ex. secondly(5)
|
69
|
+
# will run at second 0, 5, 10, 15, etc.)
|
70
|
+
def secondly(seconds, offset: 0, &callback)
|
71
|
+
start_time = utc_microseconds
|
72
|
+
milliseconds = (seconds.to_f * 1000).to_i
|
73
|
+
enqueued_at = start_time - (start_time % (milliseconds * 1000)) + offset * 1_000_000
|
74
|
+
expires_at = enqueued_at + milliseconds * 1000
|
75
|
+
|
76
|
+
slot = Slot.new(enqueued_at, expires_at, milliseconds * 1000, &callback)
|
77
|
+
add(slot)
|
78
|
+
|
79
|
+
slot
|
80
|
+
end
|
81
|
+
|
82
|
+
# Run ztimer every N minutes, starting at the nearest time slot (ex. minutely(2) will run at minute 0, 2, 4, 6, etc.)
|
83
|
+
def minutely(minutes, offset: 0, &callback)
|
84
|
+
secondly(minutes.to_f * 60, offset: offset.to_f * 60, &callback)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Run ztimer every N hours, starting at the nearest time slot (ex. hourly(2) will run at hour 0, 2, 4, 6, etc.)
|
88
|
+
def hourly(hours, offset: 0, &callback)
|
89
|
+
minutely(hours.to_f * 60, offset: offset.to_f * 60, &callback)
|
90
|
+
end
|
91
|
+
|
92
|
+
def daily(days, offset: 0, &callback)
|
93
|
+
raise ArgumentError, "Days number should be > 0: #{days.inspect}" if days.to_f <= 0
|
94
|
+
|
95
|
+
hourly(days.to_f * 24, offset: offset.to_f * 24, &callback)
|
96
|
+
end
|
97
|
+
|
98
|
+
def day_of_week(day, &callback)
|
99
|
+
days = %w[sun mon tue thu wen fri sat]
|
100
|
+
current_day = Time.now.wday
|
101
|
+
|
102
|
+
index = day.to_i
|
103
|
+
if day.is_a?(String)
|
104
|
+
# Find day number by day name
|
105
|
+
index = days.index { |day_name| day.strip.downcase == day_name }
|
106
|
+
raise ArgumentError, "Invalid week day: #{day.inspect}" if index.nil?
|
107
|
+
elsif index.negative? || index > 6
|
108
|
+
raise ArgumentError, "Invalid week day: #{day.inspect}"
|
109
|
+
end
|
110
|
+
|
111
|
+
offset = 0
|
112
|
+
offset = (current_day > index ? index - current_day : current_day - index) if current_day != index
|
113
|
+
|
114
|
+
daily(7, offset: offset, &callback)
|
115
|
+
end
|
116
|
+
|
117
|
+
def days_of_week(*args, &callback)
|
118
|
+
args.map { |day| day_of_week(day, &callback) }
|
49
119
|
end
|
50
120
|
|
51
121
|
def jobs_count
|
52
|
-
|
122
|
+
@watcher.jobs
|
53
123
|
end
|
54
124
|
|
55
125
|
def concurrency=(new_value)
|
56
|
-
|
126
|
+
value_is_integer = new_value.is_a?(Integer)
|
127
|
+
raise ArgumentError, "Invalid concurrency value: #{new_value}" unless value_is_integer && new_value >= 1
|
128
|
+
|
57
129
|
@concurrency = new_value
|
58
130
|
end
|
59
131
|
|
60
|
-
|
61
132
|
def stats
|
62
133
|
{
|
63
|
-
running:
|
134
|
+
running: @running,
|
64
135
|
scheduled: @watcher.jobs,
|
65
136
|
executing: @queue.size,
|
66
|
-
total:
|
137
|
+
total: @count
|
67
138
|
}
|
68
139
|
end
|
69
140
|
|
70
|
-
|
71
141
|
def self.method_missing(name, *args, &block)
|
72
142
|
@default_instance ||= Ztimer.new(concurrency: 20)
|
73
143
|
@default_instance.send(name, *args, &block)
|
@@ -108,12 +178,13 @@ class Ztimer
|
|
108
178
|
begin
|
109
179
|
current_slot.executed_at = utc_microseconds
|
110
180
|
current_slot.callback.call(current_slot) unless current_slot.callback.nil? || current_slot.canceled?
|
111
|
-
rescue => e
|
112
|
-
|
181
|
+
rescue StandardError => e
|
182
|
+
backtrace = e.backtrace ? "\n#{e.backtrace.join("\n")}" : ''
|
183
|
+
warn e.inspect + backtrace
|
113
184
|
end
|
114
185
|
end
|
115
186
|
rescue ThreadError
|
116
|
-
puts
|
187
|
+
puts 'queue is empty'
|
117
188
|
end
|
118
189
|
@workers_lock.synchronize { @running -= 1 }
|
119
190
|
end
|
@@ -121,6 +192,6 @@ class Ztimer
|
|
121
192
|
end
|
122
193
|
|
123
194
|
def utc_microseconds
|
124
|
-
|
195
|
+
Time.now.to_f * 1_000_000
|
125
196
|
end
|
126
197
|
end
|
data/ztimer.gemspec
CHANGED
@@ -1,25 +1,28 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'ztimer/version'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
8
|
+
spec.name = 'ztimer'
|
8
9
|
spec.version = Ztimer::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
10
|
+
spec.authors = ['Groza Sergiu']
|
11
|
+
spec.email = ['serioja90@gmail.com']
|
11
12
|
|
12
|
-
spec.summary = %
|
13
|
-
spec.description = %
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
13
|
+
spec.summary = %(An asyncrhonous timer)
|
14
|
+
spec.description = %(Ruby asyncrhonous timer that allows you to enqueue tasks to be executed asyncrhonously after a delay)
|
15
|
+
spec.homepage = 'https://github.com/serioja90/ztimer'
|
16
|
+
spec.license = 'MIT'
|
16
17
|
|
17
18
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
-
spec.bindir =
|
19
|
+
spec.bindir = 'exe'
|
19
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
-
spec.require_paths = [
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.required_ruby_version = '>= 2.5'
|
21
24
|
|
22
|
-
spec.add_development_dependency
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
+
spec.add_development_dependency 'bundler', '~>2.2', '>= 2.2.33'
|
26
|
+
spec.add_development_dependency 'rake', '~>12.3', '>= 12.3.3'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
25
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ztimer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Groza Sergiu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,28 +16,40 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.2.33
|
20
23
|
type: :development
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - "~>"
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
29
|
+
version: '2.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.2.33
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: rake
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - "~>"
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
39
|
+
version: '12.3'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 12.3.3
|
34
43
|
type: :development
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
47
|
- - "~>"
|
39
48
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
49
|
+
version: '12.3'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 12.3.3
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: rspec
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,15 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
99
|
requirements:
|
88
100
|
- - ">="
|
89
101
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
102
|
+
version: '2.5'
|
91
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
104
|
requirements:
|
93
105
|
- - ">="
|
94
106
|
- !ruby/object:Gem::Version
|
95
107
|
version: '0'
|
96
108
|
requirements: []
|
97
|
-
|
98
|
-
rubygems_version: 2.4.8
|
109
|
+
rubygems_version: 3.0.8
|
99
110
|
signing_key:
|
100
111
|
specification_version: 4
|
101
112
|
summary: An asyncrhonous timer
|