async def test_reclaim_lost_messages_one(redis_client: Redis, redis_pool): """Test that messages which another consumer has timed out on can be reclaimed""" # Add a message await redis_client.xadd( "my.dummy.my_event:stream", fields={ b"api_name": b"my.dummy", b"event_name": b"my_event", b"id": b"123", b"version": b"1", b":field": b'"value"', }, ) # Create the consumer group await redis_client.xgroup_create(stream="my.dummy.my_event:stream", group_name="test_service", latest_id="0") # Claim it in the name of another consumer result = await redis_client.xread_group( group_name="test_service", consumer_name="bad_consumer", streams=["my.dummy.my_event:stream"], latest_ids=[">"], ) assert result, "Didn't actually manage to claim any message" # Sleep a moment to fake a short timeout await asyncio.sleep(0.1) event_transport = RedisEventTransport( redis_pool=redis_pool, service_name="test_service", consumer_name="good_consumer", acknowledgement_timeout=0.01, # in ms, short for the sake of testing stream_use=StreamUse.PER_EVENT, ) reclaimer = event_transport._reclaim_lost_messages( stream_names=["my.dummy.my_event:stream"], consumer_group="test_service", expected_events={"my_event"}, ) reclaimed_messages = [] async for m in reclaimer: reclaimed_messages.extend(m) for m in reclaimed_messages: await event_transport.acknowledge(m) assert len(reclaimed_messages) == 1 assert reclaimed_messages[0].native_id assert type(reclaimed_messages[0].native_id) == str result = await redis_client.xinfo_consumers("my.dummy.my_event:stream", "test_service") consumer_info = {r[b"name"]: r for r in result} assert consumer_info[b"bad_consumer"][b"pending"] == 0 assert consumer_info[b"good_consumer"][b"pending"] == 0
async def test_reclaim_lost_messages_many(loop, redis_client, redis_pool, dummy_api): """Test that messages keep getting reclaimed until there are none left""" # Add 20 messages for x in range(0, 20): await redis_client.xadd( "my.dummy.my_event:stream", fields={ b"api_name": b"my.dummy", b"event_name": b"my_event", b"id": b"123", b"version": b"1", b":field": b'"value"', }, ) # Create the consumer group await redis_client.xgroup_create(stream="my.dummy.my_event:stream", group_name="test_service", latest_id="0") # Claim them all in the name of another consumer result = await redis_client.xread_group( group_name="test_service", consumer_name="bad_consumer", streams=["my.dummy.my_event:stream"], latest_ids=[">"], count=20, ) assert result, "Didn't actually manage to claim any message" # Sleep a moment to fake a short timeout await asyncio.sleep(0.1) event_transport = RedisEventTransport( redis_pool=redis_pool, service_name="test_service", consumer_name="good_consumer", acknowledgement_timeout=0.01, # in ms, short for the sake of testing stream_use=StreamUse.PER_EVENT, reclaim_batch_size= 5, # Less than 20 (see message creation above), so multiple fetches required ) reclaimer = event_transport._reclaim_lost_messages( stream_names=["my.dummy.my_event:stream"], consumer_group="test_service", expected_events={"my_event"}, ) # We should get 20 unique IDs back (i.e. no duplicates, nothing missed) messages = chain(*[m async for m in reclaimer]) message_ids = {m.native_id for m in messages} assert len(message_ids) == 20
async def test_reclaim_lost_messages_ignores_non_timed_out_messages( redis_client, redis_pool): """Ensure messages which have not timed out are not reclaimed""" # Add a message await redis_client.xadd( "my.dummy.my_event:stream", fields={ b"api_name": b"my.dummy", b"event_name": b"my_event", b"id": b"123", b":field": b'"value"', }, ) # Create the consumer group await redis_client.xgroup_create(stream="my.dummy.my_event:stream", group_name="test_service", latest_id="0") # Claim it in the name of another consumer await redis_client.xread_group( group_name="test_service", consumer_name="bad_consumer", streams=["my.dummy.my_event:stream"], latest_ids=[">"], ) # Sleep a moment to fake a short timeout await asyncio.sleep(0.1) event_transport = RedisEventTransport( redis_pool=redis_pool, service_name="test_service", consumer_name="good_consumer", # in ms, longer as we want to check that the messages is not reclaimed acknowledgement_timeout=0.9, stream_use=StreamUse.PER_EVENT, ) reclaimer = event_transport._reclaim_lost_messages( stream_names=["my.dummy.my_event:stream"], consumer_group="test_service", expected_events={"my_event"}, ) reclaimed_messages = [m async for m in reclaimer] assert len(reclaimed_messages) == 0
async def test_reclaim_lost_messages_different_event(redis_client: Redis, redis_pool): """Test that messages which another consumer has timed out on can be reclaimed However, in this case we have a single stream for an entire API. The stream has a lost message for an event we are not listening for. In this case the event shouldn't be claimed """ # Add a message await redis_client.xadd( "my.dummy.*:stream", fields={ b"api_name": b"my.dummy", b"event_name": b"my_event", b"id": b"123", b"version": b"1", b":field": b'"value"', }, ) # Create the consumer group await redis_client.xgroup_create(stream="my.dummy.*:stream", group_name="test_service", latest_id="0") # Claim it in the name of another consumer result = await redis_client.xread_group( group_name="test_service", consumer_name="bad_consumer", streams=["my.dummy.*:stream"], latest_ids=[">"], ) assert result, "Didn't actually manage to claim any message" # Sleep a moment to fake a short timeout await asyncio.sleep(0.1) event_transport = RedisEventTransport( redis_pool=redis_pool, service_name="test_service", consumer_name="good_consumer", acknowledgement_timeout=0.01, # in ms, short for the sake of testing stream_use=StreamUse.PER_API, ) reclaimer = event_transport._reclaim_lost_messages( stream_names=["my.dummy.*:stream"], consumer_group="test_service", expected_events={"another_event" }, # NOTE: We this is NOT the event we created above ) reclaimed_messages = [] async for m in reclaimer: reclaimed_messages.extend(m) for m in reclaimed_messages: await event_transport.acknowledge(m) assert len(reclaimed_messages) == 0 result = await redis_client.xinfo_consumers("my.dummy.*:stream", "test_service") consumer_info = {r[b"name"]: r for r in result} assert consumer_info[b"bad_consumer"][b"pending"] == 0 assert consumer_info[b"good_consumer"][b"pending"] == 0