From 5ad19a37ed2a26a24a66561bc8a168d8ca2f5e2b Mon Sep 17 00:00:00 2001 From: Mahmoud Hashemi Date: Wed, 27 Apr 2016 22:37:47 -0700 Subject: [PATCH] change BufferedSocket.recv to always return the rbuf data, even when it is less than the size requested. this way there is no chance of getting a socket error when there is data still in the read buffer, and when sockets are closed while calling recv, we know that the recv buffer is empty. --- boltons/socketutils.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/boltons/socketutils.py b/boltons/socketutils.py index c759ed7..122ae91 100644 --- a/boltons/socketutils.py +++ b/boltons/socketutils.py @@ -11,12 +11,12 @@ builtin: straightforward, tested building blocks for higher-level protocols. The :class:`BufferedSocket` wraps an ordinary socket, but provides a -layer of buffering for both sending and receiving. This is vital for -building any higher level protocols on sockets, especially on the part -of the receiver. The Bufferedsocket enables receiving until the next -relevant token, or up to a certain size. It also provides size -limiting, as well as timeouts that are compatible with multiple -concurrency paradigms. +layer of buffering for both sending and receiving. This facilitates +parsing messages from streams, which is necessary for virtually all +protocols on TCP and UDS sockets in ``SOCK_STREAM`` mode. The +Bufferedsocket enables receiving until the next relevant token, or up +to a certain size. It also provides size limiting, as well as timeouts +that are compatible with multiple concurrency paradigms. This module also provides the :class:`NetstringSocket`, a pure-Python implementation of the Netstring protocol, built on the @@ -96,9 +96,9 @@ class BufferedSocket(object): for more information on how to use BufferedSockets with built-in sockets. - The BufferedSocket is threadsafe. Still, consider your protocol - before accessing a single socket from multiple threads. - + The BufferedSocket is threadsafe, but consider the semantics of + your protocol before accessing a single socket from multiple + threads. """ def __init__(self, sock, timeout=DEFAULT_TIMEOUT, maxsize=DEFAULT_MAXSIZE): @@ -145,7 +145,10 @@ class BufferedSocket(object): set in the constructor of BufferedSocket. If the operation does not complete in *timeout* seconds, a - :exc:`Timeout` is raised. + :exc:`Timeout` is raised. Much like the built-in + :type:`socket.socket`, if this method returns an empty string, + then the socket is closed and recv buffer is empty. Further + calls to recv will raise :exc:`socket.error`. """ with self.recv_lock: if timeout is _UNSET: @@ -155,17 +158,14 @@ class BufferedSocket(object): if len(self.rbuf) >= size: data, self.rbuf = self.rbuf[:size], self.rbuf[size:] return data - size -= len(self.rbuf) + if self.rbuf: + ret, self.rbuf = self.rbuf, b'' + return ret self.sock.settimeout(timeout) try: - sock_data = self.sock.recv(size) + data = self.sock.recv(size) except socket.timeout: raise Timeout(timeout) # check the rbuf attr for more - data = self.rbuf + sock_data - # don't empty buffer till after network communication is complete, - # to avoid data loss on transient / retry-able errors (e.g. read - # timeout) - self.rbuf = b'' return data def peek(self, size, timeout=_UNSET):