redis-allocator 0.3.3__py3-none-any.whl → 0.4.1__py3-none-any.whl
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.
- redis_allocator/_version.py +1 -1
- redis_allocator/allocator.py +56 -28
- {redis_allocator-0.3.3.dist-info → redis_allocator-0.4.1.dist-info}/METADATA +5 -5
- {redis_allocator-0.3.3.dist-info → redis_allocator-0.4.1.dist-info}/RECORD +7 -7
- {redis_allocator-0.3.3.dist-info → redis_allocator-0.4.1.dist-info}/WHEEL +0 -0
- {redis_allocator-0.3.3.dist-info → redis_allocator-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {redis_allocator-0.3.3.dist-info → redis_allocator-0.4.1.dist-info}/top_level.txt +0 -0
redis_allocator/_version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.
|
1
|
+
__version__ = '0.4.1'
|
redis_allocator/allocator.py
CHANGED
@@ -175,21 +175,42 @@ class RedisAllocatorObject(Generic[U]):
|
|
175
175
|
return self.obj.is_healthy()
|
176
176
|
return True
|
177
177
|
|
178
|
-
def
|
178
|
+
def set_healthy(self, duration: Timeout = 3600):
|
179
|
+
"""Set the object as healthy."""
|
180
|
+
if self.obj is not None and self.obj.name is not None:
|
181
|
+
self.allocator.update_soft_bind(self.obj.name, self.key, duration)
|
182
|
+
if self.allocator.shared:
|
183
|
+
self.allocator.unlock(self.key)
|
184
|
+
|
185
|
+
def set_unhealthy(self, duration: Timeout = 3600):
|
179
186
|
"""Set the object as unhealthy."""
|
180
187
|
if self.obj is not None and self.obj.name is not None:
|
181
188
|
self.allocator.unbind_soft_bind(self.obj.name)
|
182
189
|
self.allocator.update(self.key, timeout=duration)
|
183
190
|
|
184
|
-
def refresh(self, timeout: Timeout = 120):
|
191
|
+
def refresh(self, timeout: Timeout = 120, cache_timeout: Timeout = 3600):
|
185
192
|
"""Refresh the object."""
|
186
193
|
self.close()
|
187
194
|
new_obj = self.allocator.policy.malloc(self.allocator, timeout=timeout,
|
188
|
-
obj=self.obj, params=self.params
|
195
|
+
obj=self.obj, params=self.params,
|
196
|
+
cache_timeout=cache_timeout)
|
189
197
|
if new_obj is not None:
|
190
198
|
self.obj = new_obj.obj
|
191
199
|
self.key = new_obj.key
|
192
200
|
self.params = new_obj.params
|
201
|
+
self.open()
|
202
|
+
|
203
|
+
def refresh_until_healthy(self, timeout: Timeout = 120, max_attempts: int = 10, lock_duration: Timeout = 3600, cache_timeout: Timeout = 3600):
|
204
|
+
"""Refresh the object until it is healthy."""
|
205
|
+
for _ in range(max_attempts):
|
206
|
+
try:
|
207
|
+
if self.is_healthy():
|
208
|
+
return
|
209
|
+
except Exception as e:
|
210
|
+
logger.error(f"Error checking health of {self.key}: {e}")
|
211
|
+
self.set_unhealthy(lock_duration)
|
212
|
+
self.refresh(timeout, cache_timeout)
|
213
|
+
raise RuntimeError("the objects is still unhealthy after %d attempts", max_attempts)
|
193
214
|
|
194
215
|
@property
|
195
216
|
def unique_id(self) -> str:
|
@@ -236,14 +257,14 @@ class RedisAllocatorUpdater:
|
|
236
257
|
return len(self.params)
|
237
258
|
|
238
259
|
|
239
|
-
class RedisAllocatorPolicy(ABC):
|
260
|
+
class RedisAllocatorPolicy(ABC, Generic[U]):
|
240
261
|
"""Abstract base class for Redis allocator policies.
|
241
262
|
|
242
263
|
This class defines the interface for allocation policies that can be used
|
243
264
|
with RedisAllocator to control allocation behavior.
|
244
265
|
"""
|
245
266
|
|
246
|
-
def initialize(self, allocator: 'RedisAllocator'):
|
267
|
+
def initialize(self, allocator: 'RedisAllocator[U]'):
|
247
268
|
"""Initialize the policy with an allocator instance.
|
248
269
|
|
249
270
|
Args:
|
@@ -252,9 +273,9 @@ class RedisAllocatorPolicy(ABC):
|
|
252
273
|
pass
|
253
274
|
|
254
275
|
@abstractmethod
|
255
|
-
def malloc(self, allocator: 'RedisAllocator', timeout: Timeout = 120,
|
256
|
-
obj: Optional[
|
257
|
-
cache_timeout: Timeout = 3600) -> Optional[RedisAllocatorObject]:
|
276
|
+
def malloc(self, allocator: 'RedisAllocator[U]', timeout: Timeout = 120,
|
277
|
+
obj: Optional[U] = None, params: Optional[dict] = None,
|
278
|
+
cache_timeout: Timeout = 3600) -> Optional[RedisAllocatorObject[U]]:
|
258
279
|
"""Allocate a resource according to the policy.
|
259
280
|
|
260
281
|
Args:
|
@@ -271,7 +292,7 @@ class RedisAllocatorPolicy(ABC):
|
|
271
292
|
pass
|
272
293
|
|
273
294
|
@abstractmethod
|
274
|
-
def refresh_pool(self, allocator: 'RedisAllocator'):
|
295
|
+
def refresh_pool(self, allocator: 'RedisAllocator[U]'):
|
275
296
|
"""Refresh the allocation pool.
|
276
297
|
|
277
298
|
This method is called periodically to update the pool with new resources.
|
@@ -281,7 +302,7 @@ class RedisAllocatorPolicy(ABC):
|
|
281
302
|
"""
|
282
303
|
pass
|
283
304
|
|
284
|
-
def check_health_once(self, r_obj: RedisAllocatorObject, duration: int = 3600) -> bool:
|
305
|
+
def check_health_once(self, r_obj: RedisAllocatorObject[U], duration: int = 3600) -> bool:
|
285
306
|
"""Check the health of the object."""
|
286
307
|
with contextlib.closing(r_obj.open()):
|
287
308
|
try:
|
@@ -297,8 +318,8 @@ class RedisAllocatorPolicy(ABC):
|
|
297
318
|
r_obj.set_unhealthy(duration)
|
298
319
|
raise
|
299
320
|
|
300
|
-
def check_health(self, allocator: 'RedisAllocator', lock_duration: Timeout = 3600, max_threads: int = 8,
|
301
|
-
obj_fn: Optional[Callable[[str],
|
321
|
+
def check_health(self, allocator: 'RedisAllocator[U]', lock_duration: Timeout = 3600, max_threads: int = 8,
|
322
|
+
obj_fn: Optional[Callable[[str], U]] = None,
|
302
323
|
params_fn: Optional[Callable[[str], dict]] = None) -> tuple[int, int]:
|
303
324
|
"""Check the health of the allocator.
|
304
325
|
|
@@ -311,6 +332,7 @@ class RedisAllocatorPolicy(ABC):
|
|
311
332
|
A tuple containing the number of healthy and unhealthy items in the allocator
|
312
333
|
"""
|
313
334
|
with ThreadPoolExecutor(max_workers=max_threads) as executor:
|
335
|
+
inputs = []
|
314
336
|
for key in allocator.keys():
|
315
337
|
if params_fn is not None:
|
316
338
|
params = params_fn(key)
|
@@ -320,12 +342,14 @@ class RedisAllocatorPolicy(ABC):
|
|
320
342
|
obj = obj_fn(key)
|
321
343
|
else:
|
322
344
|
obj = None
|
323
|
-
|
324
|
-
|
325
|
-
|
345
|
+
inputs.append(RedisAllocatorObject(allocator, key, obj, params))
|
346
|
+
results = list(executor.map(self.check_health_once, inputs, timeout=max_threads * lock_duration / (len(inputs) + 1)))
|
347
|
+
healthy = sum(results)
|
348
|
+
unhealthy = len(results) - healthy
|
349
|
+
return healthy, unhealthy
|
326
350
|
|
327
351
|
|
328
|
-
class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
352
|
+
class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy[U]):
|
329
353
|
"""Default implementation of RedisAllocatorPolicy.
|
330
354
|
|
331
355
|
This policy provides the following features:
|
@@ -349,7 +373,8 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
349
373
|
"""
|
350
374
|
|
351
375
|
def __init__(self, gc_count: int = 5, update_interval: int = 300,
|
352
|
-
expiry_duration: int = -1, updater: Optional[RedisAllocatorUpdater] = None
|
376
|
+
expiry_duration: int = -1, updater: Optional[RedisAllocatorUpdater] = None,
|
377
|
+
auto_close: bool = False):
|
353
378
|
"""Initialize the default allocation policy.
|
354
379
|
|
355
380
|
Args:
|
@@ -357,6 +382,7 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
357
382
|
update_interval: Interval in seconds between pool updates
|
358
383
|
expiry_duration: Default timeout for pool items (-1 means no timeout)
|
359
384
|
updater: Optional updater for refreshing the pool's keys
|
385
|
+
auto_close: If True, the allocator will automatically close the object when it is not unique
|
360
386
|
"""
|
361
387
|
self.gc_count = gc_count
|
362
388
|
self.update_interval: float = update_interval
|
@@ -365,8 +391,9 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
365
391
|
self._allocator: Optional[weakref.ReferenceType['RedisAllocator']] = None
|
366
392
|
self._update_lock_key: Optional[str] = None
|
367
393
|
self.objects: weakref.WeakValueDictionary[str, RedisAllocatorObject] = weakref.WeakValueDictionary()
|
394
|
+
self.auto_close = auto_close
|
368
395
|
|
369
|
-
def initialize(self, allocator: 'RedisAllocator'):
|
396
|
+
def initialize(self, allocator: 'RedisAllocator[U]'):
|
370
397
|
"""Initialize the policy with an allocator instance.
|
371
398
|
|
372
399
|
Args:
|
@@ -376,9 +403,9 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
376
403
|
self._update_lock_key = f"{allocator._pool_str()}|policy_update_lock"
|
377
404
|
atexit.register(lambda: self.finalize(self._allocator()))
|
378
405
|
|
379
|
-
def malloc(self, allocator: 'RedisAllocator', timeout: Timeout = 120,
|
380
|
-
obj: Optional[
|
381
|
-
cache_timeout: Timeout = 3600) -> Optional[RedisAllocatorObject]:
|
406
|
+
def malloc(self, allocator: 'RedisAllocator[U]', timeout: Timeout = 120,
|
407
|
+
obj: Optional[U] = None, params: Optional[dict] = None,
|
408
|
+
cache_timeout: Timeout = 3600) -> Optional[RedisAllocatorObject[U]]:
|
382
409
|
"""Allocate a resource according to the policy.
|
383
410
|
|
384
411
|
This implementation:
|
@@ -410,14 +437,15 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
410
437
|
key = allocator.malloc_key(timeout, obj_name,
|
411
438
|
cache_timeout=cache_timeout)
|
412
439
|
alloc_obj = RedisAllocatorObject(allocator, key, obj, params)
|
413
|
-
|
414
|
-
|
415
|
-
old_obj
|
440
|
+
if self.auto_close:
|
441
|
+
old_obj = self.objects.get(alloc_obj.unique_id, None)
|
442
|
+
if old_obj is not None:
|
443
|
+
old_obj.close()
|
416
444
|
self.objects[alloc_obj.unique_id] = alloc_obj
|
417
445
|
return alloc_obj
|
418
446
|
|
419
447
|
@cached(TTLCache(maxsize=64, ttl=5))
|
420
|
-
def _try_refresh_pool(self, allocator: 'RedisAllocator'):
|
448
|
+
def _try_refresh_pool(self, allocator: 'RedisAllocator[U]'):
|
421
449
|
"""Try to refresh the pool if necessary and if we can acquire the lock.
|
422
450
|
|
423
451
|
Args:
|
@@ -429,7 +457,7 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
429
457
|
# If we got here, we acquired the lock, so we can update the pool
|
430
458
|
self.refresh_pool(allocator)
|
431
459
|
|
432
|
-
def refresh_pool(self, allocator: 'RedisAllocator'):
|
460
|
+
def refresh_pool(self, allocator: 'RedisAllocator[U]'):
|
433
461
|
"""Refresh the allocation pool using the updater.
|
434
462
|
|
435
463
|
Args:
|
@@ -450,7 +478,7 @@ class DefaultRedisAllocatorPolicy(RedisAllocatorPolicy):
|
|
450
478
|
else:
|
451
479
|
allocator.extend(keys, timeout=self.expiry_duration)
|
452
480
|
|
453
|
-
def finalize(self, allocator: 'RedisAllocator'):
|
481
|
+
def finalize(self, allocator: 'RedisAllocator[U]'):
|
454
482
|
"""Finalize the policy."""
|
455
483
|
for obj in self.objects.values():
|
456
484
|
obj.close()
|
@@ -493,7 +521,7 @@ class RedisAllocator(RedisLockPool, Generic[U]):
|
|
493
521
|
"""
|
494
522
|
|
495
523
|
def __init__(self, redis: Redis, prefix: str, suffix='allocator', eps=1e-6,
|
496
|
-
shared=False, policy: Optional[RedisAllocatorPolicy] = None):
|
524
|
+
shared=False, policy: Optional[RedisAllocatorPolicy[U]] = None):
|
497
525
|
"""Initializes the RedisAllocator.
|
498
526
|
|
499
527
|
Args:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: redis-allocator
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.1
|
4
4
|
Summary: Redis-based resource allocation system.
|
5
5
|
Home-page: https://github.com/invoker-bot/RedisAllocator-python
|
6
6
|
Author: Invoker Bot
|
@@ -230,7 +230,7 @@ if allocated_obj:
|
|
230
230
|
|
231
231
|
# Using soft binding (associates a name with a resource)
|
232
232
|
allocator.update_soft_bind("worker-1", "resource-1")
|
233
|
-
#
|
233
|
+
# Unbind soft binding
|
234
234
|
allocator.unbind_soft_bind("worker-1")
|
235
235
|
|
236
236
|
# Garbage collection (reclaims unused resources)
|
@@ -291,9 +291,9 @@ graph TD
|
|
291
291
|
|
292
292
|
subgraph "PoolHash Contents (Doubly-Linked Free List)"
|
293
293
|
Key1 --> Key2["Key2: Key1||Key3||Expiry"]
|
294
|
-
Key2 --> Key3["Key3: Key2
|
295
|
-
Key3 -->
|
296
|
-
KeyN_1[
|
294
|
+
Key2 --> Key3["Key3: Key2||Key4||Expiry"]
|
295
|
+
Key3 --> Key4["Key4: Key3||Key5||Expiry"]
|
296
|
+
KeyN_1["KeyN: KeyN-1||""||Expiry"] --> KeyN
|
297
297
|
end
|
298
298
|
|
299
299
|
subgraph "Allocated Keys (Non-Shared Mode)"
|
@@ -1,15 +1,15 @@
|
|
1
1
|
redis_allocator/__init__.py,sha256=TVjUm-8YEu_MQD_PkfeIKiVknpCJBrUY9cWN1LlaZcU,1016
|
2
|
-
redis_allocator/_version.py,sha256=
|
3
|
-
redis_allocator/allocator.py,sha256=
|
2
|
+
redis_allocator/_version.py,sha256=qBs4HqYsPn6yUHWEuCN4rGUC05gABbl800d8LBd8h9w,22
|
3
|
+
redis_allocator/allocator.py,sha256=AerjK6B5-qaAsMn_ivJjRSn4GXWop27x4UDc1rlKP34,48778
|
4
4
|
redis_allocator/lock.py,sha256=fqf6WUWHKYenEArWopMIF6kWEnDfADC-bZvnQImsQVo,27400
|
5
5
|
redis_allocator/task_queue.py,sha256=8DjNr2uxhzCsHatV_CHOeGh7_K9pqQZFApSbe2blRO0,14989
|
6
|
-
redis_allocator-0.
|
6
|
+
redis_allocator-0.4.1.dist-info/licenses/LICENSE,sha256=Wt4X1rHpffQfEiyWcDUx8BMLjXxfPqaiYZ7Lgsj7L4c,1068
|
7
7
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
tests/conftest.py,sha256=Ts82uylQSzP_GcaN0E02o3xcFdjw20cXNzh3RAdYKW4,3967
|
9
9
|
tests/test_allocator.py,sha256=hFKgLe_yONtEjjm6zssUnhK0tzihG_1xZMziztHmqqA,22404
|
10
10
|
tests/test_lock.py,sha256=MDMRNN46VhWqkHUIhYOMEDgZkFFCW_WjwRLTOjkFF-Q,46952
|
11
11
|
tests/test_task_queue.py,sha256=Fh5naikFajfOvL6GngEy_TPfOYCYZolZfVwtR6T4dTY,31710
|
12
|
-
redis_allocator-0.
|
13
|
-
redis_allocator-0.
|
14
|
-
redis_allocator-0.
|
15
|
-
redis_allocator-0.
|
12
|
+
redis_allocator-0.4.1.dist-info/METADATA,sha256=K0lLGpDplwpwCMMuzUiA8CkkRfTPAIyswx4anbo0O-Y,21727
|
13
|
+
redis_allocator-0.4.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
14
|
+
redis_allocator-0.4.1.dist-info/top_level.txt,sha256=0hXzU7sK5FCeSolTEYxThOt3HOybnwaXv1FLRJvHVgI,22
|
15
|
+
redis_allocator-0.4.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|