@@ -208,11 +208,18 @@ async def read_response(
208
208
class PythonParser (BaseParser ):
209
209
"""Plain Python parsing class"""
210
210
211
- __slots__ = BaseParser .__slots__ + ("encoder" ,)
211
+ __slots__ = BaseParser .__slots__ + ("encoder" , "_buffer" , "_pos" , "_chunks" )
212
212
213
213
def __init__ (self , socket_read_size : int ):
214
214
super ().__init__ (socket_read_size )
215
215
self .encoder : Optional [Encoder ] = None
216
+ self ._buffer = b""
217
+ self ._chunks = []
218
+ self ._pos = 0
219
+
220
+ def _clear (self ):
221
+ self ._buffer = b""
222
+ self ._chunks .clear ()
216
223
217
224
def on_connect (self , connection : "Connection" ):
218
225
"""Called when the stream connects"""
@@ -227,8 +234,11 @@ def on_disconnect(self):
227
234
if self ._stream is not None :
228
235
self ._stream = None
229
236
self .encoder = None
237
+ self ._clear ()
230
238
231
239
async def can_read_destructive (self ) -> bool :
240
+ if self ._buffer :
241
+ return True
232
242
if self ._stream is None :
233
243
raise RedisError ("Buffer is closed." )
234
244
try :
@@ -237,14 +247,23 @@ async def can_read_destructive(self) -> bool:
237
247
except asyncio .TimeoutError :
238
248
return False
239
249
240
- async def read_response (
250
+ async def read_response (self , disable_decoding : bool = False ):
251
+ if self ._chunks :
252
+ # augment parsing buffer with previously read data
253
+ self ._buffer += b"" .join (self ._chunks )
254
+ self ._chunks .clear ()
255
+ self ._pos = 0
256
+ response = await self ._read_response (disable_decoding = disable_decoding )
257
+ # Successfully parsing a response allows us to clear our parsing buffer
258
+ self ._clear ()
259
+ return response
260
+
261
+ async def _read_response (
241
262
self , disable_decoding : bool = False
242
263
) -> Union [EncodableT , ResponseError , None ]:
243
264
if not self ._stream or not self .encoder :
244
265
raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
245
266
raw = await self ._readline ()
246
- if not raw :
247
- raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
248
267
response : Any
249
268
byte , response = raw [:1 ], raw [1 :]
250
269
@@ -258,6 +277,7 @@ async def read_response(
258
277
# if the error is a ConnectionError, raise immediately so the user
259
278
# is notified
260
279
if isinstance (error , ConnectionError ):
280
+ self ._clear () # Successful parse
261
281
raise error
262
282
# otherwise, we're dealing with a ResponseError that might belong
263
283
# inside a pipeline response. the connection's read_response()
@@ -282,7 +302,7 @@ async def read_response(
282
302
if length == - 1 :
283
303
return None
284
304
response = [
285
- (await self .read_response (disable_decoding )) for _ in range (length )
305
+ (await self ._read_response (disable_decoding )) for _ in range (length )
286
306
]
287
307
if isinstance (response , bytes ) and disable_decoding is False :
288
308
response = self .encoder .decode (response )
@@ -293,25 +313,38 @@ async def _read(self, length: int) -> bytes:
293
313
Read `length` bytes of data. These are assumed to be followed
294
314
by a '\r \n ' terminator which is subsequently discarded.
295
315
"""
296
- if self ._stream is None :
297
- raise RedisError ("Buffer is closed." )
298
- try :
299
- data = await self ._stream .readexactly (length + 2 )
300
- except asyncio .IncompleteReadError as error :
301
- raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR ) from error
302
- return data [:- 2 ]
316
+ want = length + 2
317
+ end = self ._pos + want
318
+ if len (self ._buffer ) >= end :
319
+ result = self ._buffer [self ._pos : end - 2 ]
320
+ else :
321
+ tail = self ._buffer [self ._pos :]
322
+ try :
323
+ data = await self ._stream .readexactly (want - len (tail ))
324
+ except asyncio .IncompleteReadError as error :
325
+ raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR ) from error
326
+ result = (tail + data )[:- 2 ]
327
+ self ._chunks .append (data )
328
+ self ._pos += want
329
+ return result
303
330
304
331
async def _readline (self ) -> bytes :
305
332
"""
306
333
read an unknown number of bytes up to the next '\r \n '
307
334
line separator, which is discarded.
308
335
"""
309
- if self ._stream is None :
310
- raise RedisError ("Buffer is closed." )
311
- data = await self ._stream .readline ()
312
- if not data .endswith (b"\r \n " ):
313
- raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
314
- return data [:- 2 ]
336
+ found = self ._buffer .find (b"\r \n " , self ._pos )
337
+ if found >= 0 :
338
+ result = self ._buffer [self ._pos : found ]
339
+ else :
340
+ tail = self ._buffer [self ._pos :]
341
+ data = await self ._stream .readline ()
342
+ if not data .endswith (b"\r \n " ):
343
+ raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
344
+ result = (tail + data )[:- 2 ]
345
+ self ._chunks .append (data )
346
+ self ._pos += len (result ) + 2
347
+ return result
315
348
316
349
317
350
class HiredisParser (BaseParser ):
0 commit comments