THRIFT-5248: Python: Make TSocket.isOpen check if server still connected
Client: py
This is inspired by changes to the Go library (THRIFT-5214) and, by
proxy, this blog post[1]. The idea is that if the other end of the
socket has closed their end of the connection, we can figure that out by
doing a non-blocking read on our socket before we waste time serializing
and sending a message just to find out the socket is closed when we try
to read the response.
[1]: https://github.blog/2020-05-20-three-bugs-in-the-go-mysql-driver/
diff --git a/lib/py/test/test_socket.py b/lib/py/test/test_socket.py
new file mode 100644
index 0000000..95124dc
--- /dev/null
+++ b/lib/py/test/test_socket.py
@@ -0,0 +1,57 @@
+import errno
+import unittest
+
+from test_sslsocket import ServerAcceptor
+
+import _import_local_thrift # noqa
+
+from thrift.transport.TSocket import TServerSocket
+from thrift.transport.TSocket import TSocket
+from thrift.transport.TTransport import TTransportException
+
+
+class TSocketTest(unittest.TestCase):
+ def test_isOpen_checks_for_readability(self):
+ # https://docs.python.org/3/library/socket.html#notes-on-socket-timeouts
+ # https://docs.python.org/3/library/socket.html#socket.socket.settimeout
+ timeouts = [
+ None, # blocking mode
+ 0, # non-blocking mode
+ 1.0, # timeout mode
+ ]
+
+ for timeout in timeouts:
+ acc = ServerAcceptor(TServerSocket(port=0))
+ acc.start()
+
+ sock = TSocket(host="localhost", port=acc.port)
+ sock.open()
+ sock.setTimeout(timeout)
+
+ # the socket shows as open immediately after connecting
+ self.assertTrue(sock.isOpen())
+
+ # and remains open during usage
+ sock.write(b"hello")
+ self.assertTrue(sock.isOpen())
+ while True:
+ try:
+ sock.read(5)
+ except TTransportException as exc:
+ if exc.inner.errno == errno.EAGAIN:
+ # try again when we're in non-blocking mode
+ continue
+ raise
+ break
+ self.assertTrue(sock.isOpen())
+
+ # once the server side closes, it no longer shows open
+ acc.client.close() # this also blocks until the other thread is done
+ acc.close()
+ self.assertFalse(sock.isOpen())
+
+ sock.close()
+
+
+if __name__ == "__main__":
+ unittest.main()