Description
tl;dr repeat of #360
My codebase uses gevent (w/monkey-patching) for concurrent workers, and schedules a gevent.Timeout to force a deadline on the workers. Since a timeout causes an exception to be raised from an arbitrary "yield point", there's risk of corrupting shared state if code is not written to be exception-safe.
The code talks to redis over a client that's shared between the workers. Socket I/O is a "yield point". Sending a command successfully, but then failing to read the entire response off the socket, gets Connection
into an undefined state: a subsequent command would try to parse a response intended for a previous command, and in many cases would fail. Here's what a typical error would look like:
File "site-packages/redis/client.py", line 3401, in parse_response
result = Redis.parse_response(
File "site-packages/redis/client.py", line 774, in parse_response
return self.response_callbacks[command_name](response, **options)
File "site-packages/redis/client.py", line 528, in <lambda>
'SET': lambda r: r and nativestr(r) == 'OK',
File "site-packages/redis/_compat.py", line 135, in nativestr
return x if isinstance(x, str) else x.decode('utf-8', 'replace')
AttributeError: 'int' object has no attribute 'decode'
As you can see, we're trying to parse an integer response to a previous command (e.g. ":12345\r\n"
) as a string response to a SET
command.
The except Exception
block in redis.connection.Connection.read_response
is intended to handle all sorts of parsing errors by disconnecting the connection:
Lines 819 to 821 in 6219574
but perhaps it could be changed to
except:
since e.g. gevent.Timeout
is intentionally a BaseException
to suggest that it should not be suppressed (and we are indeed not suppressing it).