2
2
import copy
3
3
import enum
4
4
import inspect
5
- import io
6
5
import os
7
6
import socket
8
7
import ssl
@@ -141,7 +140,7 @@ def decode(self, value: EncodableT, force=False) -> EncodableT:
141
140
class BaseParser :
142
141
"""Plain Python parsing class"""
143
142
144
- __slots__ = "_stream" , "_buffer" , " _read_size"
143
+ __slots__ = "_stream" , "_read_size"
145
144
146
145
EXCEPTION_CLASSES : ExceptionMappingT = {
147
146
"ERR" : {
@@ -171,7 +170,6 @@ class BaseParser:
171
170
172
171
def __init__ (self , socket_read_size : int ):
173
172
self ._stream : Optional [asyncio .StreamReader ] = None
174
- self ._buffer : Optional [SocketBuffer ] = None
175
173
self ._read_size = socket_read_size
176
174
177
175
def __del__ (self ):
@@ -206,127 +204,6 @@ async def read_response(
206
204
raise NotImplementedError ()
207
205
208
206
209
- class SocketBuffer :
210
- """Async-friendly re-impl of redis-py's SocketBuffer.
211
-
212
- TODO: We're currently passing through two buffers,
213
- the asyncio.StreamReader and this. I imagine we can reduce the layers here
214
- while maintaining compliance with prior art.
215
- """
216
-
217
- def __init__ (
218
- self ,
219
- stream_reader : asyncio .StreamReader ,
220
- socket_read_size : int ,
221
- ):
222
- self ._stream : Optional [asyncio .StreamReader ] = stream_reader
223
- self .socket_read_size = socket_read_size
224
- self ._buffer : Optional [io .BytesIO ] = io .BytesIO ()
225
- # number of bytes written to the buffer from the socket
226
- self .bytes_written = 0
227
- # number of bytes read from the buffer
228
- self .bytes_read = 0
229
-
230
- @property
231
- def length (self ):
232
- return self .bytes_written - self .bytes_read
233
-
234
- async def _read_from_socket (self , length : Optional [int ] = None ) -> bool :
235
- buf = self ._buffer
236
- if buf is None or self ._stream is None :
237
- raise RedisError ("Buffer is closed." )
238
- buf .seek (self .bytes_written )
239
- marker = 0
240
-
241
- while True :
242
- data = await self ._stream .read (self .socket_read_size )
243
- # an empty string indicates the server shutdown the socket
244
- if isinstance (data , bytes ) and len (data ) == 0 :
245
- raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
246
- buf .write (data )
247
- data_length = len (data )
248
- self .bytes_written += data_length
249
- marker += data_length
250
-
251
- if length is not None and length > marker :
252
- continue
253
- return True
254
-
255
- async def can_read_destructive (self ) -> bool :
256
- if self .length :
257
- return True
258
- try :
259
- async with async_timeout .timeout (0 ):
260
- return await self ._read_from_socket ()
261
- except asyncio .TimeoutError :
262
- return False
263
-
264
- async def read (self , length : int ) -> bytes :
265
- length = length + 2 # make sure to read the \r\n terminator
266
- # make sure we've read enough data from the socket
267
- if length > self .length :
268
- await self ._read_from_socket (length - self .length )
269
-
270
- if self ._buffer is None :
271
- raise RedisError ("Buffer is closed." )
272
-
273
- self ._buffer .seek (self .bytes_read )
274
- data = self ._buffer .read (length )
275
- self .bytes_read += len (data )
276
-
277
- # purge the buffer when we've consumed it all so it doesn't
278
- # grow forever
279
- if self .bytes_read == self .bytes_written :
280
- self .purge ()
281
-
282
- return data [:- 2 ]
283
-
284
- async def readline (self ) -> bytes :
285
- buf = self ._buffer
286
- if buf is None :
287
- raise RedisError ("Buffer is closed." )
288
-
289
- buf .seek (self .bytes_read )
290
- data = buf .readline ()
291
- while not data .endswith (SYM_CRLF ):
292
- # there's more data in the socket that we need
293
- await self ._read_from_socket ()
294
- buf .seek (self .bytes_read )
295
- data = buf .readline ()
296
-
297
- self .bytes_read += len (data )
298
-
299
- # purge the buffer when we've consumed it all so it doesn't
300
- # grow forever
301
- if self .bytes_read == self .bytes_written :
302
- self .purge ()
303
-
304
- return data [:- 2 ]
305
-
306
- def purge (self ):
307
- if self ._buffer is None :
308
- raise RedisError ("Buffer is closed." )
309
-
310
- self ._buffer .seek (0 )
311
- self ._buffer .truncate ()
312
- self .bytes_written = 0
313
- self .bytes_read = 0
314
-
315
- def close (self ):
316
- try :
317
- self .purge ()
318
- self ._buffer .close ()
319
- except Exception :
320
- # issue #633 suggests the purge/close somehow raised a
321
- # BadFileDescriptor error. Perhaps the client ran out of
322
- # memory or something else? It's probably OK to ignore
323
- # any error being raised from purge/close since we're
324
- # removing the reference to the instance below.
325
- pass
326
- self ._buffer = None
327
- self ._stream = None
328
-
329
-
330
207
class PythonParser (BaseParser ):
331
208
"""Plain Python parsing class"""
332
209
@@ -342,27 +219,29 @@ def on_connect(self, connection: "Connection"):
342
219
if self ._stream is None :
343
220
raise RedisError ("Buffer is closed." )
344
221
345
- self ._buffer = SocketBuffer (self ._stream , self ._read_size )
346
222
self .encoder = connection .encoder
347
223
348
224
def on_disconnect (self ):
349
225
"""Called when the stream disconnects"""
350
226
if self ._stream is not None :
351
227
self ._stream = None
352
- if self ._buffer is not None :
353
- self ._buffer .close ()
354
- self ._buffer = None
355
228
self .encoder = None
356
229
357
- async def can_read_destructive (self ):
358
- return self ._buffer and bool (await self ._buffer .can_read_destructive ())
230
+ async def can_read_destructive (self ) -> bool :
231
+ if self ._stream is None :
232
+ raise RedisError ("Buffer is closed." )
233
+ try :
234
+ async with async_timeout .timeout (0 ):
235
+ return await self ._stream .read (1 )
236
+ except asyncio .TimeoutError :
237
+ return False
359
238
360
239
async def read_response (
361
240
self , disable_decoding : bool = False
362
241
) -> Union [EncodableT , ResponseError , None ]:
363
- if not self ._buffer or not self .encoder :
242
+ if not self ._stream or not self .encoder :
364
243
raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
365
- raw = await self ._buffer . readline ()
244
+ raw = await self ._readline ()
366
245
if not raw :
367
246
raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
368
247
response : Any
@@ -395,7 +274,7 @@ async def read_response(
395
274
length = int (response )
396
275
if length == - 1 :
397
276
return None
398
- response = await self ._buffer . read (length )
277
+ response = await self ._read (length )
399
278
# multi-bulk response
400
279
elif byte == b"*" :
401
280
length = int (response )
@@ -408,6 +287,31 @@ async def read_response(
408
287
response = self .encoder .decode (response )
409
288
return response
410
289
290
+ async def _read (self , length : int ) -> bytes :
291
+ """
292
+ Read `length` bytes of data. These are assumed to be followed
293
+ by a '\r \n ' terminator which is subsequently discarded.
294
+ """
295
+ if self ._stream is None :
296
+ raise RedisError ("Buffer is closed." )
297
+ try :
298
+ data = await self ._stream .readexactly (length + 2 )
299
+ except asyncio .IncompleteReadError as error :
300
+ raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR ) from error
301
+ return data [:- 2 ]
302
+
303
+ async def _readline (self ) -> bytes :
304
+ """
305
+ read an unknown number of bytes up to the next '\r \n '
306
+ line separator, which is discarded.
307
+ """
308
+ if self ._stream is None :
309
+ raise RedisError ("Buffer is closed." )
310
+ data = await self ._stream .readline ()
311
+ if not data .endswith (b"\r \n " ):
312
+ raise ConnectionError (SERVER_CLOSED_CONNECTION_ERROR )
313
+ return data [:- 2 ]
314
+
411
315
412
316
class HiredisParser (BaseParser ):
413
317
"""Parser class for connections using Hiredis"""
0 commit comments