counting_semaphore 0.1.0 → 0.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.
@@ -0,0 +1,367 @@
1
+ # A counting semaphore that allows up to N concurrent operations.
2
+ # When capacity is exceeded, operations block until resources become available.
3
+ # API compatible with concurrent-ruby's Semaphore class.
4
+ module CountingSemaphore
5
+ VERSION: untyped
6
+
7
+ # Represents an acquired lease on a semaphore.
8
+ # Must be passed to release() to return the permits.
9
+ class Lease < Struct
10
+ # Returns a human-readable representation of the lease
11
+ def to_s: () -> String
12
+
13
+ # Returns detailed inspection string
14
+ def inspect: () -> String
15
+
16
+ # Returns the value of attribute semaphore
17
+ attr_accessor semaphore: Object
18
+
19
+ # Returns the value of attribute id
20
+ attr_accessor id: Object
21
+
22
+ # Returns the value of attribute permits
23
+ attr_accessor permits: Object
24
+ end
25
+
26
+ # Custom exception raised when a semaphore lease cannot be acquired within the specified timeout.
27
+ # Contains information about the failed acquisition attempt including the semaphore instance,
28
+ # number of permits requested, and the timeout duration.
29
+ class LeaseTimeout < StandardError
30
+ # Creates a new LeaseTimeout exception.
31
+ #
32
+ # _@param_ `permit_count` — Number of permits that were requested
33
+ #
34
+ # _@param_ `timeout_seconds` — The timeout duration that was exceeded
35
+ #
36
+ # _@param_ `semaphore` — The semaphore instance (optional)
37
+ def initialize: (Integer permit_count, Numeric timeout_seconds, ?(CountingSemaphore::LocalSemaphore | CountingSemaphore::RedisSemaphore)? semaphore) -> void
38
+
39
+ # _@return_ — The semaphore that timed out
40
+ #
41
+ # _@return_ — The number of permits that were requested
42
+ #
43
+ # _@return_ — The timeout duration in seconds
44
+ attr_reader semaphore: (CountingSemaphore::LocalSemaphore | CountingSemaphore::RedisSemaphore | Integer | Numeric)?
45
+
46
+ # _@return_ — The semaphore that timed out
47
+ #
48
+ # _@return_ — The number of permits that were requested
49
+ #
50
+ # _@return_ — The timeout duration in seconds
51
+ attr_reader permit_count: (CountingSemaphore::LocalSemaphore | CountingSemaphore::RedisSemaphore | Integer | Numeric)?
52
+
53
+ # _@return_ — The semaphore that timed out
54
+ #
55
+ # _@return_ — The number of permits that were requested
56
+ #
57
+ # _@return_ — The timeout duration in seconds
58
+ attr_reader timeout_seconds: (CountingSemaphore::LocalSemaphore | CountingSemaphore::RedisSemaphore | Integer | Numeric)?
59
+ end
60
+
61
+ # A null logger that discards all log messages.
62
+ # Provides the same interface as a real logger but does nothing.
63
+ # Only yields blocks when ENV["RUN_ALL_LOGGER_BLOCKS"] is set to "yes",
64
+ # which is useful in testing. Block form for Logger calls allows you
65
+ # to skip block evaluation if the Logger level is higher than your
66
+ # call, and thus bugs can nest in those Logger blocks. During
67
+ # testing it is helpful to excercise those blocks unconditionally.
68
+ module NullLogger
69
+ extend CountingSemaphore::NullLogger
70
+
71
+ # Logs a debug message. Discards the message but may yield the block for testing.
72
+ #
73
+ # _@param_ `message` — Optional message to log (discarded)
74
+ def debug: (?String? message) -> void
75
+
76
+ # Logs an info message. Discards the message but may yield the block for testing.
77
+ #
78
+ # _@param_ `message` — Optional message to log (discarded)
79
+ def info: (?String? message) -> void
80
+
81
+ # Logs a warning message. Discards the message but may yield the block for testing.
82
+ #
83
+ # _@param_ `message` — Optional message to log (discarded)
84
+ def warn: (?String? message) -> void
85
+
86
+ # Logs an error message. Discards the message but may yield the block for testing.
87
+ #
88
+ # _@param_ `message` — Optional message to log (discarded)
89
+ def error: (?String? message) -> void
90
+
91
+ # Logs a fatal message. Discards the message but may yield the block for testing.
92
+ #
93
+ # _@param_ `message` — Optional message to log (discarded)
94
+ def fatal: (?String? message) -> void
95
+
96
+ # Logs a debug message. Discards the message but may yield the block for testing.
97
+ #
98
+ # _@param_ `message` — Optional message to log (discarded)
99
+ def self.debug: (?String? message) -> void
100
+
101
+ # Logs an info message. Discards the message but may yield the block for testing.
102
+ #
103
+ # _@param_ `message` — Optional message to log (discarded)
104
+ def self.info: (?String? message) -> void
105
+
106
+ # Logs a warning message. Discards the message but may yield the block for testing.
107
+ #
108
+ # _@param_ `message` — Optional message to log (discarded)
109
+ def self.warn: (?String? message) -> void
110
+
111
+ # Logs an error message. Discards the message but may yield the block for testing.
112
+ #
113
+ # _@param_ `message` — Optional message to log (discarded)
114
+ def self.error: (?String? message) -> void
115
+
116
+ # Logs a fatal message. Discards the message but may yield the block for testing.
117
+ #
118
+ # _@param_ `message` — Optional message to log (discarded)
119
+ def self.fatal: (?String? message) -> void
120
+ end
121
+
122
+ class LocalSemaphore
123
+ include CountingSemaphore::WithLeaseSupport
124
+ SLEEP_WAIT_SECONDS: untyped
125
+
126
+ # Initialize the semaphore with a maximum capacity.
127
+ #
128
+ # _@param_ `capacity` — Maximum number of concurrent operations allowed (also called permits)
129
+ #
130
+ # _@param_ `logger` — the logger
131
+ def initialize: (Integer capacity, ?logger: Logger) -> void
132
+
133
+ # Acquires the given number of permits from this semaphore, blocking until all are available.
134
+ #
135
+ # _@param_ `permits` — Number of permits to acquire (default: 1)
136
+ #
137
+ # _@return_ — A lease object that must be passed to release()
138
+ def acquire: (?Integer permits) -> CountingSemaphore::Lease
139
+
140
+ # Releases a previously acquired lease, returning the permits to the semaphore.
141
+ #
142
+ # _@param_ `lease` — The lease object returned by acquire() or try_acquire()
143
+ def release: (CountingSemaphore::Lease lease) -> void
144
+
145
+ # Acquires the given number of permits from this semaphore, only if all are available
146
+ # at the time of invocation or within the timeout interval.
147
+ #
148
+ # _@param_ `permits` — Number of permits to acquire (default: 1)
149
+ #
150
+ # _@param_ `timeout` — Number of seconds to wait, or nil to return immediately (default: nil)
151
+ #
152
+ # _@return_ — A lease object if successful, nil otherwise
153
+ def try_acquire: (?Integer permits, ?timeout: Numeric?) -> CountingSemaphore::Lease?
154
+
155
+ # Returns the current number of permits available in this semaphore.
156
+ #
157
+ # _@return_ — Number of available permits
158
+ def available_permits: () -> Integer
159
+
160
+ # Acquires and returns all permits that are immediately available.
161
+ # Returns a single lease representing all drained permits.
162
+ #
163
+ # _@return_ — A lease for all available permits, or nil if none available
164
+ def drain_permits: () -> CountingSemaphore::Lease?
165
+
166
+ # Acquire a lease for the specified number of permits and execute the block.
167
+ # Blocks until sufficient resources are available.
168
+ # Kept for backwards compatibility - wraps acquire/release.
169
+ #
170
+ # _@param_ `permit_count` — Number of permits to acquire (default: 1)
171
+ #
172
+ # _@param_ `timeout` — Maximum time in seconds to wait for lease acquisition (default: 30). For Redis-backed semaphores, the timeout value will be rounded up to the nearest whole second due to Redis BLPOP limitations.
173
+ #
174
+ # _@return_ — The result of the block
175
+ def with_lease: (?Integer permit_count, ?timeout: Numeric) ?{ (CountingSemaphore::Lease? lease) -> void } -> untyped
176
+
177
+ # Get the current number of permits currently acquired.
178
+ # Kept for backwards compatibility.
179
+ #
180
+ # _@return_ — Number of permits currently in use
181
+ def currently_leased: () -> Integer
182
+
183
+ attr_reader capacity: Integer
184
+ end
185
+
186
+ class RedisSemaphore
187
+ include CountingSemaphore::WithLeaseSupport
188
+ LEASE_EXPIRATION_SECONDS: untyped
189
+ GET_LEASE_SCRIPT: untyped
190
+ GET_USAGE_SCRIPT: untyped
191
+ RELEASE_LEASE_SCRIPT: untyped
192
+ GET_LEASE_SCRIPT_SHA: untyped
193
+ GET_USAGE_SCRIPT_SHA: untyped
194
+ RELEASE_LEASE_SCRIPT_SHA: untyped
195
+
196
+ # sord warn - Redis wasn't able to be resolved to a constant in this project
197
+ # sord warn - ConnectionPool wasn't able to be resolved to a constant in this project
198
+ # sord omit - no YARD type given for "lease_expiration_seconds:", using untyped
199
+ # Initialize the semaphore with a maximum capacity and required namespace.
200
+ #
201
+ # _@param_ `capacity` — Maximum number of concurrent operations allowed (also called permits)
202
+ #
203
+ # _@param_ `namespace` — Required namespace for Redis keys
204
+ #
205
+ # _@param_ `redis` — Optional Redis client or connection pool (defaults to new Redis instance)
206
+ #
207
+ # _@param_ `logger` — the logger
208
+ def initialize: (
209
+ Integer capacity,
210
+ String namespace,
211
+ ?redis: (Redis | ConnectionPool)?,
212
+ ?logger: Logger,
213
+ ?lease_expiration_seconds: untyped
214
+ ) -> void
215
+
216
+ # Acquires the given number of permits from this semaphore, blocking until all are available.
217
+ #
218
+ # _@param_ `permits` — Number of permits to acquire (default: 1)
219
+ #
220
+ # _@return_ — A lease object that must be passed to release()
221
+ def acquire: (?Integer permits) -> CountingSemaphore::Lease
222
+
223
+ # Releases a previously acquired lease, returning the permits to the semaphore.
224
+ #
225
+ # _@param_ `lease` — The lease object returned by acquire() or try_acquire()
226
+ def release: (CountingSemaphore::Lease lease) -> void
227
+
228
+ # Acquires the given number of permits from this semaphore, only if all are available
229
+ # at the time of invocation or within the timeout interval.
230
+ #
231
+ # _@param_ `permits` — Number of permits to acquire (default: 1)
232
+ #
233
+ # _@param_ `timeout` — Number of seconds to wait, or nil to return immediately (default: nil). The timeout value will be rounded up to the nearest whole second due to Redis BLPOP limitations.
234
+ #
235
+ # _@return_ — A lease object if successful, nil otherwise
236
+ def try_acquire: (?Integer permits, ?timeout: Numeric?) -> CountingSemaphore::Lease?
237
+
238
+ # Returns the current number of permits available in this semaphore.
239
+ #
240
+ # _@return_ — Number of available permits
241
+ def available_permits: () -> Integer
242
+
243
+ # Acquires and returns all permits that are immediately available.
244
+ # Note: For distributed semaphores, this may not be perfectly accurate due to race conditions.
245
+ #
246
+ # _@return_ — A lease for all available permits, or nil if none available
247
+ def drain_permits: () -> CountingSemaphore::Lease?
248
+
249
+ # Returns debugging information about the current state of the semaphore.
250
+ # Includes current usage, capacity, available permits, and details about active leases.
251
+ #
252
+ # _@return_ — A hash containing :usage, :capacity, :available, and :active_leases
253
+ #
254
+ # ```ruby
255
+ # info = semaphore.debug_info
256
+ # puts "Usage: #{info[:usage]}/#{info[:capacity]}"
257
+ # info[:active_leases].each { |lease| puts "Lease: #{lease[:key]} - #{lease[:permits]} permits" }
258
+ # ```
259
+ def debug_info: () -> ::Hash[untyped, untyped]
260
+
261
+ # sord warn - Redis wasn't able to be resolved to a constant in this project
262
+ # sord warn - ConnectionPool wasn't able to be resolved to a constant in this project
263
+ # Wraps a Redis client to support both ConnectionPool and bare Redis connections
264
+ #
265
+ # _@param_ `redis` — The Redis client or connection pool
266
+ #
267
+ # _@return_ — A wrapper that supports the `with` method
268
+ def wrap_redis_client_with_pool: ((Redis | ConnectionPool) redis) -> Object
269
+
270
+ # Executes a block with a Redis connection from the pool
271
+ #
272
+ # _@return_ — The result of the block
273
+ def with_redis: () -> untyped
274
+
275
+ # Executes a Redis script with automatic fallback to EVAL on NOSCRIPT error
276
+ #
277
+ # _@param_ `script_type` — The type of script (:get_lease, :release_lease, :get_usage)
278
+ #
279
+ # _@param_ `keys` — Keys for the script
280
+ #
281
+ # _@param_ `argv` — Arguments for the script
282
+ #
283
+ # _@return_ — The result of the script execution
284
+ def execute_script: (Symbol script_type, ?keys: ::Array[untyped], ?argv: ::Array[untyped]) -> untyped
285
+
286
+ # sord omit - no YARD type given for "permit_count", using untyped
287
+ # sord omit - no YARD type given for "timeout_seconds:", using untyped
288
+ # sord omit - no YARD return type given, using untyped
289
+ def acquire_lease_internal: (untyped permit_count, timeout_seconds: untyped) -> untyped
290
+
291
+ # sord omit - no YARD type given for "permit_count", using untyped
292
+ # sord omit - no YARD type given for "remaining_timeout", using untyped
293
+ # sord omit - no YARD return type given, using untyped
294
+ def wait_for_permits: (untyped permit_count, untyped remaining_timeout) -> untyped
295
+
296
+ # sord omit - no YARD type given for "permit_count", using untyped
297
+ # sord omit - no YARD return type given, using untyped
298
+ def attempt_lease_acquisition: (untyped permit_count) -> untyped
299
+
300
+ # sord omit - no YARD type given for "lease_key", using untyped
301
+ # sord omit - no YARD type given for "permit_count", using untyped
302
+ # sord omit - no YARD return type given, using untyped
303
+ def release_lease: (untyped lease_key, untyped permit_count) -> untyped
304
+
305
+ # sord omit - no YARD return type given, using untyped
306
+ def get_current_usage: () -> untyped
307
+
308
+ # sord omit - no YARD return type given, using untyped
309
+ def generate_lease_id: () -> untyped
310
+
311
+ # Acquire a lease for the specified number of permits and execute the block.
312
+ # Blocks until sufficient resources are available.
313
+ # Kept for backwards compatibility - wraps acquire/release.
314
+ #
315
+ # _@param_ `permit_count` — Number of permits to acquire (default: 1)
316
+ #
317
+ # _@param_ `timeout` — Maximum time in seconds to wait for lease acquisition (default: 30). For Redis-backed semaphores, the timeout value will be rounded up to the nearest whole second due to Redis BLPOP limitations.
318
+ #
319
+ # _@return_ — The result of the block
320
+ def with_lease: (?Integer permit_count, ?timeout: Numeric) ?{ (CountingSemaphore::Lease? lease) -> void } -> untyped
321
+
322
+ # Get the current number of permits currently acquired.
323
+ # Kept for backwards compatibility.
324
+ #
325
+ # _@return_ — Number of permits currently in use
326
+ def currently_leased: () -> Integer
327
+
328
+ attr_reader capacity: Integer
329
+
330
+ # Null pool for bare Redis connections that don't need connection pooling.
331
+ # Provides a compatible interface with ConnectionPool for bare Redis instances.
332
+ class NullPool
333
+ # sord warn - Redis wasn't able to be resolved to a constant in this project
334
+ # Creates a new NullPool wrapper around a Redis connection.
335
+ #
336
+ # _@param_ `redis_connection` — The Redis connection to wrap
337
+ def initialize: (Redis redis_connection) -> void
338
+
339
+ # Yields the wrapped Redis connection to the block.
340
+ # Provides ConnectionPool-compatible interface.
341
+ #
342
+ # _@return_ — The result of the block
343
+ def with: () -> untyped
344
+ end
345
+ end
346
+
347
+ # Module providing backwards-compatible with_lease method
348
+ # Requires the including class to implement: acquire, release, capacity
349
+ module WithLeaseSupport
350
+ # Acquire a lease for the specified number of permits and execute the block.
351
+ # Blocks until sufficient resources are available.
352
+ # Kept for backwards compatibility - wraps acquire/release.
353
+ #
354
+ # _@param_ `permit_count` — Number of permits to acquire (default: 1)
355
+ #
356
+ # _@param_ `timeout` — Maximum time in seconds to wait for lease acquisition (default: 30). For Redis-backed semaphores, the timeout value will be rounded up to the nearest whole second due to Redis BLPOP limitations.
357
+ #
358
+ # _@return_ — The result of the block
359
+ def with_lease: (?Integer permit_count, ?timeout: Numeric) ?{ (CountingSemaphore::Lease? lease) -> void } -> untyped
360
+
361
+ # Get the current number of permits currently acquired.
362
+ # Kept for backwards compatibility.
363
+ #
364
+ # _@return_ — Number of permits currently in use
365
+ def currently_leased: () -> Integer
366
+ end
367
+ end