async 1.22.2 → 1.23.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: 61d9644a199a84c93e9b9212c31e210343aebd41af6fd62647d600c72658248d
4
- data.tar.gz: a275b10657182f96c8ce5578321774b4def1c50374cf0f602387e1a2312e8c36
3
+ metadata.gz: 1dc28a8cd53cda6b82492cf8a1ed3c2a7786f4f6a29da3935be00298e25eaaea
4
+ data.tar.gz: c70dfa854738d6eccd197f322312ed1ea810378428b08fba18ca24076a5d98d4
5
5
  SHA512:
6
- metadata.gz: 4c6d818ec258c64b8d1d2f46bb90360024d114829110a8f4bce27ec740b6d33781b8b5a11a622e28a680a212a91f5550b94ecd34ee3e8e0624937bb290757d8a
7
- data.tar.gz: c530521f3102302be5127d3b46a50162682a07beb632c21a281776d33a1b36a15ffc40042d05584c6d36fd8be2732f3ab364bf4149f2a27cc623a541d40c6580
6
+ metadata.gz: d305bdc4e707ba26b602809669ba8972017be01b7a599e98c471bf969e434a05b5ef725b3853793f7a82a2e7dc13c89231e383d6b06ffbadb966d77eaa36015a
7
+ data.tar.gz: 58673e31ac2e10e82062fd2def69e040c7780bf6179eb5092bed365ef3564a61d1553a63b45a151b53b64fe43cea4d7139fe0e844b7755083b13436c0008fd42
data/README.md CHANGED
@@ -307,6 +307,23 @@ Due to limitations within Ruby and the nature of this library, it is not possibl
307
307
 
308
308
  Blocking Ruby methods such as `pop` in the `Queue` class require access to their own threads and will not yield control back to the reactor which can result in a deadlock. As a substitute for the standard library `Queue`, the `Async::Queue` class can be used.
309
309
 
310
+ ## Conventions
311
+
312
+ ### Nesting Tasks
313
+
314
+ `Async::Barrier` and `Async::Semaphore` are designed to be compatible with each other, and with other tasks that nest `#async` invocations. There are other similar situations where you may want to pass in a parent task, e.g. `Async::IO::Endpoint#bind`.
315
+
316
+ ```ruby
317
+ barrier = Async::Barrier.new
318
+ semaphore = Async::Semaphore.new(2)
319
+
320
+ semaphore.async(parent: barrier) do
321
+ # ...
322
+ end
323
+ ```
324
+
325
+ A `parent:` in this context is anything that responds to `#async` in the same way that `Async::Task` responds to `#async`. In situations where you strictly depend on the interface of `Async::Task`, use the `task: Task.current` pattern.
326
+
310
327
  ## Contributing
311
328
 
312
329
  1. Fork it
@@ -46,8 +46,9 @@ module Async
46
46
  @tasks.empty?
47
47
  end
48
48
 
49
+ # Wait for tasks in FIFO order.
49
50
  def wait
50
- while task = @tasks.pop
51
+ while task = @tasks.shift
51
52
  task.wait
52
53
  end
53
54
  end
@@ -71,6 +71,10 @@ module Async
71
71
  "\#<#{description}>"
72
72
  end
73
73
 
74
+ def inspect
75
+ to_s
76
+ end
77
+
74
78
  # Change the parent of this node.
75
79
  # @param parent [Node, nil] the parent to attach to, or nil to detach.
76
80
  # @return [self]
@@ -88,7 +88,7 @@ module Async
88
88
  end
89
89
 
90
90
  def to_s
91
- "<#{self.description} stopped=#{@stopped}>"
91
+ "\#<#{self.description} (#{@stopped ? 'stopped' : 'running'})>"
92
92
  end
93
93
 
94
94
  # @attr stopped [Boolean]
@@ -131,7 +131,7 @@ module Async
131
131
 
132
132
  return monitor
133
133
  end
134
-
134
+
135
135
  # Stop the reactor at the earliest convenience. Can be called from a different thread safely.
136
136
  # @return [void]
137
137
  def stop
@@ -169,7 +169,7 @@ module Async
169
169
  initial_task = self.async(*args, &block) if block_given?
170
170
 
171
171
  until @stopped
172
- # logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
172
+ logger.debug(self) {"@ready = #{@ready} @running = #{@running}"}
173
173
 
174
174
  if @ready.any?
175
175
  # running used to correctly answer on `finished?`, and to reuse Array object.
@@ -200,7 +200,7 @@ module Async
200
200
  interval = 0
201
201
  end
202
202
 
203
- # logger.debug(self) {"Selecting with #{@children&.size} children with interval = #{interval.inspect}..."}
203
+ logger.debug(self) {"Selecting with #{@children&.size} children with interval = #{interval ? interval.round(2) : 'infinite'}..."}
204
204
  if monitors = @selector.select(interval)
205
205
  monitors.each do |monitor|
206
206
  monitor.value.resume
@@ -79,14 +79,14 @@ module Async
79
79
  end
80
80
  end
81
81
 
82
- # Release the semaphore. Must match up with a corresponding call to `acquire`.
82
+ # Release the semaphore. Must match up with a corresponding call to `acquire`. Will release waiting fibers in FIFO order.
83
83
  def release
84
84
  @count -= 1
85
85
 
86
- available = @waiting.pop(@limit - @count)
87
-
88
- available.each do |fiber|
89
- fiber.resume if fiber.alive?
86
+ while (@limit - @count) > 0 and fiber = @waiting.shift
87
+ if fiber.alive?
88
+ fiber.resume
89
+ end
90
90
  end
91
91
  end
92
92
 
@@ -85,7 +85,7 @@ module Async
85
85
  end
86
86
 
87
87
  def to_s
88
- "<#{self.description} #{@status}>"
88
+ "\#<#{self.description} (#{@status})>"
89
89
  end
90
90
 
91
91
  def logger
@@ -149,7 +149,7 @@ module Async
149
149
 
150
150
  # Stop the task and all of its children.
151
151
  # @return [void]
152
- def stop
152
+ def stop(later = false)
153
153
  if self.stopped?
154
154
  # If we already stopped this task... don't try to stop it again:
155
155
  return
@@ -157,7 +157,11 @@ module Async
157
157
 
158
158
  if self.running?
159
159
  if self.current?
160
- raise Stop, "Stopping current fiber!"
160
+ if later
161
+ @reactor << Stop::Later.new(self)
162
+ else
163
+ raise Stop, "Stopping current task!"
164
+ end
161
165
  elsif @fiber&.alive?
162
166
  begin
163
167
  @fiber.resume(Stop.new)
@@ -235,9 +239,12 @@ module Async
235
239
  end
236
240
 
237
241
  def stop!
238
- # logger.debug(self) {"Task was stopped with #{@children.size} children!"}
242
+ # logger.debug(self) {"Task was stopped with #{@children&.size.inspect} children!"}
239
243
  @status = :stopped
240
- @children&.each(&:stop)
244
+
245
+ @children&.each do |child|
246
+ child.stop(true)
247
+ end
241
248
  end
242
249
 
243
250
  def make_fiber(&block)
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Async
22
- VERSION = "1.22.2"
22
+ VERSION = "1.23.0"
23
23
  end
@@ -75,6 +75,20 @@ RSpec.describe Async::Barrier do
75
75
 
76
76
  expect(subject).to be_empty
77
77
  end
78
+
79
+ it 'waits for tasks in order' do
80
+ order = []
81
+
82
+ 5.times do |i|
83
+ subject.async do
84
+ order << i
85
+ end
86
+ end
87
+
88
+ subject.wait
89
+
90
+ expect(order).to be == [0, 1, 2, 3, 4]
91
+ end
78
92
  end
79
93
 
80
94
  context 'with semaphore' do
@@ -51,6 +51,23 @@ RSpec.shared_examples Async::Condition do
51
51
  subject.signal
52
52
  end
53
53
 
54
+ it 'resumes tasks in order' do
55
+ order = []
56
+
57
+ 5.times do |i|
58
+ task = reactor.async do
59
+ subject.wait
60
+ order << i
61
+ end
62
+ end
63
+
64
+ subject.signal
65
+
66
+ reactor.yield
67
+
68
+ expect(order).to be == [0, 1, 2, 3, 4]
69
+ end
70
+
54
71
  context "with timeout" do
55
72
  before do
56
73
  @state = nil
@@ -18,6 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require 'async/rspec'
21
22
  require 'async/notification'
22
23
 
23
24
  require_relative 'condition_examples'
@@ -181,6 +181,19 @@ RSpec.describe Async::Task do
181
181
  expect(task).to be_stopped
182
182
  end
183
183
 
184
+ it "can stop current task using exception" do
185
+ state = nil
186
+
187
+ task = reactor.async do |task|
188
+ state = :started
189
+ raise Async::Stop, "I'm finished."
190
+ state = :finished
191
+ end
192
+
193
+ expect(state).to be == :started
194
+ expect(task).to be_stopped
195
+ end
196
+
184
197
  it "should stop direct child" do
185
198
  parent_task = child_task = nil
186
199
 
@@ -205,6 +218,35 @@ RSpec.describe Async::Task do
205
218
  expect(child_task).to_not be_alive
206
219
  end
207
220
 
221
+ it "can stop nested parent" do
222
+ parent_task = nil
223
+ children_tasks = []
224
+
225
+ reactor.async do |task|
226
+ parent_task = task
227
+
228
+ reactor.async do |task|
229
+ children_tasks << task
230
+ task.sleep(2)
231
+ end
232
+
233
+ reactor.async do |task|
234
+ children_tasks << task
235
+ task.sleep(1)
236
+ parent_task.stop
237
+ end
238
+
239
+ reactor.async do |task|
240
+ children_tasks << task
241
+ task.sleep(2)
242
+ end
243
+ end
244
+
245
+ reactor.run
246
+
247
+ expect(parent_task).to_not be_alive
248
+ end
249
+
208
250
  it "should not remove running task" do
209
251
  top_task = middle_task = bottom_task = nil
210
252
 
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.22.2
4
+ version: 1.23.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-10 00:00:00.000000000 Z
11
+ date: 2019-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nio4r