async 1.9.0 → 1.9.1
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 +4 -4
- data/lib/async.rb +7 -0
- data/lib/async/semaphore.rb +33 -27
- data/lib/async/version.rb +1 -1
- data/spec/async/semaphore_spec.rb +98 -11
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82346eddb843773e16f9c2da955116a1418213921a51790050ea72b316a85098
|
4
|
+
data.tar.gz: 6b848d0c50780fc9a1013708aeff0de49858423f79d23039d6b795f6548705c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4573fa61270cad3f20b436aa322830eb1a08c77533955b58ae7e422b1788aea34db1b829a6c9a4e23faef1cc7a0d8ea40f4a2915e9102dda4ad319d91c3ed3aa
|
7
|
+
data.tar.gz: 516c2056b418feea8e39cec3513a8fc2710be916f5b1d5450a3110e9ae8207dcdb7f7a1a36688a98d081086abcb8eddde6d1766df1b01e98abb0045b44eb964a
|
data/lib/async.rb
CHANGED
data/lib/async/semaphore.rb
CHANGED
@@ -33,17 +33,39 @@ module Async
|
|
33
33
|
# The maximum number of tasks that can acquire the semaphore.
|
34
34
|
attr :limit
|
35
35
|
|
36
|
+
# Is the semaphore currently acquired?
|
37
|
+
def empty?
|
38
|
+
@count.zero?
|
39
|
+
end
|
40
|
+
|
36
41
|
# Whether trying to acquire this semaphore would block.
|
37
42
|
def blocking?
|
38
43
|
@count >= @limit
|
39
44
|
end
|
40
45
|
|
46
|
+
# Run an async task. Will wait until the semaphore is ready until spawning and running the task.
|
47
|
+
def async(*args)
|
48
|
+
parent = Task.current
|
49
|
+
|
50
|
+
wait
|
51
|
+
|
52
|
+
parent.async do |task|
|
53
|
+
@count += 1
|
54
|
+
|
55
|
+
begin
|
56
|
+
yield task, *args
|
57
|
+
ensure
|
58
|
+
self.release
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
41
63
|
# Acquire the semaphore, block if we are at the limit.
|
42
64
|
# If no block is provided, you must call release manually.
|
43
65
|
# @yield when the semaphore can be acquired
|
44
66
|
# @return the result of the block if invoked
|
45
67
|
def acquire
|
46
|
-
|
68
|
+
wait
|
47
69
|
|
48
70
|
@count += 1
|
49
71
|
|
@@ -60,37 +82,21 @@ module Async
|
|
60
82
|
def release
|
61
83
|
@count -= 1
|
62
84
|
|
63
|
-
self.signal
|
64
|
-
end
|
65
|
-
|
66
|
-
# Is anyone waiting?
|
67
|
-
def empty?
|
68
|
-
@waiting.empty?
|
69
|
-
end
|
70
|
-
|
71
|
-
# Wait on this semaphore.
|
72
|
-
def wait
|
73
|
-
@waiting << Fiber.current
|
74
|
-
Task.yield
|
75
|
-
end
|
76
|
-
|
77
|
-
# Resume any waiting tasks.
|
78
|
-
def signal(task: Task.current)
|
79
|
-
task.reactor << self if @waiting.any?
|
80
|
-
end
|
81
|
-
|
82
|
-
# Whether this semaphore has work to do when being resumed.
|
83
|
-
def alive?
|
84
|
-
@waiting.any?
|
85
|
-
end
|
86
|
-
|
87
|
-
# Resume tasks waiting on the semaphore, up to the maximum to the available limit.
|
88
|
-
def resume
|
89
85
|
available = @waiting.pop(@limit - @count)
|
90
86
|
|
91
87
|
available.each do |fiber|
|
92
88
|
fiber.resume if fiber.alive?
|
93
89
|
end
|
94
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Wait until the semaphore becomes available.
|
95
|
+
def wait
|
96
|
+
while blocking?
|
97
|
+
@waiting << Fiber.current
|
98
|
+
Task.yield
|
99
|
+
end
|
100
|
+
end
|
95
101
|
end
|
96
102
|
end
|
data/lib/async/version.rb
CHANGED
@@ -20,26 +20,113 @@
|
|
20
20
|
|
21
21
|
require 'async/semaphore'
|
22
22
|
|
23
|
+
require_relative 'condition_examples'
|
24
|
+
|
23
25
|
RSpec.describe Async::Semaphore do
|
24
26
|
include_context Async::RSpec::Reactor
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
context '#async' do
|
29
|
+
let(:repeats) {40}
|
30
|
+
let(:limit) {4}
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
it 'should process work in batches' do
|
33
|
+
semaphore = Async::Semaphore.new(limit)
|
34
|
+
current, maximum = 0, 0
|
35
|
+
|
36
|
+
result = repeats.times.map do |i|
|
37
|
+
semaphore.async do |task|
|
33
38
|
current += 1
|
34
39
|
maximum = [current, maximum].max
|
35
|
-
task.sleep(0.
|
40
|
+
task.sleep(rand * 0.1)
|
36
41
|
current -= 1
|
42
|
+
|
43
|
+
i
|
37
44
|
end
|
38
|
-
end
|
39
|
-
|
45
|
+
end.collect(&:result)
|
46
|
+
|
47
|
+
# Verify that the maximum number of concurrent tasks was the specificed limit:
|
48
|
+
expect(maximum).to be == limit
|
49
|
+
|
50
|
+
# Verify that the results were in the correct order:
|
51
|
+
expect(result).to be == (0...repeats).to_a
|
52
|
+
end
|
40
53
|
|
41
|
-
|
54
|
+
it 'only allows one task at a time' do
|
55
|
+
semaphore = Async::Semaphore.new(1)
|
56
|
+
order = []
|
57
|
+
|
58
|
+
3.times.map do |i|
|
59
|
+
semaphore.async do |task|
|
60
|
+
order << i
|
61
|
+
task.sleep(0.1)
|
62
|
+
order << i
|
63
|
+
end
|
64
|
+
end.collect(&:result)
|
65
|
+
|
66
|
+
expect(order).to be == [0, 0, 1, 1, 2, 2]
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'allows tasks to execute concurrently' do
|
70
|
+
semaphore = Async::Semaphore.new(3)
|
71
|
+
order = []
|
72
|
+
|
73
|
+
3.times.map do |i|
|
74
|
+
semaphore.async do |task|
|
75
|
+
order << i
|
76
|
+
task.sleep(0.1)
|
77
|
+
order << i
|
78
|
+
end
|
79
|
+
end.collect(&:result)
|
80
|
+
|
81
|
+
expect(order).to be == [0, 1, 2, 0, 1, 2]
|
82
|
+
end
|
42
83
|
end
|
43
84
|
|
44
|
-
|
85
|
+
context '#count' do
|
86
|
+
it 'should count number of current acquisitions' do
|
87
|
+
expect(subject.count).to be == 0
|
88
|
+
|
89
|
+
subject.acquire do
|
90
|
+
expect(subject.count).to be == 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context '#limit' do
|
96
|
+
it 'should have a default limit' do
|
97
|
+
expect(subject.limit).to be == 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context '#empty?' do
|
102
|
+
it 'should be empty unless acquired' do
|
103
|
+
expect(subject).to be_empty
|
104
|
+
|
105
|
+
subject.acquire do
|
106
|
+
expect(subject).to_not be_empty
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context '#blocking?' do
|
112
|
+
it 'will be blocking when acquired' do
|
113
|
+
expect(subject).to_not be_blocking
|
114
|
+
|
115
|
+
subject.acquire do
|
116
|
+
expect(subject).to be_blocking
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context '#acquire/#release' do
|
122
|
+
it 'works when called without block' do
|
123
|
+
subject.acquire
|
124
|
+
|
125
|
+
expect(subject.count).to be == 1
|
126
|
+
|
127
|
+
subject.release
|
128
|
+
|
129
|
+
expect(subject.count).to be == 0
|
130
|
+
end
|
131
|
+
end
|
45
132
|
end
|