@@ -514,6 +514,39 @@ async def _raising_context():
514514 assert await captured_semaphore .current_count () == 0
515515
516516
517+ async def test_semaphore_context_manager_lost_renewal (
518+ redis_client_sdk : RedisClientSDK ,
519+ semaphore_name : str ,
520+ with_short_default_semaphore_ttl : datetime .timedelta ,
521+ ):
522+ with pytest .raises (SemaphoreLostError ): # noqa: PT012
523+ async with distributed_semaphore (
524+ redis_client = redis_client_sdk ,
525+ key = semaphore_name ,
526+ capacity = 1 ,
527+ ttl = with_short_default_semaphore_ttl ,
528+ ) as semaphore :
529+ assert await semaphore .is_acquired () is True
530+ assert await semaphore .current_count () == 1
531+ assert await semaphore .available_tokens () == 0
532+ await _assert_semaphore_redis_state (
533+ redis_client_sdk ,
534+ semaphore ,
535+ expected_count = 1 ,
536+ expected_free_tokens = 0 ,
537+ )
538+
539+ # now simulate lost renewal by deleting the holder key
540+ await redis_client_sdk .redis .delete (semaphore .holder_key )
541+ # wait a bit to let the auto-renewal task detect the lost lock
542+ # the sleep will be interrupted by the exception and the context manager will exit
543+ with pytest .raises (asyncio .CancelledError ):
544+ await asyncio .sleep (
545+ with_short_default_semaphore_ttl .total_seconds () + 0.5
546+ )
547+ raise asyncio .CancelledError
548+
549+
517550async def test_multiple_semaphores_different_keys (
518551 redis_client_sdk : RedisClientSDK ,
519552 faker : Faker ,
0 commit comments