Skip to content

Commit 2ffeb7d

Browse files
Simplify getting started guide.
1 parent ba24abe commit 2ffeb7d

File tree

2 files changed

+46
-388
lines changed

2 files changed

+46
-388
lines changed

context/getting-started.md

Lines changed: 22 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -72,68 +72,40 @@ end
7272

7373
This runs a maximum of 2 tasks concurrently. Total duration is 2 seconds (tasks 0,1 run first, then tasks 2,3).
7474

75-
### Queue-Based Resource Management
75+
### Timeouts
7676

77-
Use a pre-populated queue of specific resources:
77+
You can control how long to wait when acquiring resources using the `timeout` parameter. This is particularly useful when working with limited capacity limiters that might block indefinitely.
7878

7979
```ruby
8080
require "async"
8181
require "async/limiter"
82-
require "async/queue"
8382

8483
Async do
85-
# Pre-populate queue with database connections
86-
queue = Async::Queue.new
87-
queue.push("connection_1")
88-
queue.push("connection_2")
89-
queue.push("connection_3")
84+
# Zero limit will always block:
85+
limiter = Async::Limiter::Limited.new(0)
9086

91-
limiter = Async::Limiter::Queued.new(queue)
92-
93-
5.times do |i|
94-
limiter.async do |task|
95-
# Automatically gets an available connection
96-
limiter.acquire do |connection|
97-
puts "Task #{i} using #{connection}"
98-
task.sleep 1
99-
# Connection automatically returned to queue
100-
end
101-
end
102-
end
87+
limiter.acquire(timeout: 3)
88+
# => nil
89+
90+
limiter.acquire(timeout: 3) do
91+
puts "Acquired."
92+
end or puts "Timed out!"
10393
end
10494
```
10595

106-
## Advanced Timeout Features
107-
108-
### Unified Timeouts
109-
110-
All acquisition methods support flexible timeout handling:
111-
112-
```ruby
113-
limiter = Async::Limiter::Limited.new(1)
114-
115-
# Blocking (wait forever)
116-
resource = limiter.acquire
117-
118-
# Non-blocking (immediate)
119-
resource = limiter.acquire(timeout: 0)
120-
return "busy" unless resource
121-
122-
# Timed (wait up to 2.5 seconds)
123-
resource = limiter.acquire(timeout: 2.5)
124-
return "timeout" unless resource
96+
**Key timeout behaviors:**
12597

126-
# With blocks (automatic cleanup)
127-
limiter.acquire(timeout: 1.0) do |resource|
128-
# Use resource
129-
end # Automatically released
130-
```
98+
- `timeout: nil` (default) - Wait indefinitely until a resource becomes available
99+
- `timeout: 0` - Non-blocking operation; return immediately if no resource is available
100+
- `timeout: N` (where N > 0) - Wait up to N seconds for a resource to become available
131101

132-
## Rate Limiting with Timing Strategies
102+
**Return values:**
103+
- Returns `true` (or the acquired resource) when successful
104+
- Returns `nil` when the timeout is exceeded or no resource is available
133105

134-
### Sliding Window Rate Limiting
106+
## Rate Limiting
135107

136-
Continuous rolling time windows:
108+
Timing strategies can be used to implement rate limiting, for example a continuous rolling time windows:
137109

138110
```ruby
139111
require "async"
@@ -142,9 +114,9 @@ require "async/limiter"
142114
Async do
143115
# Max 3 tasks within any 1-second sliding window
144116
timing = Async::Limiter::Timing::SlidingWindow.new(
145-
1.0, # 1-second window
146-
Async::Limiter::Timing::BurstStrategy::Greedy, # Allow bursting
147-
3 # 3 tasks per window
117+
1.0, # 1-second window.
118+
Async::Limiter::Timing::BurstStrategy::Greedy, # Allow bursting
119+
3 # 3 tasks per window
148120
)
149121

150122
limiter = Async::Limiter::Limited.new(10, timing: timing)
@@ -157,139 +129,3 @@ Async do
157129
end
158130
end
159131
```
160-
161-
### Fixed Window Rate Limiting
162-
163-
Discrete time boundaries:
164-
165-
```ruby
166-
# Max 5 tasks per 2-second window with fixed boundaries
167-
timing = Async::Limiter::Timing::FixedWindow.new(
168-
2.0, # 2-second windows
169-
Async::Limiter::Timing::BurstStrategy::Greedy, # Allow bursting
170-
5 # 5 tasks per window
171-
)
172-
173-
limiter = Async::Limiter::Limited.new(10, timing: timing)
174-
```
175-
176-
### Leaky Bucket Rate Limiting
177-
178-
Smooth rate limiting with token consumption:
179-
180-
```ruby
181-
# 10 tokens per second, bucket capacity of 50 tokens
182-
timing = Async::Limiter::Timing::LeakyBucket.new(
183-
10.0, # 10 tokens/second leak rate
184-
50.0 # 50 token capacity
185-
)
186-
187-
limiter = Async::Limiter::Limited.new(100, timing: timing)
188-
189-
# Bucket starts empty, fills with usage, leaks over time
190-
```
191-
192-
## Cost-Based Acquisition
193-
194-
Operations can consume multiple "units" based on their computational weight:
195-
196-
```ruby
197-
# Create a leaky bucket with 10 tokens capacity
198-
timing = Async::Limiter::Timing::LeakyBucket.new(5.0, 10.0) # 5/sec rate, 10 capacity
199-
limiter = Async::Limiter::Limited.new(100, timing: timing)
200-
201-
# Light operations
202-
limiter.acquire(cost: 0.5) do
203-
perform_light_operation()
204-
end
205-
206-
# Normal operations
207-
limiter.acquire(cost: 1.0) do # Default cost
208-
perform_standard_operation()
209-
end
210-
211-
# Heavy operations
212-
limiter.acquire(cost: 3.5) do
213-
perform_heavy_operation()
214-
end
215-
216-
# Impossible operations fail fast
217-
begin
218-
limiter.acquire(cost: 15.0) # Exceeds capacity!
219-
rescue ArgumentError => e
220-
puts "#{e.message}" # Cost 15.0 exceeds maximum supported cost 10.0
221-
end
222-
```
223-
224-
### Cost + Timeout Combinations
225-
226-
```ruby
227-
# Heavy operation with timeout
228-
result = limiter.acquire(timeout: 30.0, cost: 5.0) do |resource|
229-
expensive_computation()
230-
end
231-
232-
if result
233-
puts "Completed successfully"
234-
else
235-
puts "Timed out waiting for capacity"
236-
end
237-
```
238-
239-
## Manual Resource Management
240-
241-
You can manually acquire and release resources:
242-
243-
```ruby
244-
limiter = Async::Limiter::Limited.new(1)
245-
246-
# Acquire with automatic release
247-
limiter.acquire do |resource|
248-
puts "I have the resource"
249-
# Automatically released when block exits
250-
end
251-
252-
# Manual acquire/release
253-
resource = limiter.acquire
254-
begin
255-
puts "I have the resource"
256-
ensure
257-
limiter.release(resource)
258-
end
259-
260-
# Non-blocking acquisition
261-
resource = limiter.acquire(timeout: 0)
262-
if resource
263-
begin
264-
puts "Got the resource immediately"
265-
ensure
266-
limiter.release(resource)
267-
end
268-
else
269-
puts "Resource not available"
270-
end
271-
```
272-
273-
## Choosing the Right Limiter
274-
275-
### Use {Async::Limiter::Generic} when:
276-
- You want unlimited concurrency
277-
- You need timing constraints without concurrency limits
278-
- You're building a base class for custom limiters
279-
280-
### Use {Async::Limiter::Limited} when:
281-
- You need to limit concurrent execution
282-
- You want traditional semaphore behavior
283-
- You need timing + concurrency coordination
284-
285-
### Use {Async::Limiter::Queued} when:
286-
- You have a pre-existing set of resources to distribute (DB connections, API keys, etc.).
287-
- You need priority-based resource allocation.
288-
- You want queue-based resource distribution with timeout support.
289-
290-
### Timing Strategy Selection
291-
292-
- **None**: Pure concurrency control without rate limiting.
293-
- **SlidingWindow**: Smooth, continuous rate limiting.
294-
- **FixedWindow**: Discrete time periods with burst tolerance.
295-
- **LeakyBucket**: Token-based rate limiting with natural decay.

0 commit comments

Comments
 (0)