Periodically renews a Redis task lock to prevent expiry during long-running tasks. Use as a context manager after acquiring a lock: if acquire_task_lock("my_task", task_id): with TaskLockRenewer("my_task", task_id): # ... long-running work ... re
| 321 | |
| 322 | |
| 323 | class TaskLockRenewer: |
| 324 | """Periodically renews a Redis task lock to prevent expiry during long-running tasks. |
| 325 | |
| 326 | Use as a context manager after acquiring a lock: |
| 327 | |
| 328 | if acquire_task_lock("my_task", task_id): |
| 329 | with TaskLockRenewer("my_task", task_id): |
| 330 | # ... long-running work ... |
| 331 | release_task_lock("my_task", task_id) |
| 332 | |
| 333 | A daemon thread extends the lock TTL at regular intervals so that |
| 334 | slow downloads or large parsing jobs don't lose their lock mid-operation. |
| 335 | """ |
| 336 | |
| 337 | def __init__(self, task_name, id, ttl=300, renewal_interval=120): |
| 338 | self.task_name = task_name |
| 339 | self.id = id |
| 340 | self.ttl = ttl |
| 341 | self.renewal_interval = renewal_interval |
| 342 | self.lock_id = f"task_lock_{task_name}_{id}" |
| 343 | self._stop_event = threading.Event() |
| 344 | self._thread = None |
| 345 | |
| 346 | def _renew_loop(self): |
| 347 | """Background loop that extends the lock TTL until stopped.""" |
| 348 | while not self._stop_event.wait(self.renewal_interval): |
| 349 | try: |
| 350 | redis_client = RedisClient.get_client() |
| 351 | if redis_client.exists(self.lock_id): |
| 352 | redis_client.expire(self.lock_id, self.ttl) |
| 353 | logger.debug( |
| 354 | f"Renewed lock {self.lock_id} TTL to {self.ttl}s" |
| 355 | ) |
| 356 | else: |
| 357 | # Lock was deleted externally (e.g. manual release) — stop renewing |
| 358 | logger.warning( |
| 359 | f"Lock {self.lock_id} no longer exists, stopping renewal" |
| 360 | ) |
| 361 | break |
| 362 | except Exception as e: |
| 363 | logger.error(f"Error renewing lock {self.lock_id}: {e}") |
| 364 | |
| 365 | def start(self): |
| 366 | """Start the background renewal thread.""" |
| 367 | self._stop_event.clear() |
| 368 | self._thread = threading.Thread( |
| 369 | target=self._renew_loop, daemon=True, |
| 370 | name=f"lock-renew-{self.task_name}-{self.id}" |
| 371 | ) |
| 372 | self._thread.start() |
| 373 | return self |
| 374 | |
| 375 | def stop(self): |
| 376 | """Stop the renewal thread.""" |
| 377 | self._stop_event.set() |
| 378 | if self._thread and self._thread.is_alive(): |
| 379 | self._thread.join(timeout=5) |
| 380 | self._thread = None |
no outgoing calls
no test coverage detected