async 1.23.0 → 1.24.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dc28a8cd53cda6b82492cf8a1ed3c2a7786f4f6a29da3935be00298e25eaaea
4
- data.tar.gz: c70dfa854738d6eccd197f322312ed1ea810378428b08fba18ca24076a5d98d4
3
+ metadata.gz: 2edec7ff8d1bdb38814250485c36cfe4128a627c81a6976124cb689f9adf144a
4
+ data.tar.gz: 885288ad542ef4758f3a2b1fe2d5ce2bcd13ff1c71992262927ecca7ee21dbb0
5
5
  SHA512:
6
- metadata.gz: d305bdc4e707ba26b602809669ba8972017be01b7a599e98c471bf969e434a05b5ef725b3853793f7a82a2e7dc13c89231e383d6b06ffbadb966d77eaa36015a
7
- data.tar.gz: 58673e31ac2e10e82062fd2def69e040c7780bf6179eb5092bed365ef3564a61d1553a63b45a151b53b64fe43cea4d7139fe0e844b7755083b13436c0008fd42
6
+ metadata.gz: 6919a8b9bb8d54dce220024d0c8418a303cb2bb43dbeb6878491a9491c64ab45d953ca83831b6cccfcf6ee12d0db19a47509a981f3841591061e1d3240a75467
7
+ data.tar.gz: 41f251a4cf7fa2982f974964e6c7cf46d323364c53c34cce367475fe92e252433633967b4cf22a2a2edac40819ab8ec1b03207fe0e5d22febc2d96c1fdf795a8
@@ -11,7 +11,6 @@ jobs:
11
11
  # - macos
12
12
 
13
13
  ruby:
14
- - 2.3
15
14
  - 2.4
16
15
  - 2.5
17
16
  - 2.6
data/README.md CHANGED
@@ -5,7 +5,7 @@ Async is a composable asynchronous I/O framework for Ruby based on [nio4r] and [
5
5
  [timers]: https://github.com/socketry/timers
6
6
  [nio4r]: https://github.com/socketry/nio4r
7
7
 
8
- [![Build Status](https://secure.travis-ci.org/socketry/async.svg)](http://travis-ci.org/socketry/async)
8
+ [![Build Status](https://secure.travis-ci.com/socketry/async.svg)](http://travis-ci.com/socketry/async)
9
9
  [![Code Climate](https://codeclimate.com/github/socketry/async.svg)](https://codeclimate.com/github/socketry/async)
10
10
  [![Coverage Status](https://coveralls.io/repos/socketry/async/badge.svg)](https://coveralls.io/r/socketry/async)
11
11
  [![Gitter](https://badges.gitter.im/join.svg)](https://gitter.im/socketry/async)
@@ -192,9 +192,9 @@ Async do |task|
192
192
  end
193
193
  ```
194
194
 
195
- #### Stopping Reactors
195
+ #### Embedding Reactors
196
196
 
197
- `Async::Reactor#run` will run until the reactor runs out of work to do or is explicitly stopped.
197
+ `Async::Reactor#run` will run until the reactor runs out of work to do. To run a single iteration of the reactor, use `Async::Reactor#run_once`
198
198
 
199
199
  ```ruby
200
200
  require 'async'
@@ -203,13 +203,25 @@ Async.logger.debug!
203
203
  reactor = Async::Reactor.new
204
204
 
205
205
  # Run the reactor for 1 second:
206
- reactor.run do |task|
206
+ reactor.async do |task|
207
207
  task.sleep 1
208
- reactor.stop
208
+ puts "Finished!"
209
+ end
210
+
211
+ while reactor.run_once
212
+ # Round and round we go!
209
213
  end
210
214
  ```
211
215
 
212
- You can use this approach to embed the reactor in another event loop. `Async::Reactor#stop` is can be called safely from a different thread.
216
+ You can use this approach to embed the reactor in another event loop.
217
+
218
+ #### Stopping Reactors
219
+
220
+ `Async::Reactor#stop` will stop the current reactor and all children tasks.
221
+
222
+ #### Interrupting Reactors
223
+
224
+ `Async::Reactor#interrupt` can be called safely from a different thread (or signal handler) and will cause the reactor to invoke `#stop`.
213
225
 
214
226
  ### Resource Management
215
227
 
@@ -26,7 +26,7 @@ require_relative "kernel/async"
26
26
 
27
27
  module Async
28
28
  # Invoke `Reactor.run` with all arguments/block.
29
- def self.run(*args, &block)
30
- Reactor.run(*args, &block)
29
+ def self.run(*arguments, &block)
30
+ Reactor.run(*arguments, &block)
31
31
  end
32
32
  end
@@ -23,8 +23,10 @@ require_relative 'task'
23
23
  module Async
24
24
  # A semaphore is used to control access to a common resource in a concurrent system. A useful way to think of a semaphore as used in the real-world systems is as a record of how many units of a particular resource are available, coupled with operations to adjust that record safely (i.e. to avoid race conditions) as units are required or become free, and, if necessary, wait until a unit of the resource becomes available.
25
25
  class Barrier
26
- def initialize
26
+ def initialize(parent: nil)
27
27
  @tasks = []
28
+
29
+ @parent = parent
28
30
  end
29
31
 
30
32
  # All tasks which have been invoked into the barrier.
@@ -34,8 +36,8 @@ module Async
34
36
  @tasks.size
35
37
  end
36
38
 
37
- def async(*args, parent: Task.current, **options, &block)
38
- task = parent.async(*args, **options, &block)
39
+ def async(*arguments, parent: (@parent or Task.current), **options, &block)
40
+ task = parent.async(*arguments, **options, &block)
39
41
 
40
42
  @tasks << task
41
43
 
@@ -31,12 +31,12 @@ module Async
31
31
  @monitor.close
32
32
  end
33
33
 
34
- def method_missing(*args, &block)
35
- @monitor.send(*args)
34
+ def method_missing(*arguments, &block)
35
+ @monitor.send(*arguments)
36
36
  end
37
37
 
38
- def respond_to?(*args)
39
- @monitor.respond_to?(*args)
38
+ def respond_to?(*arguments)
39
+ @monitor.respond_to?(*arguments)
40
40
  end
41
41
 
42
42
  def inspect
@@ -79,8 +79,8 @@ module Async
79
79
  @selector.close
80
80
  end
81
81
 
82
- def select(*args)
83
- @selector.select(*args)
82
+ def select(*arguments)
83
+ @selector.select(*arguments)
84
84
  end
85
85
  end
86
86
  end
@@ -132,6 +132,10 @@ module Async
132
132
  end
133
133
  end
134
134
 
135
+ def stop
136
+ @children&.each(&:stop)
137
+ end
138
+
135
139
  def print_hierarchy(out = $stdout)
136
140
  self.traverse do |node, level|
137
141
  out.puts "#{"\t" * level}#{node}"
@@ -23,10 +23,11 @@ require_relative 'notification'
23
23
  module Async
24
24
  # A queue which allows items to be processed in order.
25
25
  class Queue < Notification
26
- def initialize
27
- super
26
+ def initialize(parent: nil)
27
+ super()
28
28
 
29
29
  @items = []
30
+ @parent = parent
30
31
  end
31
32
 
32
33
  attr :items
@@ -47,21 +48,26 @@ module Async
47
48
  @items.shift
48
49
  end
49
50
 
50
- def async(&block)
51
- parent = Task.current
52
-
51
+ def async(parent: (@parent or Task.current), &block)
53
52
  while item = self.dequeue
54
53
  parent.async(item, &block)
55
54
  end
56
55
  end
56
+
57
+ def each
58
+ while item = self.dequeue
59
+ yield item
60
+ end
61
+ end
57
62
  end
58
63
 
59
64
  class LimitedQueue < Queue
60
- def initialize(limit = 1)
61
- super()
65
+ def initialize(limit = 1, **options)
66
+ super(**options)
62
67
 
63
68
  @limit = limit
64
- @full = Async::Queue.new
69
+
70
+ @full = Notification.new
65
71
  end
66
72
 
67
73
  attr :limit
@@ -72,8 +78,8 @@ module Async
72
78
  end
73
79
 
74
80
  def enqueue item
75
- if limited?
76
- @full.dequeue
81
+ while limited?
82
+ @full.wait
77
83
  end
78
84
 
79
85
  super
@@ -82,7 +88,7 @@ module Async
82
88
  def dequeue
83
89
  item = super
84
90
 
85
- @full.enqueue(nil) unless @full.empty?
91
+ @full.signal
86
92
 
87
93
  return item
88
94
  end
@@ -42,16 +42,16 @@ module Async
42
42
  # - When invoked at the top level, will create and run a reactor, and invoke
43
43
  # the block as an asynchronous task. Will block until the reactor finishes
44
44
  # running.
45
- def self.run(*args, **options, &block)
45
+ def self.run(*arguments, **options, &block)
46
46
  if current = Task.current?
47
47
  reactor = current.reactor
48
48
 
49
- return reactor.async(*args, **options, &block)
49
+ return reactor.async(*arguments, **options, &block)
50
50
  else
51
51
  reactor = self.new(**options)
52
52
 
53
53
  begin
54
- return reactor.run(*args, &block)
54
+ return reactor.run(*arguments, &block)
55
55
  ensure
56
56
  reactor.close
57
57
  end
@@ -80,7 +80,8 @@ module Async
80
80
  @ready = []
81
81
  @running = []
82
82
 
83
- @stopped = true
83
+ @interrupted = false
84
+ @guard = Mutex.new
84
85
  end
85
86
 
86
87
  def logger
@@ -88,14 +89,11 @@ module Async
88
89
  end
89
90
 
90
91
  def to_s
91
- "\#<#{self.description} (#{@stopped ? 'stopped' : 'running'})>"
92
+ "\#<#{self.description} #{@children&.size || 0} children #{stopped? ? 'stopped' : 'running'}>"
92
93
  end
93
94
 
94
- # @attr stopped [Boolean]
95
- attr :stopped
96
-
97
95
  def stopped?
98
- @stopped
96
+ @children.nil? || @children.empty?
99
97
  end
100
98
 
101
99
  # TODO Remove these in next major release. They are too confusing to use correctly.
@@ -109,7 +107,7 @@ module Async
109
107
  #
110
108
  # @yield [Task] Executed within the task.
111
109
  # @return [Task] The task that was scheduled into the reactor.
112
- def async(*args, **options, &block)
110
+ def async(*arguments, **options, &block)
113
111
  task = Task.new(self, **options, &block)
114
112
 
115
113
  # I want to take a moment to explain the logic of this.
@@ -119,7 +117,7 @@ module Async
119
117
  # - Fail at the point of the method call where possible.
120
118
  # - Execute determinstically where possible.
121
119
  # - Avoid scheduler overhead if no blocking operation is performed.
122
- task.run(*args)
120
+ task.run(*arguments)
123
121
 
124
122
  # logger.debug "Initial execution of task #{fiber} complete (#{result} -> #{fiber.alive?})..."
125
123
  return task
@@ -132,12 +130,13 @@ module Async
132
130
  return monitor
133
131
  end
134
132
 
135
- # Stop the reactor at the earliest convenience. Can be called from a different thread safely.
136
- # @return [void]
137
- def stop
138
- unless @stopped
139
- @stopped = true
140
- @selector.wakeup
133
+ # Interrupt the reactor at the earliest convenience. Can be called from a different thread safely.
134
+ def interrupt
135
+ @guard.synchronize do
136
+ unless @interrupted
137
+ @interrupted = true
138
+ @selector.wakeup
139
+ end
141
140
  end
142
141
  end
143
142
 
@@ -155,73 +154,95 @@ module Async
155
154
  end
156
155
 
157
156
  def finished?
158
- # I'm not sure if checking `@running.empty?` is really required.
157
+ # TODO I'm not sure if checking `@running.empty?` is really required.
159
158
  super && @ready.empty? && @running.empty?
160
159
  end
161
160
 
162
- # Run the reactor until either all tasks complete or {#stop} is invoked.
163
- # Proxies arguments to {#async} immediately before entering the loop.
164
- def run(*args, &block)
165
- raise RuntimeError, 'Reactor has been closed' if @selector.nil?
161
+ # Run one iteration of the event loop.
162
+ # @param timeout [Float | nil] the maximum timeout, or if nil, indefinite.
163
+ # @return [Boolean] whether there is more work to do.
164
+ def run_once(timeout = nil)
165
+ logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
166
166
 
167
- @stopped = false
168
-
169
- initial_task = self.async(*args, &block) if block_given?
170
-
171
- until @stopped
172
- logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
167
+ if @ready.any?
168
+ # running used to correctly answer on `finished?`, and to reuse Array object.
169
+ @running, @ready = @ready, @running
173
170
 
174
- if @ready.any?
175
- # running used to correctly answer on `finished?`, and to reuse Array object.
176
- @running, @ready = @ready, @running
177
-
178
- @running.each do |fiber|
179
- fiber.resume if fiber.alive?
180
- end
181
-
182
- @running.clear
171
+ @running.each do |fiber|
172
+ fiber.resume if fiber.alive?
183
173
  end
184
174
 
185
- if @ready.empty?
186
- interval = @timers.wait_interval
187
- else
188
- # if there are tasks ready to execute, don't sleep:
189
- interval = 0
175
+ @running.clear
176
+ end
177
+
178
+ if @ready.empty?
179
+ interval = @timers.wait_interval
180
+ else
181
+ # if there are tasks ready to execute, don't sleep:
182
+ interval = 0
183
+ end
184
+
185
+ # If there is no interval to wait (thus no timers), and no tasks, we could be done:
186
+ if interval.nil?
187
+ if self.finished?
188
+ # If there is nothing to do, then finish:
189
+ return false
190
190
  end
191
191
 
192
- # If there is no interval to wait (thus no timers), and no tasks, we could be done:
193
- if interval.nil?
194
- if self.finished?
195
- # If there is nothing to do, then finish:
196
- return initial_task
197
- end
198
- elsif interval < 0
199
- # We have timers ready to fire, don't sleep in the selctor:
200
- interval = 0
192
+ # Allow the user to specify a maximum interval if we would otherwise be sleeping indefinitely:
193
+ interval = timeout
194
+ elsif interval < 0
195
+ # We have timers ready to fire, don't sleep in the selctor:
196
+ interval = 0
197
+ elsif timeout and interval > timeout
198
+ interval = timeout
199
+ end
200
+
201
+ logger.debug(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
202
+ if monitors = @selector.select(interval)
203
+ monitors.each do |monitor|
204
+ monitor.value.resume
201
205
  end
202
-
203
- logger.debug(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
204
- if monitors = @selector.select(interval)
205
- monitors.each do |monitor|
206
- monitor.value.resume
207
- end
206
+ end
207
+
208
+ @timers.fire
209
+
210
+ # We check and clear the interrupted flag here:
211
+ if @interrupted
212
+ @guard.synchronize do
213
+ @interrupted = false
208
214
  end
209
215
 
210
- @timers.fire
216
+ self.stop
217
+
218
+ return false
219
+ end
220
+
221
+ return true
222
+ end
223
+
224
+ # Run the reactor until either all tasks complete or {#pause} or {#stop} is
225
+ # invoked. Proxies arguments to {#async} immediately before entering the
226
+ # loop, if a block is provided.
227
+ def run(*arguments, &block)
228
+ raise RuntimeError, 'Reactor has been closed' if @selector.nil?
229
+
230
+ initial_task = self.async(*arguments, &block) if block_given?
231
+
232
+ while self.run_once
233
+ # Round and round we go!
211
234
  end
212
235
 
213
236
  return initial_task
214
237
  ensure
215
238
  logger.debug(self) {"Exiting run-loop because #{$! ? $! : 'finished'}."}
216
-
217
- @stopped = true
218
239
  end
219
-
240
+
220
241
  # Stop each of the children tasks and close the selector.
221
242
  #
222
243
  # @return [void]
223
244
  def close
224
- @children&.each(&:stop)
245
+ self.stop
225
246
 
226
247
  # TODO Should we also clear all timers?
227
248
  @selector.close
@@ -267,12 +288,5 @@ module Async
267
288
  ensure
268
289
  timer.cancel if timer
269
290
  end
270
-
271
- # TODO remove
272
- def timeout(*args, &block)
273
- warn "#{self.class}\#timeout(...) is deprecated, use #{self.class}\#with_timeout(...) instead."
274
-
275
- with_timeout(*args, &block)
276
- end
277
291
  end
278
292
  end
@@ -21,10 +21,12 @@
21
21
  module Async
22
22
  # A semaphore is used to control access to a common resource in a concurrent system. A useful way to think of a semaphore as used in the real-world systems is as a record of how many units of a particular resource are available, coupled with operations to adjust that record safely (i.e. to avoid race conditions) as units are required or become free, and, if necessary, wait until a unit of the resource becomes available.
23
23
  class Semaphore
24
- def initialize(limit = 1)
24
+ def initialize(limit = 1, parent: nil)
25
25
  @count = 0
26
26
  @limit = limit
27
27
  @waiting = []
28
+
29
+ @parent = parent
28
30
  end
29
31
 
30
32
  # The current number of tasks that have acquired the semaphore.
@@ -47,14 +49,14 @@ module Async
47
49
  end
48
50
 
49
51
  # Run an async task. Will wait until the semaphore is ready until spawning and running the task.
50
- def async(*args, parent: Task.current, **options)
52
+ def async(*arguments, parent: (@parent or Task.current), **options)
51
53
  wait
52
54
 
53
55
  parent.async(**options) do |task|
54
56
  @count += 1
55
57
 
56
58
  begin
57
- yield task, *args
59
+ yield task, *arguments
58
60
  ensure
59
61
  self.release
60
62
  end
@@ -112,19 +112,20 @@ module Async
112
112
  attr :status
113
113
 
114
114
  # Begin the execution of the task.
115
- def run(*args)
115
+ def run(*arguments)
116
116
  if @status == :initialized
117
117
  @status = :running
118
- @fiber.resume(*args)
118
+
119
+ @fiber.resume(*arguments)
119
120
  else
120
121
  raise RuntimeError, "Task already running!"
121
122
  end
122
123
  end
123
124
 
124
- def async(*args, **options, &block)
125
+ def async(*arguments, **options, &block)
125
126
  task = Task.new(@reactor, self, **options, &block)
126
127
 
127
- task.run(*args)
128
+ task.run(*arguments)
128
129
 
129
130
  return task
130
131
  end
@@ -248,11 +249,11 @@ module Async
248
249
  end
249
250
 
250
251
  def make_fiber(&block)
251
- Fiber.new do |*args|
252
+ Fiber.new do |*arguments|
252
253
  set!
253
254
 
254
255
  begin
255
- @result = yield(self, *args)
256
+ @result = yield(self, *arguments)
256
257
  @status = :complete
257
258
  # logger.debug(self) {"Task was completed with #{@children.size} children!"}
258
259
  rescue Stop
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "1.23.0"
22
+ VERSION = "1.24.0"
23
23
  end
@@ -73,22 +73,22 @@ module Async
73
73
  self.class.new(@io.dup, @reactor)
74
74
  end
75
75
 
76
- def resume(*args)
76
+ def resume(*arguments)
77
77
  # It's possible that the monitor was closed before calling resume.
78
78
  return unless @monitor
79
79
 
80
80
  readiness = @monitor.readiness
81
81
 
82
82
  if @readable and (readiness == :r or readiness == :rw)
83
- @readable.resume(*args)
83
+ @readable.resume(*arguments)
84
84
  end
85
85
 
86
86
  if @writable and (readiness == :w or readiness == :rw)
87
- @writable.resume(*args)
87
+ @writable.resume(*arguments)
88
88
  end
89
89
 
90
90
  if @any
91
- @any.resume(*args)
91
+ @any.resume(*arguments)
92
92
  end
93
93
  end
94
94
 
@@ -22,7 +22,7 @@ require_relative "../async/reactor"
22
22
 
23
23
  module Kernel
24
24
  # Run the given block of code in a task, asynchronously, creating a reactor if necessary.
25
- def Async(*args, &block)
26
- ::Async::Reactor.run(*args, &block)
25
+ def Async(*arguments, &block)
26
+ ::Async::Reactor.run(*arguments, &block)
27
27
  end
28
28
  end
@@ -24,6 +24,8 @@ require 'async/rspec'
24
24
 
25
25
  require 'async/semaphore'
26
26
 
27
+ require_relative 'chainable_async_examples'
28
+
27
29
  RSpec.describe Async::Barrier do
28
30
  include_context Async::RSpec::Reactor
29
31
 
@@ -107,4 +109,6 @@ RSpec.describe Async::Barrier do
107
109
  subject.wait
108
110
  end
109
111
  end
112
+
113
+ it_behaves_like 'chainable async'
110
114
  end
@@ -0,0 +1,12 @@
1
+
2
+ RSpec.shared_examples 'chainable async' do
3
+ let(:parent) {double}
4
+ subject {described_class.new(parent: parent)}
5
+
6
+ it 'should chain async to parent' do
7
+ expect(parent).to receive(:async)
8
+
9
+ subject.async do
10
+ end
11
+ end
12
+ end
@@ -21,8 +21,10 @@
21
21
  require 'async'
22
22
  require 'async/queue'
23
23
  require 'async/rspec'
24
+ require 'async/semaphore'
24
25
 
25
26
  require_relative 'condition_examples'
27
+ require_relative 'chainable_async_examples'
26
28
 
27
29
  RSpec.shared_context Async::Queue do
28
30
  it 'should process items in order' do
@@ -48,6 +50,41 @@ RSpec.shared_context Async::Queue do
48
50
  expect(item).to be 1
49
51
  end
50
52
  end
53
+
54
+ context 'with semaphore' do
55
+ let(:capacity) {2}
56
+ let(:semaphore) {Async::Semaphore.new(capacity)}
57
+ let(:repeats) {capacity * 2}
58
+
59
+ it 'should process several items limited by a semaphore' do
60
+ count = 0
61
+
62
+ Async do
63
+ repeats.times do
64
+ subject.enqueue :item
65
+ end
66
+
67
+ subject.enqueue nil
68
+ end
69
+
70
+ subject.async(parent: semaphore) do |task|
71
+ count += 1
72
+ end
73
+
74
+ expect(count).to be == repeats
75
+ end
76
+ end
77
+
78
+ it_behaves_like 'chainable async' do
79
+ before do
80
+ subject.enqueue(:item)
81
+
82
+ # The limited queue may block.
83
+ Async do
84
+ subject.enqueue(nil)
85
+ end
86
+ end
87
+ end
51
88
  end
52
89
 
53
90
  RSpec.describe Async::Queue do
@@ -19,6 +19,8 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'async'
22
+ require 'async/rspec/reactor'
23
+
22
24
  require 'benchmark/ips'
23
25
 
24
26
  RSpec.describe Async::Reactor do
@@ -45,8 +47,39 @@ RSpec.describe Async::Reactor do
45
47
  end
46
48
  end
47
49
 
50
+ describe '#run_once' do
51
+ it "can run the reactor" do
52
+ # Run the reactor for 1 second:
53
+ task = subject.async do |task|
54
+ task.yield
55
+ end
56
+
57
+ expect(task).to be_running
58
+
59
+ # This will resume the task, and then the reactor will be finished.
60
+ expect(subject.run_once).to be false
61
+
62
+ expect(task).to be_finished
63
+ end
64
+
65
+ it "can run one iteration" do
66
+ state = nil
67
+
68
+ subject.async do |task|
69
+ state = :started
70
+ task.yield
71
+ state = :finished
72
+ end
73
+
74
+ expect(state).to be :started
75
+
76
+ subject.run_once
77
+ expect(state).to be :finished
78
+ end
79
+ end
80
+
48
81
  describe '#stop' do
49
- it "can be stop reactor" do
82
+ it "can stop the reactor" do
50
83
  state = nil
51
84
 
52
85
  subject.async do |task|
@@ -22,6 +22,8 @@ require 'async/semaphore'
22
22
  require 'async/barrier'
23
23
  require 'async/rspec'
24
24
 
25
+ require_relative 'chainable_async_examples'
26
+
25
27
  RSpec.describe Async::Semaphore do
26
28
  include_context Async::RSpec::Reactor
27
29
 
@@ -160,4 +162,6 @@ RSpec.describe Async::Semaphore do
160
162
  barrier.wait
161
163
  end
162
164
  end
165
+
166
+ it_behaves_like 'chainable async'
163
167
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.0
4
+ version: 1.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-15 00:00:00.000000000 Z
11
+ date: 2019-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r
@@ -132,7 +132,6 @@ extensions: []
132
132
  extra_rdoc_files: []
133
133
  files:
134
134
  - ".editorconfig"
135
- - ".github/FUNDING.yml"
136
135
  - ".github/workflows/tests.yml"
137
136
  - ".gitignore"
138
137
  - ".rspec"
@@ -181,6 +180,7 @@ files:
181
180
  - papers/1982 Grossman.pdf
182
181
  - papers/1987 ODell.pdf
183
182
  - spec/async/barrier_spec.rb
183
+ - spec/async/chainable_async_examples.rb
184
184
  - spec/async/clock_spec.rb
185
185
  - spec/async/condition_examples.rb
186
186
  - spec/async/condition_spec.rb
@@ -218,12 +218,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
218
  - !ruby/object:Gem::Version
219
219
  version: '0'
220
220
  requirements: []
221
- rubygems_version: 3.0.4
221
+ rubygems_version: 3.0.6
222
222
  signing_key:
223
223
  specification_version: 4
224
224
  summary: Async is an asynchronous I/O framework based on nio4r.
225
225
  test_files:
226
226
  - spec/async/barrier_spec.rb
227
+ - spec/async/chainable_async_examples.rb
227
228
  - spec/async/clock_spec.rb
228
229
  - spec/async/condition_examples.rb
229
230
  - spec/async/condition_spec.rb
@@ -1,4 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- # github: ioquatix
4
- custom: https://github.com/socketry/community/#funding