async-limiter 2.0.0 → 2.2.0
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
- checksums.yaml.gz.sig +0 -0
- data/context/generic-limiter.md +2 -2
- data/context/getting-started.md +3 -3
- data/context/limited-limiter.md +6 -6
- data/context/queued-limiter.md +4 -4
- data/context/timing-strategies.md +3 -3
- data/lib/async/limiter/generic.rb +22 -3
- data/lib/async/limiter/limited.rb +62 -6
- data/lib/async/limiter/queued.rb +58 -6
- data/lib/async/limiter/timing/fixed_window.rb +1 -2
- data/lib/async/limiter/version.rb +1 -1
- data/license.md +3 -3
- data/readme.md +45 -0
- data/releases.md +10 -0
- data.tar.gz.sig +0 -0
- metadata +18 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d153319af347142e9837f3a8e7523f47f2bbd28d56e497d03c0fd7279302af8f
|
|
4
|
+
data.tar.gz: 9d1da2a75240ba77a68d26141abf50b677d5248b9bf191a50e8821aac3bceee7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a35d89989f3d8faf3cd203a84b2e04125809ae07d6609ce0dc205214f97bc1bf11ab099bbc46542a3fa701674185e641461d548f67bb1d1325b8badce9b6c6cc
|
|
7
|
+
data.tar.gz: df563bd337ae1e9157df61f0b47ae35d1e536b15ad5c0e7cb6fe7ebf48f32bf18249185307aa07d287c3c54df90f8463ce014531763536bd8f92428c0d13e0f9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/generic-limiter.md
CHANGED
|
@@ -35,7 +35,7 @@ require "async/limiter"
|
|
|
35
35
|
|
|
36
36
|
Async do
|
|
37
37
|
limiter = Async::Limiter::Generic.new
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
# Create async tasks through the limiter:
|
|
40
40
|
tasks = 5.times.map do |i|
|
|
41
41
|
limiter.async do |task|
|
|
@@ -81,7 +81,7 @@ Async do
|
|
|
81
81
|
# Allow unlimited concurrency but rate limit to 10 operations per second:
|
|
82
82
|
timing = Async::Limiter::Timing::LeakyBucket.new(10.0, 50.0)
|
|
83
83
|
limiter = Async::Limiter::Generic.new(timing: timing)
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
# All tasks start immediately, but timing strategy controls rate:
|
|
86
86
|
100.times do |i|
|
|
87
87
|
limiter.async do |task|
|
data/context/getting-started.md
CHANGED
|
@@ -86,7 +86,7 @@ Async do
|
|
|
86
86
|
|
|
87
87
|
limiter.acquire(timeout: 3)
|
|
88
88
|
# => nil
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
limiter.acquire(timeout: 3) do
|
|
91
91
|
puts "Acquired."
|
|
92
92
|
end or puts "Timed out!"
|
|
@@ -199,8 +199,8 @@ fair_limiter.acquire(cost: 8.0) do
|
|
|
199
199
|
end
|
|
200
200
|
|
|
201
201
|
# These must wait even though they need fewer tokens
|
|
202
|
-
fair_limiter.acquire(cost: 0.5)
|
|
203
|
-
fair_limiter.acquire(cost: 0.5)
|
|
202
|
+
fair_limiter.acquire(cost: 0.5){puts "Quick op 1"} # Blocked
|
|
203
|
+
fair_limiter.acquire(cost: 0.5){puts "Quick op 2"} # Blocked
|
|
204
204
|
```
|
|
205
205
|
|
|
206
206
|
#### Choosing the Right Strategy
|
data/context/limited-limiter.md
CHANGED
|
@@ -89,16 +89,16 @@ The limiter prevents convoy effects where quick timeouts aren't blocked by slow
|
|
|
89
89
|
limiter = Async::Limiter::Limited.new(1)
|
|
90
90
|
Async do
|
|
91
91
|
limiter.acquire # Fill to capacity.
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
results = []
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
# Start multiple tasks with different timeouts:
|
|
96
96
|
tasks = [
|
|
97
|
-
Async
|
|
98
|
-
Async
|
|
99
|
-
Async
|
|
97
|
+
Async{limiter.acquire(timeout: 1.0); results << "Long timeout."},
|
|
98
|
+
Async{limiter.acquire(timeout: 0.1); results << "Short timeout."},
|
|
99
|
+
Async{limiter.acquire(timeout: 0); results << "Non-blocking."},
|
|
100
100
|
]
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
# All tasks complete quickly, even with a long timeout task present:
|
|
103
103
|
tasks.map(&:wait)
|
|
104
104
|
puts results
|
data/context/queued-limiter.md
CHANGED
|
@@ -42,7 +42,7 @@ For fine-grained control over resource lifecycle:
|
|
|
42
42
|
|
|
43
43
|
```ruby
|
|
44
44
|
queue = Async::Queue.new
|
|
45
|
-
3.times
|
|
45
|
+
3.times{|i| queue.push("resource_#{i}")}
|
|
46
46
|
|
|
47
47
|
limiter = Async::Limiter::Queued.new(queue)
|
|
48
48
|
|
|
@@ -71,7 +71,7 @@ Async do
|
|
|
71
71
|
queue = Async::PriorityQueue.new
|
|
72
72
|
limiter = Async::Limiter::Queued.new(queue)
|
|
73
73
|
results = []
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
# Start tasks with different priorities
|
|
76
76
|
tasks = [
|
|
77
77
|
Async do
|
|
@@ -100,9 +100,9 @@ Async do
|
|
|
100
100
|
2.times do |i|
|
|
101
101
|
limiter.release("worker_#{i}")
|
|
102
102
|
end
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
tasks.each(&:wait)
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
puts results
|
|
107
107
|
# High priority task gets resource first, then medium, then low.
|
|
108
108
|
end
|
|
@@ -385,7 +385,7 @@ require "async/queue"
|
|
|
385
385
|
|
|
386
386
|
# Create resource queue
|
|
387
387
|
queue = Async::Queue.new
|
|
388
|
-
3.times
|
|
388
|
+
3.times{|i| queue.push("worker_#{i}")}
|
|
389
389
|
|
|
390
390
|
# Add timing constraint
|
|
391
391
|
timing = Async::Limiter::Timing::FixedWindow.new(2.0,
|
|
@@ -437,8 +437,8 @@ class RateLimitedAPIClient
|
|
|
437
437
|
|
|
438
438
|
def make_request(endpoint, cost: 1.0)
|
|
439
439
|
@limiter.acquire(cost: cost) do
|
|
440
|
-
|
|
441
|
-
|
|
440
|
+
# Make actual HTTP request:
|
|
441
|
+
puts "Making request to #{endpoint} at #{Time.now}"
|
|
442
442
|
simulate_http_request(endpoint)
|
|
443
443
|
end
|
|
444
444
|
end
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2025, by Shopify Inc.
|
|
4
|
+
# Copyright, 2025-2026, by Shopify Inc.
|
|
5
5
|
# Copyright, 2025, by Samuel Williams.
|
|
6
6
|
|
|
7
7
|
require "async/task"
|
|
8
8
|
require "async/deadline"
|
|
9
|
+
require "async/utilization"
|
|
10
|
+
require "json"
|
|
9
11
|
require_relative "timing/none"
|
|
10
12
|
require_relative "timing/sliding_window"
|
|
11
13
|
require_relative "token"
|
|
@@ -23,10 +25,12 @@ module Async
|
|
|
23
25
|
# Initialize a new generic limiter.
|
|
24
26
|
# @parameter timing [#acquire, #wait, #maximum_cost] Strategy for timing constraints.
|
|
25
27
|
# @parameter parent [Async::Task, nil] Parent task for creating child tasks.
|
|
26
|
-
|
|
28
|
+
# @parameter utilization [#metric] Registry-like object for utilization metrics.
|
|
29
|
+
def initialize(timing: Timing::None, parent: nil, tags: nil, utilization: Async::Utilization::Registry.new)
|
|
27
30
|
@timing = timing
|
|
28
31
|
@parent = parent
|
|
29
32
|
@tags = tags
|
|
33
|
+
@utilization = utilization
|
|
30
34
|
|
|
31
35
|
@mutex = Mutex.new
|
|
32
36
|
end
|
|
@@ -34,6 +38,9 @@ module Async
|
|
|
34
38
|
# @attribute [Array(String)] Tags associated with this limiter for identification or categorization.
|
|
35
39
|
attr :tags
|
|
36
40
|
|
|
41
|
+
# @attribute [#metric] Registry-like object for utilization metrics.
|
|
42
|
+
attr :utilization
|
|
43
|
+
|
|
37
44
|
# @returns [Boolean] Whether this limiter is currently limiting concurrency.
|
|
38
45
|
def limited?
|
|
39
46
|
false
|
|
@@ -66,7 +73,7 @@ module Async
|
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
# Manually acquire a resource with timing and concurrency coordination.
|
|
69
|
-
#
|
|
76
|
+
#
|
|
70
77
|
# This method provides the core acquisition logic with support for:
|
|
71
78
|
# - Flexible timeout handling (blocking, non-blocking, timed)
|
|
72
79
|
# - Cost-based consumption for timing strategies
|
|
@@ -135,6 +142,18 @@ module Async
|
|
|
135
142
|
end
|
|
136
143
|
end
|
|
137
144
|
|
|
145
|
+
# Get a JSON-compatible representation of the limiter statistics.
|
|
146
|
+
# @returns [Hash] Statistics hash with current state.
|
|
147
|
+
def as_json(...)
|
|
148
|
+
statistics
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Get a JSON string representation of the limiter statistics.
|
|
152
|
+
# @returns [String] JSON encoded statistics.
|
|
153
|
+
def to_json(...)
|
|
154
|
+
as_json.to_json(...)
|
|
155
|
+
end
|
|
156
|
+
|
|
138
157
|
protected
|
|
139
158
|
|
|
140
159
|
def acquire_synchronized(timeout, cost, **options)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2020, by Bruno Sutic.
|
|
5
|
-
# Copyright, 2025, by Shopify Inc.
|
|
5
|
+
# Copyright, 2025-2026, by Shopify Inc.
|
|
6
6
|
# Copyright, 2025, by Samuel Williams.
|
|
7
7
|
|
|
8
8
|
require_relative "generic"
|
|
@@ -20,16 +20,22 @@ module Async
|
|
|
20
20
|
class Limited < Generic
|
|
21
21
|
# Initialize a limited concurrency limiter.
|
|
22
22
|
# @parameter limit [Integer] Maximum concurrent tasks allowed.
|
|
23
|
-
# @parameter
|
|
24
|
-
# @parameter parent [Async::Task, nil] Parent task for creating child tasks.
|
|
23
|
+
# @parameter options [Hash] Options passed to {Generic#initialize}.
|
|
25
24
|
# @raises [ArgumentError] If limit is not positive.
|
|
26
|
-
def initialize(limit = 1,
|
|
27
|
-
super(
|
|
25
|
+
def initialize(limit = 1, **options)
|
|
26
|
+
super(**options)
|
|
28
27
|
|
|
29
28
|
@limit = limit
|
|
30
29
|
@count = 0
|
|
31
30
|
|
|
32
31
|
@available = ConditionVariable.new
|
|
32
|
+
|
|
33
|
+
@acquired_count_metric = @utilization.metric(:acquired_count)
|
|
34
|
+
@available_count_metric = @utilization.metric(:available_count)
|
|
35
|
+
@waiting_count_metric = @utilization.metric(:waiting_count)
|
|
36
|
+
@reacquire_waiting_count_metric = @utilization.metric(:reacquire_waiting_count)
|
|
37
|
+
|
|
38
|
+
update_utilization_metrics
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
# @attribute [Integer] The maximum number of concurrent tasks.
|
|
@@ -38,6 +44,26 @@ module Async
|
|
|
38
44
|
# @attribute [Integer] Current count of active tasks.
|
|
39
45
|
attr_reader :count
|
|
40
46
|
|
|
47
|
+
# @returns [Integer] Current count of active tasks.
|
|
48
|
+
def acquired_count
|
|
49
|
+
@mutex.synchronize{@count}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @returns [Integer] Current count of available capacity.
|
|
53
|
+
def available_count
|
|
54
|
+
@mutex.synchronize{@limit - @count}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @returns [Integer] Current count of tasks waiting for capacity.
|
|
58
|
+
def waiting_count
|
|
59
|
+
@waiting_count_metric.value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @returns [Integer] Current count of reacquiring tasks waiting for capacity.
|
|
63
|
+
def reacquire_waiting_count
|
|
64
|
+
@reacquire_waiting_count_metric.value
|
|
65
|
+
end
|
|
66
|
+
|
|
41
67
|
# Check if a new task can be acquired.
|
|
42
68
|
# @returns [Boolean] True if under the limit.
|
|
43
69
|
def limited?
|
|
@@ -51,6 +77,7 @@ module Async
|
|
|
51
77
|
@mutex.synchronize do
|
|
52
78
|
old_limit = @limit
|
|
53
79
|
@limit = new_limit
|
|
80
|
+
update_utilization_metrics
|
|
54
81
|
|
|
55
82
|
# Wake up waiting tasks if limit increased:
|
|
56
83
|
@available.broadcast if new_limit > old_limit
|
|
@@ -64,6 +91,10 @@ module Async
|
|
|
64
91
|
{
|
|
65
92
|
limit: @limit,
|
|
66
93
|
count: @count,
|
|
94
|
+
acquired_count: @count,
|
|
95
|
+
available_count: @limit - @count,
|
|
96
|
+
waiting_count: @waiting_count_metric.value,
|
|
97
|
+
reacquire_waiting_count: @reacquire_waiting_count_metric.value,
|
|
67
98
|
timing: @timing.statistics
|
|
68
99
|
}
|
|
69
100
|
end
|
|
@@ -72,32 +103,57 @@ module Async
|
|
|
72
103
|
protected
|
|
73
104
|
|
|
74
105
|
# Acquire resource with optional deadline.
|
|
75
|
-
def acquire_resource(deadline, **options)
|
|
106
|
+
def acquire_resource(deadline, reacquire: false, **options)
|
|
76
107
|
# Fast path: immediate return for expired deadlines, but only if at capacity
|
|
77
108
|
return nil if deadline&.expired? && @count >= @limit
|
|
78
109
|
|
|
110
|
+
waiting = false
|
|
111
|
+
acquired = false
|
|
112
|
+
|
|
79
113
|
# Wait for capacity with deadline tracking
|
|
80
114
|
while @count >= @limit
|
|
81
115
|
remaining = deadline&.remaining
|
|
82
116
|
return nil if remaining && remaining <= 0
|
|
83
117
|
|
|
118
|
+
unless waiting
|
|
119
|
+
@waiting_count_metric.increment
|
|
120
|
+
@reacquire_waiting_count_metric.increment if reacquire
|
|
121
|
+
waiting = true
|
|
122
|
+
end
|
|
123
|
+
|
|
84
124
|
unless @available.wait(@mutex, remaining)
|
|
85
125
|
return nil # Timeout exceeded
|
|
86
126
|
end
|
|
87
127
|
end
|
|
88
128
|
|
|
89
129
|
@count += 1
|
|
130
|
+
acquired = true
|
|
90
131
|
|
|
91
132
|
return true
|
|
133
|
+
ensure
|
|
134
|
+
if waiting
|
|
135
|
+
@waiting_count_metric.decrement
|
|
136
|
+
@reacquire_waiting_count_metric.decrement if reacquire
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
update_utilization_metrics if acquired
|
|
92
140
|
end
|
|
93
141
|
|
|
94
142
|
# Release resource.
|
|
95
143
|
def release_resource(resource)
|
|
96
144
|
@mutex.synchronize do
|
|
97
145
|
@count -= 1
|
|
146
|
+
update_utilization_metrics
|
|
98
147
|
@available.signal
|
|
99
148
|
end
|
|
100
149
|
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def update_utilization_metrics
|
|
154
|
+
@acquired_count_metric.set(@count)
|
|
155
|
+
@available_count_metric.set(@limit - @count)
|
|
156
|
+
end
|
|
101
157
|
end
|
|
102
158
|
end
|
|
103
159
|
end
|
data/lib/async/limiter/queued.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2025, by Francisco Mejia.
|
|
5
|
-
# Copyright, 2025, by Shopify Inc.
|
|
5
|
+
# Copyright, 2025-2026, by Shopify Inc.
|
|
6
6
|
# Copyright, 2025, by Samuel Williams.
|
|
7
7
|
|
|
8
8
|
require_relative "generic"
|
|
@@ -28,16 +28,42 @@ module Async
|
|
|
28
28
|
|
|
29
29
|
# Initialize a queue-based limiter.
|
|
30
30
|
# @parameter queue [#push, #pop, #empty?] Thread-safe queue containing pre-existing resources.
|
|
31
|
-
# @parameter
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
super(timing: timing, parent: parent)
|
|
31
|
+
# @parameter options [Hash] Options passed to {Generic#initialize}.
|
|
32
|
+
def initialize(queue = self.class.default_queue, **options)
|
|
33
|
+
super(**options)
|
|
35
34
|
@queue = queue
|
|
35
|
+
|
|
36
|
+
@acquired_count_metric = @utilization.metric(:acquired_count)
|
|
37
|
+
@available_count_metric = @utilization.metric(:available_count)
|
|
38
|
+
@waiting_count_metric = @utilization.metric(:waiting_count)
|
|
39
|
+
@reacquire_waiting_count_metric = @utilization.metric(:reacquire_waiting_count)
|
|
40
|
+
|
|
41
|
+
update_utilization_metrics
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
# @attribute [Queue] The queue managing resources.
|
|
39
45
|
attr_reader :queue
|
|
40
46
|
|
|
47
|
+
# @returns [Integer] Current count of acquired resources.
|
|
48
|
+
def acquired_count
|
|
49
|
+
@acquired_count_metric.value
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @returns [Integer] Current count of available resources.
|
|
53
|
+
def available_count
|
|
54
|
+
@queue.size
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @returns [Integer] Current count of tasks waiting for resources.
|
|
58
|
+
def waiting_count
|
|
59
|
+
@queue.waiting_count
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @returns [Integer] Current count of reacquiring tasks waiting for resources.
|
|
63
|
+
def reacquire_waiting_count
|
|
64
|
+
@reacquire_waiting_count_metric.value
|
|
65
|
+
end
|
|
66
|
+
|
|
41
67
|
# Check if a new task can be acquired.
|
|
42
68
|
# @returns [Boolean] True if resources are available.
|
|
43
69
|
def limited?
|
|
@@ -51,6 +77,8 @@ module Async
|
|
|
51
77
|
count.times do
|
|
52
78
|
@queue.push(value)
|
|
53
79
|
end
|
|
80
|
+
|
|
81
|
+
update_utilization_metrics
|
|
54
82
|
end
|
|
55
83
|
|
|
56
84
|
# Get current limiter statistics.
|
|
@@ -60,6 +88,10 @@ module Async
|
|
|
60
88
|
{
|
|
61
89
|
waiting: @queue.waiting_count,
|
|
62
90
|
available: @queue.size,
|
|
91
|
+
acquired_count: @acquired_count_metric.value,
|
|
92
|
+
available_count: @queue.size,
|
|
93
|
+
waiting_count: @queue.waiting_count,
|
|
94
|
+
reacquire_waiting_count: @reacquire_waiting_count_metric.value,
|
|
63
95
|
timing: @timing.statistics
|
|
64
96
|
}
|
|
65
97
|
end
|
|
@@ -69,16 +101,36 @@ module Async
|
|
|
69
101
|
|
|
70
102
|
# Acquire a resource from the queue with optional deadline.
|
|
71
103
|
def acquire_resource(deadline, reacquire: false, **options)
|
|
104
|
+
@reacquire_waiting_count_metric.increment if reacquire
|
|
105
|
+
update_utilization_metrics if reacquire
|
|
106
|
+
|
|
72
107
|
@mutex.unlock
|
|
73
|
-
|
|
108
|
+
resource = @queue.pop(timeout: deadline&.remaining, **options)
|
|
109
|
+
return resource
|
|
74
110
|
ensure
|
|
75
111
|
@mutex.lock
|
|
112
|
+
@reacquire_waiting_count_metric.decrement if reacquire
|
|
113
|
+
@acquired_count_metric.increment if resource
|
|
114
|
+
update_utilization_metrics if reacquire || resource
|
|
76
115
|
end
|
|
77
116
|
|
|
78
117
|
# Release a previously acquired resource back to the queue.
|
|
79
118
|
def release_resource(value)
|
|
119
|
+
@mutex.synchronize do
|
|
120
|
+
@acquired_count_metric.decrement if @acquired_count_metric.value > 0
|
|
121
|
+
update_utilization_metrics
|
|
122
|
+
end
|
|
123
|
+
|
|
80
124
|
# Return a default resource to the queue:
|
|
81
125
|
@queue.push(value)
|
|
126
|
+
update_utilization_metrics
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def update_utilization_metrics
|
|
132
|
+
@available_count_metric.set(@queue.size)
|
|
133
|
+
@waiting_count_metric.set(@queue.waiting_count)
|
|
82
134
|
end
|
|
83
135
|
end
|
|
84
136
|
end
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Copyright, 2020, by Bruno Sutic.
|
|
5
5
|
# Copyright, 2025, by Shopify Inc.
|
|
6
6
|
# Copyright, 2025, by Samuel Williams.
|
|
7
|
+
# Copyright, 2026, by William T. Nelson.
|
|
7
8
|
|
|
8
9
|
require_relative "sliding_window"
|
|
9
10
|
|
|
@@ -25,8 +26,6 @@ module Async
|
|
|
25
26
|
# Get current timing strategy statistics.
|
|
26
27
|
# @returns [Hash] Statistics hash with current state.
|
|
27
28
|
def statistics
|
|
28
|
-
current_time = Time.now
|
|
29
|
-
|
|
30
29
|
{
|
|
31
30
|
name: "FixedWindow",
|
|
32
31
|
window_duration: @duration,
|
data/license.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Copyright, 2020-2021, by Bruno Sutic.
|
|
4
4
|
Copyright, 2025, by Francisco Mejia.
|
|
5
|
-
Copyright, 2025, by Shopify Inc.
|
|
6
|
-
Copyright, 2025, by Samuel Williams.
|
|
7
|
-
Copyright, 2025, by William T. Nelson.
|
|
5
|
+
Copyright, 2025-2026, by Shopify Inc.
|
|
6
|
+
Copyright, 2025-2026, by Samuel Williams.
|
|
7
|
+
Copyright, 2025-2026, by William T. Nelson.
|
|
8
8
|
|
|
9
9
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
10
|
of this software and associated documentation files (the "Software"), to deal
|
data/readme.md
CHANGED
|
@@ -20,6 +20,35 @@ Please see the [project documentation](https://socketry.github.io/async-limiter/
|
|
|
20
20
|
|
|
21
21
|
- [Token Usage](https://socketry.github.io/async-limiter/guides/token-usage/index) - This guide explains how to use tokens for advanced resource management with `async-limiter`. Tokens provide sophisticated resource handling with support for re-acquisition and automatic cleanup.
|
|
22
22
|
|
|
23
|
+
## Releases
|
|
24
|
+
|
|
25
|
+
Please see the [project releases](https://socketry.github.io/async-limiter/releases/index) for all releases.
|
|
26
|
+
|
|
27
|
+
### v2.2.0
|
|
28
|
+
|
|
29
|
+
- Add `async-utilization` metrics for limiter telemetry counters.
|
|
30
|
+
|
|
31
|
+
### v2.1.0
|
|
32
|
+
|
|
33
|
+
- Add telemetry counters to `Async::Limiter::Limited` and `Async::Limiter::Queued`: `acquired_count`, `available_count`, `waiting_count`, and `reacquire_waiting_count` for observability into limiter state.
|
|
34
|
+
- Add `as_json` and `to_json` methods to `Async::Limiter::Generic` for JSON serialization of limiter statistics.
|
|
35
|
+
- Fix unused variable warning in `Async::Limiter::Timing::FixedWindow`.
|
|
36
|
+
|
|
37
|
+
### v2.0.0
|
|
38
|
+
|
|
39
|
+
The 2.0.x release should be considered somewhat unstable.
|
|
40
|
+
|
|
41
|
+
- **Breaking**: Complete API redesign. The v1.x classes (`Async::Limiter::Concurrent`, `Async::Limiter::Unlimited`, etc.) have been replaced with a new inheritance-based architecture.
|
|
42
|
+
- **Breaking**: Removed `blocking?` method due to inherent race conditions. Use `acquire(timeout: 0)` for non-blocking checks.
|
|
43
|
+
- **Breaking**: Timing strategies now use consumption-only model (no explicit `release` methods).
|
|
44
|
+
- **Breaking**: Window classes moved from `limiter/window/` to `limiter/timing/` with renamed classes.
|
|
45
|
+
- [New Architecture (replaces v1.x classes)](https://socketry.github.io/async-limiter/releases/index#new-architecture-\(replaces-v1.x-classes\))
|
|
46
|
+
- [Advanced Timeout Features](https://socketry.github.io/async-limiter/releases/index#advanced-timeout-features)
|
|
47
|
+
- [Cost-Based Acquisition](https://socketry.github.io/async-limiter/releases/index#cost-based-acquisition)
|
|
48
|
+
- [Enhanced Timing Strategies](https://socketry.github.io/async-limiter/releases/index#enhanced-timing-strategies)
|
|
49
|
+
- [Token-Based Resource Management](https://socketry.github.io/async-limiter/releases/index#token-based-resource-management)
|
|
50
|
+
- [Thread Safety and Performance](https://socketry.github.io/async-limiter/releases/index#thread-safety-and-performance)
|
|
51
|
+
|
|
23
52
|
## See Also
|
|
24
53
|
|
|
25
54
|
- [falcon](https://github.com/socketry/falcon) - A high-performance web server
|
|
@@ -36,6 +65,22 @@ We welcome contributions to this project.
|
|
|
36
65
|
4. Push to the branch (`git push origin my-new-feature`).
|
|
37
66
|
5. Create new Pull Request.
|
|
38
67
|
|
|
68
|
+
### Running Tests
|
|
69
|
+
|
|
70
|
+
To run the test suite:
|
|
71
|
+
|
|
72
|
+
``` shell
|
|
73
|
+
bundle exec sus
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Making Releases
|
|
77
|
+
|
|
78
|
+
To make a new release:
|
|
79
|
+
|
|
80
|
+
``` shell
|
|
81
|
+
bundle exec bake gem:release:patch # or minor or major
|
|
82
|
+
```
|
|
83
|
+
|
|
39
84
|
### Developer Certificate of Origin
|
|
40
85
|
|
|
41
86
|
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v2.2.0
|
|
4
|
+
|
|
5
|
+
- Add `async-utilization` metrics for limiter telemetry counters.
|
|
6
|
+
|
|
7
|
+
## v2.1.0
|
|
8
|
+
|
|
9
|
+
- Add telemetry counters to `Async::Limiter::Limited` and `Async::Limiter::Queued`: `acquired_count`, `available_count`, `waiting_count`, and `reacquire_waiting_count` for observability into limiter state.
|
|
10
|
+
- Add `as_json` and `to_json` methods to `Async::Limiter::Generic` for JSON serialization of limiter statistics.
|
|
11
|
+
- Fix unused variable warning in `Async::Limiter::Timing::FixedWindow`.
|
|
12
|
+
|
|
3
13
|
## v2.0.0
|
|
4
14
|
|
|
5
15
|
The 2.0.x release should be considered somewhat unstable.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-limiter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bruno Sutic
|
|
8
8
|
- Shopify Inc.
|
|
9
9
|
- Samuel Williams
|
|
10
|
+
- William T. Nelson
|
|
10
11
|
- Francisco Mejia
|
|
11
|
-
- William
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain:
|
|
14
14
|
- |
|
|
@@ -56,6 +56,20 @@ dependencies:
|
|
|
56
56
|
- - ">="
|
|
57
57
|
- !ruby/object:Gem::Version
|
|
58
58
|
version: '2.31'
|
|
59
|
+
- !ruby/object:Gem::Dependency
|
|
60
|
+
name: async-utilization
|
|
61
|
+
requirement: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '0.4'
|
|
66
|
+
type: :runtime
|
|
67
|
+
prerelease: false
|
|
68
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - "~>"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0.4'
|
|
59
73
|
executables: []
|
|
60
74
|
extensions: []
|
|
61
75
|
extra_rdoc_files: []
|
|
@@ -99,14 +113,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
99
113
|
requirements:
|
|
100
114
|
- - ">="
|
|
101
115
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '3.
|
|
116
|
+
version: '3.3'
|
|
103
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
118
|
requirements:
|
|
105
119
|
- - ">="
|
|
106
120
|
- !ruby/object:Gem::Version
|
|
107
121
|
version: '0'
|
|
108
122
|
requirements: []
|
|
109
|
-
rubygems_version:
|
|
123
|
+
rubygems_version: 4.0.6
|
|
110
124
|
specification_version: 4
|
|
111
125
|
summary: Execution rate limiting for Async
|
|
112
126
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|