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.
- checksums.yaml +4 -4
- data/README.md +167 -21
- data/Rakefile +6 -1
- data/lib/counting_semaphore/local_semaphore.rb +129 -47
- data/lib/counting_semaphore/null_logger.rb +29 -2
- data/lib/counting_semaphore/redis_semaphore.rb +175 -86
- data/lib/counting_semaphore/version.rb +3 -1
- data/lib/counting_semaphore/with_lease_support.rb +60 -0
- data/lib/counting_semaphore.rb +39 -5
- data/rbi/counting_semaphore.rbi +517 -0
- data/sig/counting_semaphore.rbs +367 -0
- data/test/counting_semaphore/local_semaphore_test.rb +365 -3
- data/test/counting_semaphore/redis_semaphore_test.rb +423 -9
- metadata +19 -4
- data/Gemfile.lock +0 -76
- data/lib/counting_semaphore/shared_semaphore.rb +0 -381
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# A counting semaphore that allows up to N concurrent operations.
|
|
3
|
+
# When capacity is exceeded, operations block until resources become available.
|
|
4
|
+
# API compatible with concurrent-ruby's Semaphore class.
|
|
5
|
+
module CountingSemaphore
|
|
6
|
+
VERSION = T.let("0.2.0", T.untyped)
|
|
7
|
+
|
|
8
|
+
# Represents an acquired lease on a semaphore.
|
|
9
|
+
# Must be passed to release() to return the permits.
|
|
10
|
+
class Lease < Struct
|
|
11
|
+
# Returns a human-readable representation of the lease
|
|
12
|
+
sig { returns(String) }
|
|
13
|
+
def to_s; end
|
|
14
|
+
|
|
15
|
+
# Returns detailed inspection string
|
|
16
|
+
sig { returns(String) }
|
|
17
|
+
def inspect; end
|
|
18
|
+
|
|
19
|
+
# Returns the value of attribute semaphore
|
|
20
|
+
sig { returns(Object) }
|
|
21
|
+
attr_accessor :semaphore
|
|
22
|
+
|
|
23
|
+
# Returns the value of attribute id
|
|
24
|
+
sig { returns(Object) }
|
|
25
|
+
attr_accessor :id
|
|
26
|
+
|
|
27
|
+
# Returns the value of attribute permits
|
|
28
|
+
sig { returns(Object) }
|
|
29
|
+
attr_accessor :permits
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Custom exception raised when a semaphore lease cannot be acquired within the specified timeout.
|
|
33
|
+
# Contains information about the failed acquisition attempt including the semaphore instance,
|
|
34
|
+
# number of permits requested, and the timeout duration.
|
|
35
|
+
class LeaseTimeout < StandardError
|
|
36
|
+
# Creates a new LeaseTimeout exception.
|
|
37
|
+
#
|
|
38
|
+
# _@param_ `permit_count` — Number of permits that were requested
|
|
39
|
+
#
|
|
40
|
+
# _@param_ `timeout_seconds` — The timeout duration that was exceeded
|
|
41
|
+
#
|
|
42
|
+
# _@param_ `semaphore` — The semaphore instance (optional)
|
|
43
|
+
sig { params(permit_count: Integer, timeout_seconds: Numeric, semaphore: T.nilable(T.any(CountingSemaphore::LocalSemaphore, CountingSemaphore::RedisSemaphore))).void }
|
|
44
|
+
def initialize(permit_count, timeout_seconds, semaphore = nil); end
|
|
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
|
+
sig { returns(T.nilable(T.any(CountingSemaphore::LocalSemaphore, CountingSemaphore::RedisSemaphore, Integer, Numeric))) }
|
|
52
|
+
attr_reader :semaphore
|
|
53
|
+
|
|
54
|
+
# _@return_ — The semaphore that timed out
|
|
55
|
+
#
|
|
56
|
+
# _@return_ — The number of permits that were requested
|
|
57
|
+
#
|
|
58
|
+
# _@return_ — The timeout duration in seconds
|
|
59
|
+
sig { returns(T.nilable(T.any(CountingSemaphore::LocalSemaphore, CountingSemaphore::RedisSemaphore, Integer, Numeric))) }
|
|
60
|
+
attr_reader :permit_count
|
|
61
|
+
|
|
62
|
+
# _@return_ — The semaphore that timed out
|
|
63
|
+
#
|
|
64
|
+
# _@return_ — The number of permits that were requested
|
|
65
|
+
#
|
|
66
|
+
# _@return_ — The timeout duration in seconds
|
|
67
|
+
sig { returns(T.nilable(T.any(CountingSemaphore::LocalSemaphore, CountingSemaphore::RedisSemaphore, Integer, Numeric))) }
|
|
68
|
+
attr_reader :timeout_seconds
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A null logger that discards all log messages.
|
|
72
|
+
# Provides the same interface as a real logger but does nothing.
|
|
73
|
+
# Only yields blocks when ENV["RUN_ALL_LOGGER_BLOCKS"] is set to "yes",
|
|
74
|
+
# which is useful in testing. Block form for Logger calls allows you
|
|
75
|
+
# to skip block evaluation if the Logger level is higher than your
|
|
76
|
+
# call, and thus bugs can nest in those Logger blocks. During
|
|
77
|
+
# testing it is helpful to excercise those blocks unconditionally.
|
|
78
|
+
module NullLogger
|
|
79
|
+
extend CountingSemaphore::NullLogger
|
|
80
|
+
|
|
81
|
+
# Logs a debug message. Discards the message but may yield the block for testing.
|
|
82
|
+
#
|
|
83
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
84
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
85
|
+
def debug(message = nil, &block); end
|
|
86
|
+
|
|
87
|
+
# Logs an info message. Discards the message but may yield the block for testing.
|
|
88
|
+
#
|
|
89
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
90
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
91
|
+
def info(message = nil, &block); end
|
|
92
|
+
|
|
93
|
+
# Logs a warning message. Discards the message but may yield the block for testing.
|
|
94
|
+
#
|
|
95
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
96
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
97
|
+
def warn(message = nil, &block); end
|
|
98
|
+
|
|
99
|
+
# Logs an error message. Discards the message but may yield the block for testing.
|
|
100
|
+
#
|
|
101
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
102
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
103
|
+
def error(message = nil, &block); end
|
|
104
|
+
|
|
105
|
+
# Logs a fatal message. Discards the message but may yield the block for testing.
|
|
106
|
+
#
|
|
107
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
108
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
109
|
+
def fatal(message = nil, &block); end
|
|
110
|
+
|
|
111
|
+
# Logs a debug message. Discards the message but may yield the block for testing.
|
|
112
|
+
#
|
|
113
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
114
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
115
|
+
def self.debug(message = nil, &block); end
|
|
116
|
+
|
|
117
|
+
# Logs an info message. Discards the message but may yield the block for testing.
|
|
118
|
+
#
|
|
119
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
120
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
121
|
+
def self.info(message = nil, &block); end
|
|
122
|
+
|
|
123
|
+
# Logs a warning message. Discards the message but may yield the block for testing.
|
|
124
|
+
#
|
|
125
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
126
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
127
|
+
def self.warn(message = nil, &block); end
|
|
128
|
+
|
|
129
|
+
# Logs an error message. Discards the message but may yield the block for testing.
|
|
130
|
+
#
|
|
131
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
132
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
133
|
+
def self.error(message = nil, &block); end
|
|
134
|
+
|
|
135
|
+
# Logs a fatal message. Discards the message but may yield the block for testing.
|
|
136
|
+
#
|
|
137
|
+
# _@param_ `message` — Optional message to log (discarded)
|
|
138
|
+
sig { params(message: T.nilable(String), block: T.untyped).void }
|
|
139
|
+
def self.fatal(message = nil, &block); end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class LocalSemaphore
|
|
143
|
+
include CountingSemaphore::WithLeaseSupport
|
|
144
|
+
SLEEP_WAIT_SECONDS = T.let(0.25, T.untyped)
|
|
145
|
+
|
|
146
|
+
# Initialize the semaphore with a maximum capacity.
|
|
147
|
+
#
|
|
148
|
+
# _@param_ `capacity` — Maximum number of concurrent operations allowed (also called permits)
|
|
149
|
+
#
|
|
150
|
+
# _@param_ `logger` — the logger
|
|
151
|
+
sig { params(capacity: Integer, logger: Logger).void }
|
|
152
|
+
def initialize(capacity, logger: CountingSemaphore::NullLogger); end
|
|
153
|
+
|
|
154
|
+
# Acquires the given number of permits from this semaphore, blocking until all are available.
|
|
155
|
+
#
|
|
156
|
+
# _@param_ `permits` — Number of permits to acquire (default: 1)
|
|
157
|
+
#
|
|
158
|
+
# _@return_ — A lease object that must be passed to release()
|
|
159
|
+
sig { params(permits: Integer).returns(CountingSemaphore::Lease) }
|
|
160
|
+
def acquire(permits = 1); end
|
|
161
|
+
|
|
162
|
+
# Releases a previously acquired lease, returning the permits to the semaphore.
|
|
163
|
+
#
|
|
164
|
+
# _@param_ `lease` — The lease object returned by acquire() or try_acquire()
|
|
165
|
+
sig { params(lease: CountingSemaphore::Lease).void }
|
|
166
|
+
def release(lease); end
|
|
167
|
+
|
|
168
|
+
# Acquires the given number of permits from this semaphore, only if all are available
|
|
169
|
+
# at the time of invocation or within the timeout interval.
|
|
170
|
+
#
|
|
171
|
+
# _@param_ `permits` — Number of permits to acquire (default: 1)
|
|
172
|
+
#
|
|
173
|
+
# _@param_ `timeout` — Number of seconds to wait, or nil to return immediately (default: nil)
|
|
174
|
+
#
|
|
175
|
+
# _@return_ — A lease object if successful, nil otherwise
|
|
176
|
+
sig { params(permits: Integer, timeout: T.nilable(Numeric)).returns(T.nilable(CountingSemaphore::Lease)) }
|
|
177
|
+
def try_acquire(permits = 1, timeout: nil); end
|
|
178
|
+
|
|
179
|
+
# Returns the current number of permits available in this semaphore.
|
|
180
|
+
#
|
|
181
|
+
# _@return_ — Number of available permits
|
|
182
|
+
sig { returns(Integer) }
|
|
183
|
+
def available_permits; end
|
|
184
|
+
|
|
185
|
+
# Acquires and returns all permits that are immediately available.
|
|
186
|
+
# Returns a single lease representing all drained permits.
|
|
187
|
+
#
|
|
188
|
+
# _@return_ — A lease for all available permits, or nil if none available
|
|
189
|
+
sig { returns(T.nilable(CountingSemaphore::Lease)) }
|
|
190
|
+
def drain_permits; end
|
|
191
|
+
|
|
192
|
+
# Acquire a lease for the specified number of permits and execute the block.
|
|
193
|
+
# Blocks until sufficient resources are available.
|
|
194
|
+
# Kept for backwards compatibility - wraps acquire/release.
|
|
195
|
+
#
|
|
196
|
+
# _@param_ `permit_count` — Number of permits to acquire (default: 1)
|
|
197
|
+
#
|
|
198
|
+
# _@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.
|
|
199
|
+
#
|
|
200
|
+
# _@return_ — The result of the block
|
|
201
|
+
sig { params(permit_count: Integer, timeout: Numeric, blk: T.proc.params(lease: T.nilable(CountingSemaphore::Lease)).void).returns(T.untyped) }
|
|
202
|
+
def with_lease(permit_count = 1, timeout: 30, &blk); end
|
|
203
|
+
|
|
204
|
+
# Get the current number of permits currently acquired.
|
|
205
|
+
# Kept for backwards compatibility.
|
|
206
|
+
#
|
|
207
|
+
# _@return_ — Number of permits currently in use
|
|
208
|
+
sig { returns(Integer) }
|
|
209
|
+
def currently_leased; end
|
|
210
|
+
|
|
211
|
+
sig { returns(Integer) }
|
|
212
|
+
attr_reader :capacity
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
class RedisSemaphore
|
|
216
|
+
include CountingSemaphore::WithLeaseSupport
|
|
217
|
+
LEASE_EXPIRATION_SECONDS = T.let(5, T.untyped)
|
|
218
|
+
GET_LEASE_SCRIPT = T.let(<<~LUA, T.untyped)
|
|
219
|
+
local lease_key = KEYS[1]
|
|
220
|
+
local lease_set_key = KEYS[2]
|
|
221
|
+
local capacity = tonumber(ARGV[1])
|
|
222
|
+
local permit_count = tonumber(ARGV[2])
|
|
223
|
+
local expiration_seconds = tonumber(ARGV[3])
|
|
224
|
+
|
|
225
|
+
-- Get all active leases from the set and calculate current usage
|
|
226
|
+
local lease_keys = redis.call('SMEMBERS', lease_set_key)
|
|
227
|
+
local current_usage = 0
|
|
228
|
+
local valid_leases = {}
|
|
229
|
+
|
|
230
|
+
for i, key in ipairs(lease_keys) do
|
|
231
|
+
local permits = redis.call('GET', key)
|
|
232
|
+
if permits then
|
|
233
|
+
local permits_from_lease = tonumber(permits)
|
|
234
|
+
if permits_from_lease then
|
|
235
|
+
current_usage = current_usage + permits_from_lease
|
|
236
|
+
table.insert(valid_leases, key)
|
|
237
|
+
else
|
|
238
|
+
-- Remove lease with invalid permit count
|
|
239
|
+
redis.call('DEL', key)
|
|
240
|
+
redis.call('SREM', lease_set_key, key)
|
|
241
|
+
end
|
|
242
|
+
else
|
|
243
|
+
-- Lease key doesn't exist, remove from set
|
|
244
|
+
redis.call('SREM', lease_set_key, key)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
-- Check if we have capacity
|
|
249
|
+
local available = capacity - current_usage
|
|
250
|
+
if available >= permit_count then
|
|
251
|
+
-- Set lease with TTL (value is just the permit count)
|
|
252
|
+
redis.call('SETEX', lease_key, expiration_seconds, permit_count)
|
|
253
|
+
-- Add lease key to the set
|
|
254
|
+
redis.call('SADD', lease_set_key, lease_key)
|
|
255
|
+
-- Set TTL on the set (4x the lease TTL to ensure cleanup)
|
|
256
|
+
redis.call('EXPIRE', lease_set_key, expiration_seconds * 4)
|
|
257
|
+
|
|
258
|
+
return {1, lease_key, current_usage + permit_count}
|
|
259
|
+
else
|
|
260
|
+
return {0, '', current_usage}
|
|
261
|
+
end
|
|
262
|
+
LUA
|
|
263
|
+
GET_USAGE_SCRIPT = T.let(<<~LUA, T.untyped)
|
|
264
|
+
local lease_set_key = KEYS[1]
|
|
265
|
+
local expiration_seconds = tonumber(ARGV[1])
|
|
266
|
+
|
|
267
|
+
-- Get all active leases from the set and calculate current usage
|
|
268
|
+
local lease_keys = redis.call('SMEMBERS', lease_set_key)
|
|
269
|
+
local current_usage = 0
|
|
270
|
+
local has_valid_leases = false
|
|
271
|
+
|
|
272
|
+
for i, lease_key in ipairs(lease_keys) do
|
|
273
|
+
local permits = redis.call('GET', lease_key)
|
|
274
|
+
if permits then
|
|
275
|
+
local permits_from_lease = tonumber(permits)
|
|
276
|
+
if permits_from_lease then
|
|
277
|
+
current_usage = current_usage + permits_from_lease
|
|
278
|
+
has_valid_leases = true
|
|
279
|
+
else
|
|
280
|
+
-- Remove lease with invalid permit count
|
|
281
|
+
redis.call('DEL', lease_key)
|
|
282
|
+
redis.call('SREM', lease_set_key, lease_key)
|
|
283
|
+
end
|
|
284
|
+
else
|
|
285
|
+
-- Lease key doesn't exist, remove from set
|
|
286
|
+
redis.call('SREM', lease_set_key, lease_key)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
-- Refresh TTL on the set if there are valid leases (4x the lease TTL)
|
|
291
|
+
if has_valid_leases then
|
|
292
|
+
redis.call('EXPIRE', lease_set_key, expiration_seconds * 4)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
return current_usage
|
|
296
|
+
LUA
|
|
297
|
+
RELEASE_LEASE_SCRIPT = T.let(<<~LUA, T.untyped)
|
|
298
|
+
local lease_key = KEYS[1]
|
|
299
|
+
local queue_key = KEYS[2]
|
|
300
|
+
local lease_set_key = KEYS[3]
|
|
301
|
+
local permit_count = tonumber(ARGV[1])
|
|
302
|
+
local max_signals = tonumber(ARGV[2])
|
|
303
|
+
|
|
304
|
+
-- Remove the lease
|
|
305
|
+
redis.call('DEL', lease_key)
|
|
306
|
+
-- Remove from the lease set
|
|
307
|
+
redis.call('SREM', lease_set_key, lease_key)
|
|
308
|
+
|
|
309
|
+
-- Signal waiting clients about the released permits
|
|
310
|
+
redis.call('LPUSH', queue_key, 'permits:' .. permit_count)
|
|
311
|
+
|
|
312
|
+
-- Trim queue to prevent indefinite growth (atomic)
|
|
313
|
+
redis.call('LTRIM', queue_key, 0, max_signals - 1)
|
|
314
|
+
|
|
315
|
+
return 1
|
|
316
|
+
LUA
|
|
317
|
+
GET_LEASE_SCRIPT_SHA = T.let(Digest::SHA1.hexdigest(GET_LEASE_SCRIPT), T.untyped)
|
|
318
|
+
GET_USAGE_SCRIPT_SHA = T.let(Digest::SHA1.hexdigest(GET_USAGE_SCRIPT), T.untyped)
|
|
319
|
+
RELEASE_LEASE_SCRIPT_SHA = T.let(Digest::SHA1.hexdigest(RELEASE_LEASE_SCRIPT), T.untyped)
|
|
320
|
+
|
|
321
|
+
# sord warn - Redis wasn't able to be resolved to a constant in this project
|
|
322
|
+
# sord warn - ConnectionPool wasn't able to be resolved to a constant in this project
|
|
323
|
+
# sord omit - no YARD type given for "lease_expiration_seconds:", using untyped
|
|
324
|
+
# Initialize the semaphore with a maximum capacity and required namespace.
|
|
325
|
+
#
|
|
326
|
+
# _@param_ `capacity` — Maximum number of concurrent operations allowed (also called permits)
|
|
327
|
+
#
|
|
328
|
+
# _@param_ `namespace` — Required namespace for Redis keys
|
|
329
|
+
#
|
|
330
|
+
# _@param_ `redis` — Optional Redis client or connection pool (defaults to new Redis instance)
|
|
331
|
+
#
|
|
332
|
+
# _@param_ `logger` — the logger
|
|
333
|
+
sig do
|
|
334
|
+
params(
|
|
335
|
+
capacity: Integer,
|
|
336
|
+
namespace: String,
|
|
337
|
+
redis: T.nilable(T.any(Redis, ConnectionPool)),
|
|
338
|
+
logger: Logger,
|
|
339
|
+
lease_expiration_seconds: T.untyped
|
|
340
|
+
).void
|
|
341
|
+
end
|
|
342
|
+
def initialize(capacity, namespace, redis: nil, logger: CountingSemaphore::NullLogger, lease_expiration_seconds: LEASE_EXPIRATION_SECONDS); end
|
|
343
|
+
|
|
344
|
+
# Acquires the given number of permits from this semaphore, blocking until all are available.
|
|
345
|
+
#
|
|
346
|
+
# _@param_ `permits` — Number of permits to acquire (default: 1)
|
|
347
|
+
#
|
|
348
|
+
# _@return_ — A lease object that must be passed to release()
|
|
349
|
+
sig { params(permits: Integer).returns(CountingSemaphore::Lease) }
|
|
350
|
+
def acquire(permits = 1); end
|
|
351
|
+
|
|
352
|
+
# Releases a previously acquired lease, returning the permits to the semaphore.
|
|
353
|
+
#
|
|
354
|
+
# _@param_ `lease` — The lease object returned by acquire() or try_acquire()
|
|
355
|
+
sig { params(lease: CountingSemaphore::Lease).void }
|
|
356
|
+
def release(lease); end
|
|
357
|
+
|
|
358
|
+
# Acquires the given number of permits from this semaphore, only if all are available
|
|
359
|
+
# at the time of invocation or within the timeout interval.
|
|
360
|
+
#
|
|
361
|
+
# _@param_ `permits` — Number of permits to acquire (default: 1)
|
|
362
|
+
#
|
|
363
|
+
# _@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.
|
|
364
|
+
#
|
|
365
|
+
# _@return_ — A lease object if successful, nil otherwise
|
|
366
|
+
sig { params(permits: Integer, timeout: T.nilable(Numeric)).returns(T.nilable(CountingSemaphore::Lease)) }
|
|
367
|
+
def try_acquire(permits = 1, timeout: nil); end
|
|
368
|
+
|
|
369
|
+
# Returns the current number of permits available in this semaphore.
|
|
370
|
+
#
|
|
371
|
+
# _@return_ — Number of available permits
|
|
372
|
+
sig { returns(Integer) }
|
|
373
|
+
def available_permits; end
|
|
374
|
+
|
|
375
|
+
# Acquires and returns all permits that are immediately available.
|
|
376
|
+
# Note: For distributed semaphores, this may not be perfectly accurate due to race conditions.
|
|
377
|
+
#
|
|
378
|
+
# _@return_ — A lease for all available permits, or nil if none available
|
|
379
|
+
sig { returns(T.nilable(CountingSemaphore::Lease)) }
|
|
380
|
+
def drain_permits; end
|
|
381
|
+
|
|
382
|
+
# Returns debugging information about the current state of the semaphore.
|
|
383
|
+
# Includes current usage, capacity, available permits, and details about active leases.
|
|
384
|
+
#
|
|
385
|
+
# _@return_ — A hash containing :usage, :capacity, :available, and :active_leases
|
|
386
|
+
#
|
|
387
|
+
# ```ruby
|
|
388
|
+
# info = semaphore.debug_info
|
|
389
|
+
# puts "Usage: #{info[:usage]}/#{info[:capacity]}"
|
|
390
|
+
# info[:active_leases].each { |lease| puts "Lease: #{lease[:key]} - #{lease[:permits]} permits" }
|
|
391
|
+
# ```
|
|
392
|
+
sig { returns(T::Hash[T.untyped, T.untyped]) }
|
|
393
|
+
def debug_info; end
|
|
394
|
+
|
|
395
|
+
# sord warn - Redis wasn't able to be resolved to a constant in this project
|
|
396
|
+
# sord warn - ConnectionPool wasn't able to be resolved to a constant in this project
|
|
397
|
+
# Wraps a Redis client to support both ConnectionPool and bare Redis connections
|
|
398
|
+
#
|
|
399
|
+
# _@param_ `redis` — The Redis client or connection pool
|
|
400
|
+
#
|
|
401
|
+
# _@return_ — A wrapper that supports the `with` method
|
|
402
|
+
sig { params(redis: T.any(Redis, ConnectionPool)).returns(Object) }
|
|
403
|
+
def wrap_redis_client_with_pool(redis); end
|
|
404
|
+
|
|
405
|
+
# Executes a block with a Redis connection from the pool
|
|
406
|
+
#
|
|
407
|
+
# _@return_ — The result of the block
|
|
408
|
+
sig { params(block: T.untyped).returns(T.untyped) }
|
|
409
|
+
def with_redis(&block); end
|
|
410
|
+
|
|
411
|
+
# Executes a Redis script with automatic fallback to EVAL on NOSCRIPT error
|
|
412
|
+
#
|
|
413
|
+
# _@param_ `script_type` — The type of script (:get_lease, :release_lease, :get_usage)
|
|
414
|
+
#
|
|
415
|
+
# _@param_ `keys` — Keys for the script
|
|
416
|
+
#
|
|
417
|
+
# _@param_ `argv` — Arguments for the script
|
|
418
|
+
#
|
|
419
|
+
# _@return_ — The result of the script execution
|
|
420
|
+
sig { params(script_type: Symbol, keys: T::Array[T.untyped], argv: T::Array[T.untyped]).returns(T.untyped) }
|
|
421
|
+
def execute_script(script_type, keys: [], argv: []); end
|
|
422
|
+
|
|
423
|
+
# sord omit - no YARD type given for "permit_count", using untyped
|
|
424
|
+
# sord omit - no YARD type given for "timeout_seconds:", using untyped
|
|
425
|
+
# sord omit - no YARD return type given, using untyped
|
|
426
|
+
sig { params(permit_count: T.untyped, timeout_seconds: T.untyped).returns(T.untyped) }
|
|
427
|
+
def acquire_lease_internal(permit_count, timeout_seconds:); end
|
|
428
|
+
|
|
429
|
+
# sord omit - no YARD type given for "permit_count", using untyped
|
|
430
|
+
# sord omit - no YARD type given for "remaining_timeout", using untyped
|
|
431
|
+
# sord omit - no YARD return type given, using untyped
|
|
432
|
+
sig { params(permit_count: T.untyped, remaining_timeout: T.untyped).returns(T.untyped) }
|
|
433
|
+
def wait_for_permits(permit_count, remaining_timeout); end
|
|
434
|
+
|
|
435
|
+
# sord omit - no YARD type given for "permit_count", using untyped
|
|
436
|
+
# sord omit - no YARD return type given, using untyped
|
|
437
|
+
sig { params(permit_count: T.untyped).returns(T.untyped) }
|
|
438
|
+
def attempt_lease_acquisition(permit_count); end
|
|
439
|
+
|
|
440
|
+
# sord omit - no YARD type given for "lease_key", using untyped
|
|
441
|
+
# sord omit - no YARD type given for "permit_count", using untyped
|
|
442
|
+
# sord omit - no YARD return type given, using untyped
|
|
443
|
+
sig { params(lease_key: T.untyped, permit_count: T.untyped).returns(T.untyped) }
|
|
444
|
+
def release_lease(lease_key, permit_count); end
|
|
445
|
+
|
|
446
|
+
# sord omit - no YARD return type given, using untyped
|
|
447
|
+
sig { returns(T.untyped) }
|
|
448
|
+
def get_current_usage; end
|
|
449
|
+
|
|
450
|
+
# sord omit - no YARD return type given, using untyped
|
|
451
|
+
sig { returns(T.untyped) }
|
|
452
|
+
def generate_lease_id; end
|
|
453
|
+
|
|
454
|
+
# Acquire a lease for the specified number of permits and execute the block.
|
|
455
|
+
# Blocks until sufficient resources are available.
|
|
456
|
+
# Kept for backwards compatibility - wraps acquire/release.
|
|
457
|
+
#
|
|
458
|
+
# _@param_ `permit_count` — Number of permits to acquire (default: 1)
|
|
459
|
+
#
|
|
460
|
+
# _@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.
|
|
461
|
+
#
|
|
462
|
+
# _@return_ — The result of the block
|
|
463
|
+
sig { params(permit_count: Integer, timeout: Numeric, blk: T.proc.params(lease: T.nilable(CountingSemaphore::Lease)).void).returns(T.untyped) }
|
|
464
|
+
def with_lease(permit_count = 1, timeout: 30, &blk); end
|
|
465
|
+
|
|
466
|
+
# Get the current number of permits currently acquired.
|
|
467
|
+
# Kept for backwards compatibility.
|
|
468
|
+
#
|
|
469
|
+
# _@return_ — Number of permits currently in use
|
|
470
|
+
sig { returns(Integer) }
|
|
471
|
+
def currently_leased; end
|
|
472
|
+
|
|
473
|
+
sig { returns(Integer) }
|
|
474
|
+
attr_reader :capacity
|
|
475
|
+
|
|
476
|
+
# Null pool for bare Redis connections that don't need connection pooling.
|
|
477
|
+
# Provides a compatible interface with ConnectionPool for bare Redis instances.
|
|
478
|
+
class NullPool
|
|
479
|
+
# sord warn - Redis wasn't able to be resolved to a constant in this project
|
|
480
|
+
# Creates a new NullPool wrapper around a Redis connection.
|
|
481
|
+
#
|
|
482
|
+
# _@param_ `redis_connection` — The Redis connection to wrap
|
|
483
|
+
sig { params(redis_connection: Redis).void }
|
|
484
|
+
def initialize(redis_connection); end
|
|
485
|
+
|
|
486
|
+
# Yields the wrapped Redis connection to the block.
|
|
487
|
+
# Provides ConnectionPool-compatible interface.
|
|
488
|
+
#
|
|
489
|
+
# _@return_ — The result of the block
|
|
490
|
+
sig { params(block: T.untyped).returns(T.untyped) }
|
|
491
|
+
def with(&block); end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Module providing backwards-compatible with_lease method
|
|
496
|
+
# Requires the including class to implement: acquire, release, capacity
|
|
497
|
+
module WithLeaseSupport
|
|
498
|
+
# Acquire a lease for the specified number of permits and execute the block.
|
|
499
|
+
# Blocks until sufficient resources are available.
|
|
500
|
+
# Kept for backwards compatibility - wraps acquire/release.
|
|
501
|
+
#
|
|
502
|
+
# _@param_ `permit_count` — Number of permits to acquire (default: 1)
|
|
503
|
+
#
|
|
504
|
+
# _@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.
|
|
505
|
+
#
|
|
506
|
+
# _@return_ — The result of the block
|
|
507
|
+
sig { params(permit_count: Integer, timeout: Numeric, blk: T.proc.params(lease: T.nilable(CountingSemaphore::Lease)).void).returns(T.untyped) }
|
|
508
|
+
def with_lease(permit_count = 1, timeout: 30, &blk); end
|
|
509
|
+
|
|
510
|
+
# Get the current number of permits currently acquired.
|
|
511
|
+
# Kept for backwards compatibility.
|
|
512
|
+
#
|
|
513
|
+
# _@return_ — Number of permits currently in use
|
|
514
|
+
sig { returns(Integer) }
|
|
515
|
+
def currently_leased; end
|
|
516
|
+
end
|
|
517
|
+
end
|