async 1.22.2 → 1.23.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
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