diff options
author | Hervé Beraud <hberaud@redhat.com> | 2021-06-30 16:35:54 +0200 |
---|---|---|
committer | Hervé Beraud <hberaud@redhat.com> | 2021-07-01 20:13:58 +0200 |
commit | 15c35ef184c09279cbb886ceb4bd2807241d48b1 (patch) | |
tree | c0c254db621edb7c68e49c9a3d4f417be8946a0f | |
parent | 41ad03533fe15ea9ad6188c65df129bfd8d077cd (diff) | |
download | pymemcache-15c35ef184c09279cbb886ceb4bd2807241d48b1.tar.gz |
Ensure us to close socket on MemcacheUnexpectedCloseError
During network maintenance operations or during server reboot users could
face MemcacheUnexpectedCloseError.
This kind of exception will be raised down the stack. It's a design
choice [1]. Users should manage this kind of exception.
However, at some points the opened sockets remains more or less opened
after this kind of fail on the client side [2]. It will require n+1
calls to see them recreated when the connection will be possible. That's
could be problem with services that implement several cache logics in parallel
and where their clients are based on pymemcache.
These changes simply ensure to close the socket properly. The socket
will be recreated by the client if needed.
[1] https://github.com/pinterest/pymemcache/issues/307
[2] https://bugzilla.redhat.com/show_bug.cgi?id=1977711
-rw-r--r-- | pymemcache/client/base.py | 28 | ||||
-rw-r--r-- | pymemcache/test/test_client.py | 14 |
2 files changed, 38 insertions, 4 deletions
diff --git a/pymemcache/client/base.py b/pymemcache/client/base.py index abba063..64f65d7 100644 --- a/pymemcache/client/base.py +++ b/pymemcache/client/base.py @@ -993,7 +993,12 @@ class Client(object): except Exception as e: raise ValueError("Unable to parse line %s: %s" % (line, e)) - buf, value = _readvalue(self.sock, buf, int(size)) + value = None + try: + buf, value = _readvalue(self.sock, buf, int(size)) + except MemcacheUnexpectedCloseError: + self.close() + raise key = remapped_keys[key] value = self.serde.deserialize(key, value, int(flags)) @@ -1019,9 +1024,14 @@ class Client(object): self.sock.sendall(cmd) buf = b'' + line = None result = {} while True: - buf, line = _readline(self.sock, buf) + try: + buf, line = _readline(self.sock, buf) + except MemcacheUnexpectedCloseError: + self.close() + raise self._raise_errors(line, name) if line == b'END' or line == b'OK': return result @@ -1091,8 +1101,13 @@ class Client(object): results = {} buf = b'' + line = None for key in keys: - buf, line = _readline(self.sock, buf) + try: + buf, line = _readline(self.sock, buf) + except MemcacheUnexpectedCloseError: + self.close() + raise self._raise_errors(line, name) if line in VALID_STORE_RESULTS[name]: @@ -1116,8 +1131,13 @@ class Client(object): results = [] buf = b'' + line = None for cmd in cmds: - buf, line = _readline(self.sock, buf) + try: + buf, line = _readline(self.sock, buf) + except MemcacheUnexpectedCloseError: + self.close() + raise self._raise_errors(line, cmd_name) results.append(line) return results diff --git a/pymemcache/test/test_client.py b/pymemcache/test/test_client.py index c09f48a..118a229 100644 --- a/pymemcache/test/test_client.py +++ b/pymemcache/test/test_client.py @@ -820,6 +820,20 @@ class TestClient(ClientTestMixin, unittest.TestCase): with pytest.raises(MemcacheServerError): _set() + def test_closing_socket_on_unexpected_closed_error(self): + client = self.make_client([ + b'VALUE ', + MemcacheUnexpectedCloseError("foo bar"), + ]) + + def _set(): + client.set(b'key', b'value', noreply=False) + + with pytest.raises(MemcacheUnexpectedCloseError): + _set() + + assert client.sock is None + def test_set_unknown_error(self): client = self.make_client([b'foobarbaz\r\n']) |