ztimer 0.4.3 → 1.0.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: 5e403be0894d09cc0bbc6c390b101665532e8a18
4
- data.tar.gz: eb034343767cadf543893013f0e115319f51b2ed
2
+ SHA256:
3
+ metadata.gz: 328b728b6ba74021474c30ef9093a1bca987a368b2bd211d5545abc888447ef2
4
+ data.tar.gz: df0ff61ae1bbf3901a1ddc9fa0ac3416dce79eeb1c4e05f1d9a3f61abce722c0
5
5
  SHA512:
6
- metadata.gz: f80e696e261cdc59c62e1e4daae92d0734dd432130f8c18cdd80f7f2b701c0ec945344f12ce390ff8e797c900812ca6d31b9fe91e4fce81d3301404f0a3407ed
7
- data.tar.gz: 786b8c931a6775197fb0475f92f5a3a223fb7845ed3a2f6a85faa920413ead1b32366424ddb80964c15152f367d53ccbaa15ecfbef01bdfee212db51cbf527b5
6
+ metadata.gz: d482e9792d40f7083258a66cac5b1857624d02dbc6f4297a3ea608c48f8f50908dc3fe429e0666b54bd67b3b3e9b846f7869aa8ea253e980167bd2502cfd4626
7
+ data.tar.gz: d5b3ce5d4189739c8a32851383775afe84bd238d324dc26806e122f2b19fd9a4dd65509c9b04ded9aae399b3eeff19bb3a01377155408b680ed533261401b1a3
data/.travis.yml CHANGED
@@ -1,4 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.1
3
+ - 2.2
4
+
4
5
  before_install: gem install bundler -v 1.11.2
6
+ script:
7
+ - gem build ztimer.gemspec
8
+ - gem install ztimer --local
9
+ - bundle exec rspec
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in ztimer.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'lounger', '~> 0.2.0'
8
+ end
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Ztimer
2
2
 
3
- **Ztimer** is a Ruby gem that allows to get asynchronous delayed notifications. You can enqueue callbacks to be
4
- called after some amount of time.
3
+ **Ztimer** is a simple Ruby implementation of an asynchronous timer, that allows to enqueue the execution of Ruby
4
+ code, so that it will be asynchronously executed on timeout. It's very useful when you need a simple way to execute
5
+ some code asynchronously or with a certain delay.
6
+
5
7
 
6
8
  ## Installation
7
9
 
@@ -32,6 +34,12 @@ Ztimer.after(delay) do
32
34
  puts "Doing something useful..."
33
35
  end
34
36
 
37
+ # Async execution
38
+ Ztimer.async do
39
+ # this code will be executed in background asyncronously
40
+ puts "Doing something useful in background..."
41
+ end
42
+
35
43
  # Recurrent jobs
36
44
  job = Ztimer.every(delay) do # execute the block every second
37
45
  puts "Executing a recurrent job..."
@@ -40,17 +48,41 @@ end
40
48
  sleep 10 # wait for 10 seconds (10 executions)
41
49
  job.cancel! # cancel the recurrent job
42
50
 
51
+ # Custom Ztimer instance
52
+ my_timer = Ztimer.new(concurrency: 5) # create a new Ztimer instance
53
+ 10.times do
54
+ # Use the custom ztimer to execute jobs asynchronously
55
+ my_timer.async do
56
+ puts "Doing async job..."
57
+ end
58
+ end
43
59
  ```
44
60
 
45
- By default **Ztimer** will run at maximum 20 notifications concurrently, so that if you have 100 notifications to be
46
- executed at the same time, at maximum 20 of them will run at the same time. This is necessary in order to prevent uncontrolled
47
- threads spawn when many notifications have to be sent at the same time.
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. |
48
73
 
49
- Anyway, you can change the concurrency by calling `Ztimer.concurrency = <concurrency>`, where `<concurrency>` is the maximum number
50
- of `Ztimer` workers allowed to run in parallel (ex: `Ztimer.concurrency = 50`).
51
74
 
52
- ## Contributing
75
+ By default **Ztimer** will run at maximum 20 jobs concurrently, so that if you have 100 jobs to be
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.
77
+
78
+ Anyway, you can change the concurrency level by calling `Ztimer.concurrency = <concurrency>`, where `<concurrency>` is the maximum number of `Ztimer` workers allowed to run in parallel (ex: `Ztimer.concurrency = 50`).
53
79
 
54
- Bug reports and pull requests are welcome on GitHub at https://github.com/serioja90/ztimer. This project is intended to be a safe,
55
- welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
80
+ If you're using custom **Ztimer** instance, you can specify the concurrency while creating the new instance:
81
+
82
+ ```ruby
83
+ my_timer = Ztimer.new(concurrency: 42) # create a ztimer with concurrency set to 42
84
+ ```
85
+
86
+ ## Contributing
56
87
 
88
+ Bug reports and pull requests are welcome on GitHub at https://github.com/serioja90/ztimer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/lib/ztimer/slot.rb CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
1
2
 
2
- module Ztimer
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 @@ module Ztimer
15
17
  end
16
18
 
17
19
  def recurrent?
18
- return @recurrency > 0
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
- return @canceled
28
+ @canceled
29
29
  end
30
30
 
31
31
  def cancel!
@@ -33,7 +33,7 @@ module Ztimer
33
33
  end
34
34
 
35
35
  def <=>(other)
36
- return @expires_at <=> other.expires_at
36
+ @expires_at <=> other.expires_at
37
37
  end
38
38
  end
39
- end
39
+ end
@@ -1,97 +1,94 @@
1
+ # frozen_string_literal: true
1
2
 
2
- module Ztimer
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
- return self
13
+
14
+ self
12
15
  end
13
16
 
14
17
  def delete(value)
15
18
  index = index_of(value)
16
- if index
17
- @store.delete_at(index)
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
- return @store[index]
24
+ @store[index]
25
25
  end
26
26
 
27
27
  def first
28
- return @store.first
28
+ @store.first
29
29
  end
30
30
 
31
31
  def last
32
- return @store.last
32
+ @store.last
33
33
  end
34
34
 
35
35
  def shift
36
- return @store.shift
36
+ @store.shift
37
37
  end
38
38
 
39
39
  def pop
40
- return @store.pop
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
- return nil
46
- elsif start == stop
47
- return value == @store[start] ? start : nil
48
- else
49
- position = ((stop + start)/ 2).to_i
50
- case value <=> @store[position]
51
- when -1 then return index_of(value, start, position)
52
- when 0 then return position
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
- return @store.count
57
+ @store.count
60
58
  end
61
59
 
62
60
  def size
63
- return @store.size
61
+ @store.size
64
62
  end
65
63
 
66
64
  def empty?
67
- return @store.empty?
65
+ @store.empty?
68
66
  end
69
67
 
70
68
  def clear
71
- return @store.clear
69
+ @store.clear
72
70
  end
73
71
 
74
72
  def to_a
75
- return @store.dup
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
- raise "Invalid range (#{start}, #{stop})"
84
- elsif start == stop
79
+ raise "Invalid range (#{start}, #{stop})" if start > stop
80
+
81
+ if start == stop
85
82
  element = @store[start]
86
- return element.nil? || ((item <=> element) <= 0) ? start : start + 1
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 return position_for(item, start, position)
91
- when 0 then return position
92
- when 1 then return position_for(item, position + 1, stop)
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
96
93
  end
97
- end
94
+ end
@@ -1,3 +1,5 @@
1
- module Ztimer
2
- VERSION = "0.4.3"
1
+ # frozen_string_literal: true
2
+
3
+ class Ztimer
4
+ VERSION = '1.0.0'
3
5
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
1
2
 
2
- module Ztimer
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 @@ module Ztimer
10
12
  @mutex = Mutex.new
11
13
  end
12
14
 
13
- def << (slot)
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
- return @slots.size
23
+ @slots.size
24
24
  end
25
25
 
26
26
  protected
@@ -37,10 +37,11 @@ module 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 = get_delay
44
+ delay = calculate_delay
44
45
  if delay.nil?
45
46
  Thread.stop
46
47
  next
@@ -48,10 +49,10 @@ module 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 get_first_expired do
52
+ while fetch_first_expired
52
53
  end
53
- rescue => e
54
- puts e.inspect + "\n" + e.backtrace.join("\n")
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 @@ module Ztimer
59
60
  end
60
61
  end
61
62
 
62
- def get_delay
63
- return @mutex.synchronize { @slots.empty? ? nil : @slots.first.expires_at - utc_microseconds }
63
+ def calculate_delay
64
+ @mutex.synchronize { @slots.empty? ? nil : @slots.first.expires_at - utc_microseconds }
64
65
  end
65
66
 
66
- def get_first_expired
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 @@ module Ztimer
89
90
  end
90
91
 
91
92
  def utc_microseconds
92
- return Time.now.to_f * 1_000_000
93
+ Time.now.to_f * 1_000_000
93
94
  end
94
95
  end
95
- end
96
+ end
data/lib/ztimer.rb CHANGED
@@ -1,83 +1,197 @@
1
- require "ztimer/version"
2
- require "ztimer/slot"
3
- require "ztimer/sorted_store"
4
- require "ztimer/watcher"
5
-
6
- module Ztimer
7
- @concurrency = 20
8
- @watcher = Ztimer::Watcher.new(){|slot| execute(slot) }
9
- @workers_lock = Mutex.new
10
- @queue = Queue.new
11
- @running = 0
12
- @count = 0
13
-
14
- class << self
15
- attr_reader :concurrency, :running, :count
16
-
17
- def after(milliseconds, &callback)
18
- enqueued_at = utc_microseconds
19
- expires_at = enqueued_at + milliseconds * 1000
20
- slot = Slot.new(enqueued_at, expires_at, -1, &callback)
21
-
22
- add(slot)
23
-
24
- return slot
25
- end
1
+ # frozen_string_literal: true
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.
9
+ class Ztimer
10
+ @default_instance = nil
11
+
12
+ attr_reader :concurrency, :running, :count, :watcher, :queue
13
+
14
+ def initialize(concurrency: 20)
15
+ @concurrency = concurrency
16
+ @watcher = Ztimer::Watcher.new { |slot| execute(slot) }
17
+ @workers_lock = Mutex.new
18
+ @count_lock = Mutex.new
19
+ @queue = Queue.new
20
+ @running = 0
21
+ @count = 0
22
+ end
26
23
 
27
- def every(milliseconds, &callback)
28
- enqueued_at = utc_microseconds
29
- expires_at = enqueued_at + milliseconds * 1000
30
- slot = Slot.new(enqueued_at, expires_at, milliseconds * 1000, &callback)
24
+ # Execute the code block asyncrhonously right now
25
+ def async(&callback)
26
+ enqueued_at = utc_microseconds
27
+ slot = Slot.new(enqueued_at, enqueued_at, -1, &callback)
31
28
 
32
- add(slot)
29
+ incr_counter!
30
+ execute(slot)
33
31
 
34
- return slot
35
- end
32
+ slot
33
+ end
36
34
 
37
- def jobs_count
38
- return @watcher.jobs
39
- end
35
+ # Execute the code block after the specified delay
36
+ def after(milliseconds, &callback)
37
+ enqueued_at = utc_microseconds
38
+ expires_at = enqueued_at + milliseconds * 1000
39
+ slot = Slot.new(enqueued_at, expires_at, -1, &callback)
40
40
 
41
- def concurrency=(new_value)
42
- raise ArgumentError.new("Invalid concurrency value: #{new_value}") unless new_value.is_a?(Fixnum) && new_value >= 1
43
- @concurrency = new_value
44
- end
41
+ add(slot)
42
+
43
+ slot
44
+ end
45
+
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
- protected
63
+ add(slot)
64
+
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
47
97
 
48
- def add(slot)
49
- @count += 1
50
- @watcher << slot
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}"
51
109
  end
52
110
 
111
+ offset = 0
112
+ offset = (current_day > index ? index - current_day : current_day - index) if current_day != index
53
113
 
54
- def execute(slot)
55
- @queue << slot
56
-
57
- @workers_lock.synchronize do
58
- [@concurrency - @running, @queue.size].min.times do
59
- @running += 1
60
- worker = Thread.new do
61
- begin
62
- while !@queue.empty? && (slot = @queue.pop(true)) do
63
- slot.executed_at = utc_microseconds
64
- slot.callback.call(slot) unless slot.callback.nil?
65
- end
66
- rescue ThreadError
67
- # queue is empty
68
- puts "queue is empty"
69
- rescue => e
70
- STDERR.puts e.inspect + (e.backtrace ? "\n" + e.backtrace.join("\n") : "")
71
- end
72
- @workers_lock.synchronize { @running -= 1 }
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) }
119
+ end
120
+
121
+ def jobs_count
122
+ @watcher.jobs
123
+ end
124
+
125
+ def concurrency=(new_value)
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
+
129
+ @concurrency = new_value
130
+ end
131
+
132
+ def stats
133
+ {
134
+ running: @running,
135
+ scheduled: @watcher.jobs,
136
+ executing: @queue.size,
137
+ total: @count
138
+ }
139
+ end
140
+
141
+ def self.method_missing(name, *args, &block)
142
+ @default_instance ||= Ztimer.new(concurrency: 20)
143
+ @default_instance.send(name, *args, &block)
144
+ end
145
+
146
+ protected
147
+
148
+ def add(slot)
149
+ incr_counter!
150
+ @watcher << slot
151
+ end
152
+
153
+ def incr_counter!
154
+ @count_lock.synchronize{ @count += 1 }
155
+ end
156
+
157
+ def execute(slot)
158
+ @queue << slot
159
+
160
+ @workers_lock.synchronize do
161
+ [@concurrency - @running, @queue.size].min.times do
162
+ @running += 1
163
+ start_new_thread!
164
+ end
165
+ end
166
+ end
167
+
168
+ def start_new_thread!
169
+ worker = Thread.new do
170
+ begin
171
+ loop do
172
+ current_slot = nil
173
+ @workers_lock.synchronize do
174
+ current_slot = @queue.pop(true) unless @queue.empty?
175
+ end
176
+ break if current_slot.nil?
177
+
178
+ begin
179
+ current_slot.executed_at = utc_microseconds
180
+ current_slot.callback.call(current_slot) unless current_slot.callback.nil? || current_slot.canceled?
181
+ rescue StandardError => e
182
+ backtrace = e.backtrace ? "\n#{e.backtrace.join("\n")}" : ''
183
+ warn e.inspect + backtrace
73
184
  end
74
- worker.abort_on_exception = true
75
185
  end
186
+ rescue ThreadError
187
+ puts 'queue is empty'
76
188
  end
189
+ @workers_lock.synchronize { @running -= 1 }
77
190
  end
191
+ worker.abort_on_exception = true
192
+ end
78
193
 
79
- def utc_microseconds
80
- return Time.now.to_f * 1_000_000
81
- end
194
+ def utc_microseconds
195
+ Time.now.to_f * 1_000_000
82
196
  end
83
- end
197
+ end
data/ztimer.gemspec CHANGED
@@ -1,25 +1,28 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
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 = "ztimer"
8
+ spec.name = 'ztimer'
8
9
  spec.version = Ztimer::VERSION
9
- spec.authors = ["Groza Sergiu"]
10
- spec.email = ["serioja90@gmail.com"]
10
+ spec.authors = ['Groza Sergiu']
11
+ spec.email = ['serioja90@gmail.com']
11
12
 
12
- spec.summary = %q{An asyncrhonous timer}
13
- spec.description = %q{Ruby asyncrhonous timer that allows you to enqueue tasks to be executed asyncrhonously after a delay}
14
- spec.homepage = "https://github.com/serioja90/ztimer"
15
- spec.license = "MIT"
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 = "exe"
19
+ spec.bindir = 'exe'
19
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
- spec.require_paths = ["lib"]
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = '>= 2.5'
21
24
 
22
- spec.add_development_dependency "bundler", "~> 1.11"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "rspec", "~> 3.0"
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.3
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: 2016-04-20 00:00:00.000000000 Z
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: '1.11'
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: '1.11'
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: '10.0'
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: '10.0'
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,17 +99,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
99
  requirements:
88
100
  - - ">="
89
101
  - !ruby/object:Gem::Version
90
- version: '0'
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
- rubyforge_project:
98
- rubygems_version: 2.4.6
109
+ rubygems_version: 3.0.8
99
110
  signing_key:
100
111
  specification_version: 4
101
112
  summary: An asyncrhonous timer
102
113
  test_files: []
103
- has_rdoc: