Skip to content

Commit 0461786

Browse files
Add Async::Promise.
1 parent 32a1fb3 commit 0461786

File tree

12 files changed

+1051
-37
lines changed

12 files changed

+1051
-37
lines changed

benchmark/condition.rb

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Released under the MIT License.
5+
# Copyright, 2025, by Samuel Williams.
6+
7+
require "sus/fixtures/benchmark"
8+
require "async/condition"
9+
require "async"
10+
11+
describe Async::Condition do
12+
include Sus::Fixtures::Benchmark
13+
14+
let(:condition) {Async::Condition.new}
15+
16+
with "basic signal and wait operations" do
17+
measure "signal without waiters" do |repeats|
18+
test_condition = Async::Condition.new
19+
20+
repeats.times do
21+
test_condition.signal("value")
22+
end
23+
end
24+
25+
measure "simple signal and wait pairs" do |repeats|
26+
Async do |task|
27+
signal_counter = 0
28+
repeats.times do
29+
waiter = task.async do
30+
condition.wait
31+
end
32+
33+
# Signal the waiting fiber
34+
condition.signal("signal-#{signal_counter}")
35+
36+
waiter.wait
37+
signal_counter += 1
38+
end
39+
end
40+
end
41+
42+
measure "immediate signal then wait" do |repeats|
43+
repeats.times do
44+
test_condition = Async::Condition.new
45+
46+
Async do |task|
47+
# Signal first, then wait
48+
test_condition.signal("immediate")
49+
50+
# This should return immediately since already signaled
51+
waiter = task.async do
52+
test_condition.wait
53+
end
54+
55+
waiter.wait
56+
end
57+
end
58+
end
59+
end
60+
61+
with "multiple waiters scenarios" do
62+
measure "single signal to multiple waiters" do |repeats|
63+
repeats.times do
64+
Async do |task|
65+
# Create multiple waiters
66+
waiters = 5.times.map do |i|
67+
task.async do
68+
condition.wait
69+
end
70+
end
71+
72+
# Single signal should wake all waiters
73+
condition.signal("broadcast")
74+
75+
# Wait for all waiters to complete
76+
waiters.each(&:wait)
77+
end
78+
end
79+
end
80+
81+
measure "multiple signals to multiple waiters" do |repeats|
82+
repeats.times do
83+
Async do |task|
84+
# Create waiters
85+
waiters = 5.times.map do |i|
86+
task.async do
87+
condition.wait
88+
end
89+
end
90+
91+
# Multiple signals
92+
5.times do |idx|
93+
condition.signal("signal-#{idx}")
94+
end
95+
96+
waiters.each(&:wait)
97+
end
98+
end
99+
end
100+
101+
measure "sequential waiter creation and signaling" do |repeats|
102+
Async do |task|
103+
value_counter = 0
104+
repeats.times do
105+
# Create waiter
106+
waiter = task.async do
107+
condition.wait
108+
end
109+
110+
# Create signaler
111+
signaler = task.async do
112+
sleep(0.001) # Brief delay
113+
condition.signal("value-#{value_counter}")
114+
end
115+
116+
[waiter, signaler].each(&:wait)
117+
value_counter += 1
118+
end
119+
end
120+
end
121+
end
122+
123+
with "condition state management" do
124+
measure "empty and waiting state checks" do |repeats|
125+
repeats.times do
126+
test_condition = Async::Condition.new
127+
128+
Async do |task|
129+
# Check empty state
130+
100.times {test_condition.empty?}
131+
132+
# Create a waiter
133+
waiter = task.async do
134+
test_condition.wait
135+
end
136+
137+
# Check waiting state
138+
100.times {test_condition.waiting?}
139+
140+
# Signal to complete
141+
test_condition.signal("done")
142+
waiter.wait
143+
end
144+
end
145+
end
146+
147+
measure "rapid signal/wait cycling" do |repeats|
148+
Async do |task|
149+
repeats.times do
150+
waiter = task.async do
151+
condition.wait
152+
end
153+
154+
condition.signal("cycle")
155+
waiter.wait
156+
end
157+
end
158+
end
159+
end
160+
161+
with "high concurrency scenarios" do
162+
measure "many concurrent waiters", minimum: 3 do |repeats|
163+
repeats.times do
164+
Async do |task|
165+
# Create many waiters
166+
waiters = 20.times.map do
167+
task.async do
168+
condition.wait
169+
end
170+
end
171+
172+
# Signal to wake them all
173+
condition.signal("mass-signal")
174+
175+
# Wait for all to complete
176+
waiters.each(&:wait)
177+
end
178+
end
179+
end
180+
181+
measure "producer-consumer with condition coordination" do |repeats|
182+
repeats.times do
183+
Async do |task|
184+
ready_condition = Async::Condition.new
185+
done_condition = Async::Condition.new
186+
187+
# Producer
188+
producer = task.async do
189+
ready_condition.wait # Wait for consumer to be ready
190+
50.times {|i| "item-#{i}"} # Simulate work
191+
done_condition.signal("finished")
192+
end
193+
194+
# Consumer
195+
consumer = task.async do
196+
ready_condition.signal("ready") # Signal producer
197+
done_condition.wait # Wait for completion
198+
end
199+
200+
[producer, consumer].each(&:wait)
201+
end
202+
end
203+
end
204+
end
205+
206+
with "memory and cleanup patterns" do
207+
measure "condition lifecycle" do |repeats|
208+
repeats.times do
209+
# Create condition, use it, let it be collected
210+
test_condition = Async::Condition.new
211+
212+
Async do |task|
213+
waiter = task.async {test_condition.wait}
214+
215+
test_condition.signal("cleanup")
216+
waiter.wait
217+
end
218+
219+
# Condition should be eligible for GC now
220+
end
221+
end
222+
223+
measure "signal with different value types" do |repeats|
224+
Async do |task|
225+
repeats.times do
226+
waiter = task.async do
227+
condition.wait
228+
end
229+
230+
# Signal with different value types to test overhead
231+
case value_counter % 4
232+
when 0
233+
condition.signal(nil)
234+
when 1
235+
condition.signal(value_counter)
236+
when 2
237+
condition.signal("string-#{value_counter}")
238+
when 3
239+
condition.signal({key: value_counter})
240+
end
241+
242+
value_counter += 1
243+
244+
waiter.wait
245+
end
246+
end
247+
end
248+
end
249+
end

benchmark/gems.locked

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
PATH
2+
remote: ..
3+
specs:
4+
async (2.28.1)
5+
console (~> 1.29)
6+
fiber-annotation
7+
io-event (~> 1.11)
8+
metrics (~> 0.12)
9+
traces (~> 0.18)
10+
11+
GEM
12+
remote: https://rubygems.org/
13+
specs:
14+
console (1.33.0)
15+
fiber-annotation
16+
fiber-local (~> 1.1)
17+
json
18+
fiber-annotation (0.2.0)
19+
fiber-local (1.1.0)
20+
fiber-storage
21+
fiber-storage (1.0.1)
22+
io-event (1.13.0)
23+
json (2.13.2)
24+
metrics (0.14.0)
25+
sus (0.34.0)
26+
sus-fixtures-benchmark (0.2.1)
27+
sus (~> 0.31)
28+
traces (0.18.1)
29+
30+
PLATFORMS
31+
arm64-darwin-24
32+
ruby
33+
34+
DEPENDENCIES
35+
async!
36+
sus
37+
sus-fixtures-benchmark
38+
39+
BUNDLED WITH
40+
2.7.0

benchmark/gems.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
# gem "async"
6+
gem "async", path: "../"
7+
8+
gem "sus"
9+
gem "sus-fixtures-benchmark"

benchmark/queue.rb

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
require "async/queue"
99
require "async"
1010

11-
describe "Async::Queue Performance" do
11+
describe Async::Queue do
1212
include Sus::Fixtures::Benchmark
1313

1414
let(:queue) {Async::Queue.new}
@@ -139,21 +139,22 @@
139139
end
140140
end
141141

142-
with "queue size operations" do
143-
measure "size checks with concurrent modifications" do |repeats|
142+
with "queue state operations" do
143+
measure "size and empty checks" do |repeats|
144144
repeats.times do
145145
Async do |task|
146146
# Pre-populate with some items
147147
100.times {|i| queue.push("item-#{i}")}
148148

149-
# Reader checking size
149+
# Check state frequently
150150
size_checker = task.async do
151151
100.times do
152152
queue.size
153+
queue.empty?
153154
end
154155
end
155156

156-
# Writer modifying queue
157+
# Concurrent operations
157158
modifier = task.async do
158159
50.times do |i|
159160
queue.push("new-item-#{i}")
@@ -168,6 +169,26 @@
168169
end
169170
end
170171
end
172+
173+
measure "close and reopen patterns" do |repeats|
174+
repeats.times do
175+
# Create fresh queue each time
176+
test_queue = Async::Queue.new
177+
178+
Async do
179+
# Fill queue
180+
50.times {|i| test_queue.push("item-#{i}")}
181+
182+
# Close it
183+
test_queue.close
184+
185+
# Try to dequeue remaining items
186+
while item = test_queue.dequeue
187+
# Process remaining items
188+
end
189+
end
190+
end
191+
end
171192
end
172193

173194
with "memory efficiency" do
@@ -189,4 +210,4 @@
189210
end
190211
end
191212
end
192-
end
213+
end

0 commit comments

Comments
 (0)