@@ -122,3 +122,48 @@ def test_connect_timeout_error_without_retry(self):
122
122
assert conn ._connect .call_count == 1
123
123
assert str (e .value ) == "Timeout connecting to server"
124
124
self .clear (conn )
125
+
126
+ @pytest .mark .parametrize (
127
+ 'exc_type' ,
128
+ [
129
+ Exception ,
130
+ pytest .param (
131
+ BaseException ,
132
+ marks = pytest .mark .xfail (
133
+ reason = 'https://github.com/redis/redis-py/issues/360' ,
134
+ ),
135
+ ),
136
+ ],
137
+ )
138
+ def test_read_response__interrupt_does_not_corrupt (self , exc_type ):
139
+ conn = Connection ()
140
+
141
+ # A note on BaseException:
142
+ # While socket.recv is not supposed to raise BaseException, gevent's version
143
+ # of socket (which, when using gevent + redis-py, one would monkey-patch in)
144
+ # can raise BaseException on a timer elapse, since `gevent.Timeout` derives
145
+ # from BaseException. This design suggests that a timeout should
146
+ # not be suppressed but rather allowed to propagate.
147
+ # asyncio.exceptions.CancelledError also derives from BaseException
148
+ # for same reason.
149
+ #
150
+ # What we should ensure, one way or another, is that the connection is
151
+ # not left in a corrupted state.
152
+
153
+ conn .send_command ("GET non_existent_key" )
154
+ resp = conn .read_response ()
155
+ assert resp is None
156
+
157
+ with pytest .raises (exc_type ):
158
+ conn .send_command ("EXISTS non_existent_key" )
159
+ # due to the interrupt, the integer '0' result of EXISTS will remain
160
+ # on the socket's buffer
161
+ with patch .object (socket .socket , "recv" , side_effect = exc_type ) as mock_recv :
162
+ _ = conn .read_response ()
163
+ mock_recv .assert_called_once ()
164
+
165
+ conn .send_command ("GET non_existent_key" )
166
+ resp = conn .read_response ()
167
+ # If working properly, this will get a None.
168
+ # If not, it will get a zero (the integer result of the previous command).
169
+ assert resp is None
0 commit comments