async 1.9.0 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|