@@ -1566,3 +1566,60 @@ async def stream_create(
15661566
15671567 reader_stream_mock_with_error .wait_error .assert_any_await ()
15681568 reader_stream_mock_with_error .wait_messages .assert_any_await ()
1569+
1570+ async def test_reconnect_on_connection_lost (self , monkeypatch ):
1571+ """Test that ConnectionLost (from gRPC CancelledError) is treated as retriable.
1572+
1573+ This tests the fix for issue #735 - gRPC wrapper converts CancelledError to
1574+ ConnectionLost, which should cause reconnection, not permanent failure.
1575+ """
1576+
1577+ async def wait_error_with_connection_lost ():
1578+ raise issues .ConnectionLost ("gRPC stream cancelled" )
1579+
1580+ reader_stream_mock_with_error = mock .Mock (ReaderStream )
1581+ reader_stream_mock_with_error ._id = 0
1582+ reader_stream_mock_with_error .wait_error = mock .AsyncMock (side_effect = wait_error_with_connection_lost )
1583+ reader_stream_mock_with_error .close = mock .AsyncMock ()
1584+
1585+ # First stream's wait_messages should also fail (simulating connection issue)
1586+ async def wait_messages_with_error ():
1587+ raise issues .ConnectionLost ("connection lost" )
1588+
1589+ reader_stream_mock_with_error .wait_messages = mock .AsyncMock (side_effect = wait_messages_with_error )
1590+
1591+ async def wait_forever ():
1592+ f = asyncio .Future ()
1593+ await f
1594+
1595+ reader_stream_with_messages = mock .Mock (ReaderStream )
1596+ reader_stream_with_messages ._id = 1
1597+ reader_stream_with_messages .wait_error = mock .AsyncMock (side_effect = wait_forever )
1598+ reader_stream_with_messages .wait_messages .return_value = None
1599+ reader_stream_with_messages .close = mock .AsyncMock ()
1600+
1601+ stream_index = 0
1602+
1603+ async def stream_create (
1604+ reader_reconnector_id : int ,
1605+ driver : SupportedDriverType ,
1606+ settings : PublicReaderSettings ,
1607+ ):
1608+ nonlocal stream_index
1609+ stream_index += 1
1610+ if stream_index == 1 :
1611+ return reader_stream_mock_with_error
1612+ elif stream_index == 2 :
1613+ return reader_stream_with_messages
1614+ else :
1615+ raise Exception ("unexpected create stream" )
1616+
1617+ with mock .patch .object (ReaderStream , "create" , stream_create ):
1618+ reconnector = ReaderReconnector (mock .Mock (), PublicReaderSettings ("" , "" ))
1619+ # This would hang/fail before the fix because gRPC errors weren't retriable
1620+ await asyncio .wait_for (reconnector .wait_message (), timeout = 5 )
1621+ await reconnector .close (flush = False )
1622+
1623+ # Verify that reconnection happened (ConnectionLost from wait_error triggered reconnect)
1624+ reader_stream_mock_with_error .wait_error .assert_any_await ()
1625+ assert stream_index == 2 , "Should have created second stream after ConnectionLost"
0 commit comments