async-limiter 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9b6e7e0d162f4068ced173efa8f365f53e809bf36b80fb41c21988865ea558b
4
- data.tar.gz: d6ed3ed9482da942bf2fdec9403d2c842174eaa090cf0ea61036cf839c731d9c
3
+ metadata.gz: d153319af347142e9837f3a8e7523f47f2bbd28d56e497d03c0fd7279302af8f
4
+ data.tar.gz: 9d1da2a75240ba77a68d26141abf50b677d5248b9bf191a50e8821aac3bceee7
5
5
  SHA512:
6
- metadata.gz: 496cd5748700aa4c7eb897fc2c47720ea10c7650ab2407e0d15394ce56d44c1d7bd82a31f4138b9437d1e0e474449defe2a5f190d2140f41bc44afafa68bfe8b
7
- data.tar.gz: 8733a7138915188e423fee9f9b4213cc087d93b86f69a43e3ac7954272e94eb15ef99e2ea100ccfd621aebc6d4f292267c298d609f559448f92e45c7c8ffb0dd
6
+ metadata.gz: a35d89989f3d8faf3cd203a84b2e04125809ae07d6609ce0dc205214f97bc1bf11ab099bbc46542a3fa701674185e641461d548f67bb1d1325b8badce9b6c6cc
7
+ data.tar.gz: df563bd337ae1e9157df61f0b47ae35d1e536b15ad5c0e7cb6fe7ebf48f32bf18249185307aa07d287c3c54df90f8463ce014531763536bd8f92428c0d13e0f9
checksums.yaml.gz.sig CHANGED
Binary file
@@ -6,6 +6,7 @@
6
6
 
7
7
  require "async/task"
8
8
  require "async/deadline"
9
+ require "async/utilization"
9
10
  require "json"
10
11
  require_relative "timing/none"
11
12
  require_relative "timing/sliding_window"
@@ -24,10 +25,12 @@ module Async
24
25
  # Initialize a new generic limiter.
25
26
  # @parameter timing [#acquire, #wait, #maximum_cost] Strategy for timing constraints.
26
27
  # @parameter parent [Async::Task, nil] Parent task for creating child tasks.
27
- def initialize(timing: Timing::None, parent: nil, tags: nil)
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)
28
30
  @timing = timing
29
31
  @parent = parent
30
32
  @tags = tags
33
+ @utilization = utilization
31
34
 
32
35
  @mutex = Mutex.new
33
36
  end
@@ -35,6 +38,9 @@ module Async
35
38
  # @attribute [Array(String)] Tags associated with this limiter for identification or categorization.
36
39
  attr :tags
37
40
 
41
+ # @attribute [#metric] Registry-like object for utilization metrics.
42
+ attr :utilization
43
+
38
44
  # @returns [Boolean] Whether this limiter is currently limiting concurrency.
39
45
  def limited?
40
46
  false
@@ -67,7 +73,7 @@ module Async
67
73
  end
68
74
 
69
75
  # Manually acquire a resource with timing and concurrency coordination.
70
- #
76
+ #
71
77
  # This method provides the core acquisition logic with support for:
72
78
  # - Flexible timeout handling (blocking, non-blocking, timed)
73
79
  # - Cost-based consumption for timing strategies
@@ -20,18 +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 timing [#acquire, #wait, #maximum_cost] Strategy for timing constraints.
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, timing: Timing::None, parent: nil)
27
- super(timing: timing, parent: parent)
25
+ def initialize(limit = 1, **options)
26
+ super(**options)
28
27
 
29
28
  @limit = limit
30
29
  @count = 0
31
- @waiting_count = 0
32
- @reacquire_waiting_count = 0
33
30
 
34
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
35
39
  end
36
40
 
37
41
  # @attribute [Integer] The maximum number of concurrent tasks.
@@ -52,12 +56,12 @@ module Async
52
56
 
53
57
  # @returns [Integer] Current count of tasks waiting for capacity.
54
58
  def waiting_count
55
- @mutex.synchronize{@waiting_count}
59
+ @waiting_count_metric.value
56
60
  end
57
61
 
58
62
  # @returns [Integer] Current count of reacquiring tasks waiting for capacity.
59
63
  def reacquire_waiting_count
60
- @mutex.synchronize{@reacquire_waiting_count}
64
+ @reacquire_waiting_count_metric.value
61
65
  end
62
66
 
63
67
  # Check if a new task can be acquired.
@@ -73,6 +77,7 @@ module Async
73
77
  @mutex.synchronize do
74
78
  old_limit = @limit
75
79
  @limit = new_limit
80
+ update_utilization_metrics
76
81
 
77
82
  # Wake up waiting tasks if limit increased:
78
83
  @available.broadcast if new_limit > old_limit
@@ -88,8 +93,8 @@ module Async
88
93
  count: @count,
89
94
  acquired_count: @count,
90
95
  available_count: @limit - @count,
91
- waiting_count: @waiting_count,
92
- reacquire_waiting_count: @reacquire_waiting_count,
96
+ waiting_count: @waiting_count_metric.value,
97
+ reacquire_waiting_count: @reacquire_waiting_count_metric.value,
93
98
  timing: @timing.statistics
94
99
  }
95
100
  end
@@ -103,6 +108,7 @@ module Async
103
108
  return nil if deadline&.expired? && @count >= @limit
104
109
 
105
110
  waiting = false
111
+ acquired = false
106
112
 
107
113
  # Wait for capacity with deadline tracking
108
114
  while @count >= @limit
@@ -110,8 +116,8 @@ module Async
110
116
  return nil if remaining && remaining <= 0
111
117
 
112
118
  unless waiting
113
- @waiting_count += 1
114
- @reacquire_waiting_count += 1 if reacquire
119
+ @waiting_count_metric.increment
120
+ @reacquire_waiting_count_metric.increment if reacquire
115
121
  waiting = true
116
122
  end
117
123
 
@@ -121,22 +127,33 @@ module Async
121
127
  end
122
128
 
123
129
  @count += 1
130
+ acquired = true
124
131
 
125
132
  return true
126
133
  ensure
127
134
  if waiting
128
- @waiting_count -= 1
129
- @reacquire_waiting_count -= 1 if reacquire
135
+ @waiting_count_metric.decrement
136
+ @reacquire_waiting_count_metric.decrement if reacquire
130
137
  end
138
+
139
+ update_utilization_metrics if acquired
131
140
  end
132
141
 
133
142
  # Release resource.
134
143
  def release_resource(resource)
135
144
  @mutex.synchronize do
136
145
  @count -= 1
146
+ update_utilization_metrics
137
147
  @available.signal
138
148
  end
139
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
140
157
  end
141
158
  end
142
159
  end
@@ -28,13 +28,17 @@ 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 timing [#acquire, #wait, #maximum_cost] Strategy for timing constraints.
32
- # @parameter parent [Async::Task, nil] Parent task for creating child tasks.
33
- def initialize(queue = self.class.default_queue, timing: Timing::None, parent: nil)
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
36
- @acquired_count = 0
37
- @reacquire_waiting_count = 0
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
38
42
  end
39
43
 
40
44
  # @attribute [Queue] The queue managing resources.
@@ -42,7 +46,7 @@ module Async
42
46
 
43
47
  # @returns [Integer] Current count of acquired resources.
44
48
  def acquired_count
45
- @mutex.synchronize{@acquired_count}
49
+ @acquired_count_metric.value
46
50
  end
47
51
 
48
52
  # @returns [Integer] Current count of available resources.
@@ -57,7 +61,7 @@ module Async
57
61
 
58
62
  # @returns [Integer] Current count of reacquiring tasks waiting for resources.
59
63
  def reacquire_waiting_count
60
- @mutex.synchronize{@reacquire_waiting_count}
64
+ @reacquire_waiting_count_metric.value
61
65
  end
62
66
 
63
67
  # Check if a new task can be acquired.
@@ -73,6 +77,8 @@ module Async
73
77
  count.times do
74
78
  @queue.push(value)
75
79
  end
80
+
81
+ update_utilization_metrics
76
82
  end
77
83
 
78
84
  # Get current limiter statistics.
@@ -82,10 +88,10 @@ module Async
82
88
  {
83
89
  waiting: @queue.waiting_count,
84
90
  available: @queue.size,
85
- acquired_count: @acquired_count,
91
+ acquired_count: @acquired_count_metric.value,
86
92
  available_count: @queue.size,
87
93
  waiting_count: @queue.waiting_count,
88
- reacquire_waiting_count: @reacquire_waiting_count,
94
+ reacquire_waiting_count: @reacquire_waiting_count_metric.value,
89
95
  timing: @timing.statistics
90
96
  }
91
97
  end
@@ -95,25 +101,36 @@ module Async
95
101
 
96
102
  # Acquire a resource from the queue with optional deadline.
97
103
  def acquire_resource(deadline, reacquire: false, **options)
98
- @reacquire_waiting_count += 1 if reacquire
104
+ @reacquire_waiting_count_metric.increment if reacquire
105
+ update_utilization_metrics if reacquire
99
106
 
100
107
  @mutex.unlock
101
108
  resource = @queue.pop(timeout: deadline&.remaining, **options)
102
109
  return resource
103
110
  ensure
104
111
  @mutex.lock
105
- @reacquire_waiting_count -= 1 if reacquire
106
- @acquired_count += 1 if resource
112
+ @reacquire_waiting_count_metric.decrement if reacquire
113
+ @acquired_count_metric.increment if resource
114
+ update_utilization_metrics if reacquire || resource
107
115
  end
108
116
 
109
117
  # Release a previously acquired resource back to the queue.
110
118
  def release_resource(value)
111
119
  @mutex.synchronize do
112
- @acquired_count -= 1 if @acquired_count > 0
120
+ @acquired_count_metric.decrement if @acquired_count_metric.value > 0
121
+ update_utilization_metrics
113
122
  end
114
123
 
115
124
  # Return a default resource to the queue:
116
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)
117
134
  end
118
135
  end
119
136
  end
@@ -7,6 +7,6 @@
7
7
 
8
8
  module Async
9
9
  module Limiter
10
- VERSION = "2.1.0"
10
+ VERSION = "2.2.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -24,6 +24,10 @@ Please see the [project documentation](https://socketry.github.io/async-limiter/
24
24
 
25
25
  Please see the [project releases](https://socketry.github.io/async-limiter/releases/index) for all releases.
26
26
 
27
+ ### v2.2.0
28
+
29
+ - Add `async-utilization` metrics for limiter telemetry counters.
30
+
27
31
  ### v2.1.0
28
32
 
29
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.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v2.2.0
4
+
5
+ - Add `async-utilization` metrics for limiter telemetry counters.
6
+
3
7
  ## v2.1.0
4
8
 
5
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.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-limiter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Sutic
@@ -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: []
metadata.gz.sig CHANGED
Binary file