88
99import grpc
1010
11- from .. import _apis , issues
11+ import ydb
12+ from .. import _apis , issues , RetrySettings
1213from ..aio import Driver
13- from ..issues import Error as YdbError
14+ from ..issues import (
15+ Error as YdbError ,
16+ _process_response
17+ )
1418from .datatypes import PartitionSession , PublicMessage , PublicBatch
1519from .topic_reader import PublicReaderSettings
1620from .._topic_wrapper .common import TokenGetterFuncType , IGrpcWrapperAsyncIO , SupportedDriverType , GrpcWrapperAsyncIO
1721from .._topic_wrapper .reader import StreamReadMessage
22+ from .._errors import check_retriable_error
1823
1924
2025class TopicReaderError (YdbError ):
@@ -48,22 +53,42 @@ class ReaderReconnector:
4853
4954 _state_changed : asyncio .Event
5055 _stream_reader : Optional ["ReaderStream" ]
56+ _first_error : asyncio .Future [ydb .Error ]
5157
5258 def __init__ (self , driver : Driver , settings : PublicReaderSettings ):
5359 self ._settings = settings
5460 self ._driver = driver
5561 self ._background_tasks = set ()
62+ self ._retry_settins = RetrySettings (idempotent = True ) # get from settings
5663
5764 self ._state_changed = asyncio .Event ()
5865 self ._stream_reader = None
59- self ._background_tasks .add (asyncio .create_task (self .start ()))
66+ self ._background_tasks .add (asyncio .create_task (self ._connection_loop ()))
67+ self ._first_error = asyncio .get_running_loop ().create_future ()
6068
61- async def start (self ):
62- self ._stream_reader = await ReaderStream .create (self ._driver , self ._settings )
63- self ._state_changed .set ()
69+ async def _connection_loop (self ):
70+ attempt = 0
71+ while True :
72+ try :
73+ self ._stream_reader = await ReaderStream .create (self ._driver , self ._settings )
74+ self ._state_changed .set ()
75+ self ._stream_reader ._state_changed .wait ()
76+ except Exception as err :
77+ # todo reset attempts when connection established
78+
79+ retry_info = check_retriable_error (err , self ._settings ._retry_settings (), attempt )
80+ if not retry_info .is_retriable :
81+ self ._set_first_error (err )
82+ return
83+ await asyncio .sleep (retry_info .sleep_timeout_seconds )
84+
85+ attempt += 1
6486
6587 async def wait_message (self ):
6688 while True :
89+ if self ._first_error .done ():
90+ raise self ._first_error .result ()
91+
6792 if self ._stream_reader is not None :
6893 await self ._stream_reader .wait_messages ()
6994 return
@@ -81,6 +106,13 @@ async def close(self):
81106
82107 await asyncio .wait (self ._background_tasks )
83108
109+ def _set_first_error (self , err : issues .Error ):
110+ try :
111+ self ._first_error .set_result (err )
112+ self ._state_changed .set ()
113+ except asyncio .InvalidStateError :
114+ # skip if already has result
115+ pass
84116
85117class ReaderStream :
86118 _token_getter : Optional [TokenGetterFuncType ]
@@ -93,8 +125,8 @@ class ReaderStream:
93125
94126 _state_changed : asyncio .Event
95127 _closed : bool
96- _first_error : Optional [YdbError ]
97128 _message_batches : typing .Deque [PublicBatch ]
129+ first_error : asyncio .Future [YdbError ]
98130
99131 def __init__ (self , settings : PublicReaderSettings ):
100132 self ._token_getter = settings ._token_getter
@@ -107,7 +139,7 @@ def __init__(self, settings: PublicReaderSettings):
107139
108140 self ._state_changed = asyncio .Event ()
109141 self ._closed = False
110- self ._first_error = None
142+ self .first_error = asyncio . get_running_loop (). create_future ()
111143 self ._message_batches = deque ()
112144
113145 @staticmethod
@@ -144,8 +176,8 @@ async def _start(self, stream: IGrpcWrapperAsyncIO, init_message: StreamReadMess
144176
145177 async def wait_messages (self ):
146178 while True :
147- if self ._first_error is not None :
148- raise self ._first_error
179+ if self ._get_first_error () is not None :
180+ raise self ._get_first_error ()
149181
150182 if len (self ._message_batches ) > 0 :
151183 return
@@ -154,8 +186,8 @@ async def wait_messages(self):
154186 self ._state_changed .clear ()
155187
156188 def receive_batch_nowait (self ):
157- if self ._first_error is not None :
158- raise self ._first_error
189+ if self ._get_first_error () is not None :
190+ raise self ._get_first_error ()
159191
160192 try :
161193 batch = self ._message_batches .popleft ()
@@ -173,6 +205,7 @@ async def _read_messages_loop(self, stream: IGrpcWrapperAsyncIO):
173205 ))
174206 while True :
175207 message = await stream .receive () # type: StreamReadMessage.FromServer
208+ _process_response (message .server_status )
176209 if isinstance (message .server_message , StreamReadMessage .ReadResponse ):
177210 self ._on_read_response (message .server_message )
178211 elif isinstance (message .server_message , StreamReadMessage .StartPartitionSessionRequest ):
@@ -285,9 +318,18 @@ def _read_response_to_batches(self, message: StreamReadMessage.ReadResponse) ->
285318 return batches
286319
287320 def _set_first_error (self , err ):
288- if self . _first_error is None :
289- self ._first_error = err
321+ try :
322+ self .first_error . set_result ( err )
290323 self ._state_changed .set ()
324+ except asyncio .InvalidStateError :
325+ # skip later set errors
326+ pass
327+
328+ def _get_first_error (self ):
329+ if self .first_error .done ():
330+ return self .first_error .result ()
331+ else :
332+ return None
291333
292334 async def close (self ):
293335 if self ._closed :
0 commit comments