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.
@@ -1 +1 @@
1
- __version__ = '0.3.3'
1
+ __version__ = '0.4.1'
@@ -175,21 +175,42 @@ class RedisAllocatorObject(Generic[U]):
175
175
  return self.obj.is_healthy()
176
176
  return True
177
177
 
178
- def set_unhealthy(self, duration: int = 3600):
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[Any] = None, params: Optional[dict] = None,
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], Any]] = None,
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
- alloc_obj = RedisAllocatorObject(allocator, key, obj, params)
324
- executor.submit(self.check_health_once, alloc_obj, lock_duration)
325
- executor.shutdown(wait=True)
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[Any] = None, params: Optional[dict] = None,
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
- old_obj = self.objects.get(alloc_obj.unique_id, None)
414
- if old_obj is not None:
415
- old_obj.close()
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.3
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
- # Later...
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||...||Expiry"]
295
- Key3 --> ...
296
- KeyN_1[...] --> KeyN
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=on4Bj4mGEjEjyUkQExxprhOoEopcv_sPapsEnNxyelE,22
3
- redis_allocator/allocator.py,sha256=oFJSRAhFJsBbeho5TibY81_3Vy07glrAuEhpIOYfeJI,47282
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.3.3.dist-info/licenses/LICENSE,sha256=Wt4X1rHpffQfEiyWcDUx8BMLjXxfPqaiYZ7Lgsj7L4c,1068
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.3.3.dist-info/METADATA,sha256=oEPcsqP1oiKP_HaZgIgfEEqIxuY6c9aoapC5ecLrP8w,21653
13
- redis_allocator-0.3.3.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
14
- redis_allocator-0.3.3.dist-info/top_level.txt,sha256=0hXzU7sK5FCeSolTEYxThOt3HOybnwaXv1FLRJvHVgI,22
15
- redis_allocator-0.3.3.dist-info/RECORD,,
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,,