Skip to content

Commit 24237ea

Browse files
committed
Fix a bug introduced in r62627. see issue2760 and issue2632.
An assertion in readline() would fail as data was already in the internal buffer even though the socket was in unbuffered read mode. That case is now handled. More importantly, read() has been fixed to not over-recv() and leave newly recv()d data in the _fileobject buffer. The max() vs min() issue in read() is now gone. Neither was correct. On bounded reads, always ask recv() for the exact amount of data we still need. Candidate for backporting to release25-maint along with r62627.
1 parent 98fd036 commit 24237ea

2 files changed

Lines changed: 45 additions & 19 deletions

File tree

Lib/socket.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ def _get_wbuf_len(self):
312312

313313
def read(self, size=-1):
314314
# Use max, disallow tiny reads in a loop as they are very inefficient.
315-
# We never leave read() with any leftover data in our internal buffer.
315+
# We never leave read() with any leftover data from a new recv() call
316+
# in our internal buffer.
316317
rbufsize = max(self._rbufsize, self.default_bufsize)
317318
# Our use of StringIO rather than lists of string objects returned by
318319
# recv() minimizes memory usage and fragmentation that occurs when
@@ -342,13 +343,12 @@ def read(self, size=-1):
342343
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
343344
while True:
344345
left = size - buf_len
345-
# Using max() here means that recv() can malloc a
346-
# large amount of memory even though recv may return
347-
# much less data than that. But the returned data
348-
# string is short lived in that case as we copy it
349-
# into a StringIO and free it.
350-
recv_size = max(rbufsize, left)
351-
data = self._sock.recv(recv_size)
346+
# recv() will malloc the amount of memory given as its
347+
# parameter even though it often returns much less data
348+
# than that. The returned data string is short lived
349+
# as we copy it into a StringIO and free it. This avoids
350+
# fragmentation issues on many platforms.
351+
data = self._sock.recv(left)
352352
if not data:
353353
break
354354
n = len(data)
@@ -359,13 +359,11 @@ def read(self, size=-1):
359359
# - Our call to recv returned exactly the
360360
# number of bytes we were asked to read.
361361
return data
362-
if n >= left:
363-
# avoids data copy of: buf.write(data[:left])
364-
buf.write(buffer(data, 0, left))
365-
# avoids data copy of: self._rbuf.write(data[left:])
366-
self._rbuf.write(buffer(data, left))
362+
if n == left:
363+
buf.write(data)
367364
del data # explicit free
368365
break
366+
assert n <= left, "recv(%d) returned %d bytes" % (left, n)
369367
buf.write(data)
370368
buf_len += n
371369
del data # explicit free
@@ -374,22 +372,23 @@ def read(self, size=-1):
374372

375373
def readline(self, size=-1):
376374
buf = self._rbuf
377-
if self._rbufsize > 1:
378-
# if we're buffering, check if we already have it in our buffer
375+
buf.seek(0, 2) # seek end
376+
if buf.tell() > 0:
377+
# check if we already have it in our buffer
379378
buf.seek(0)
380379
bline = buf.readline(size)
381380
if bline.endswith('\n') or len(bline) == size:
382381
self._rbuf = StringIO()
383382
self._rbuf.write(buf.read())
384383
return bline
385384
del bline
386-
buf.seek(0, 2) # seek end
387385
if size < 0:
388386
# Read until \n or EOF, whichever comes first
389387
if self._rbufsize <= 1:
390388
# Speed up unbuffered case
391-
assert buf.tell() == 0
392-
buffers = []
389+
buf.seek(0)
390+
buffers = [buf.read()]
391+
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
393392
data = None
394393
recv = self._sock.recv
395394
while data != "\n":
@@ -399,7 +398,6 @@ def readline(self, size=-1):
399398
buffers.append(data)
400399
return "".join(buffers)
401400

402-
buf = self._rbuf
403401
buf.seek(0, 2) # seek end
404402
self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
405403
while True:
@@ -417,6 +415,7 @@ def readline(self, size=-1):
417415
return buf.getvalue()
418416
else:
419417
# Read until size bytes or \n or EOF seen, whichever comes first
418+
buf.seek(0, 2) # seek end
420419
buf_len = buf.tell()
421420
if buf_len >= size:
422421
buf.seek(0)

Lib/test/test_socket.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,33 @@ def _testReadline(self):
789789
self.cli_file.write(MSG)
790790
self.cli_file.flush()
791791

792+
def testReadlineAfterRead(self):
793+
a_baloo_is = self.serv_file.read(len("A baloo is"))
794+
self.assertEqual("A baloo is", a_baloo_is)
795+
_a_bear = self.serv_file.read(len(" a bear"))
796+
self.assertEqual(" a bear", _a_bear)
797+
line = self.serv_file.readline()
798+
self.assertEqual("\n", line)
799+
line = self.serv_file.readline()
800+
self.assertEqual("A BALOO IS A BEAR.\n", line)
801+
line = self.serv_file.readline()
802+
self.assertEqual(MSG, line)
803+
804+
def _testReadlineAfterRead(self):
805+
self.cli_file.write("A baloo is a bear\n")
806+
self.cli_file.write("A BALOO IS A BEAR.\n")
807+
self.cli_file.write(MSG)
808+
self.cli_file.flush()
809+
810+
def testReadlineAfterReadNoNewline(self):
811+
end_of_ = self.serv_file.read(len("End Of "))
812+
self.assertEqual("End Of ", end_of_)
813+
line = self.serv_file.readline()
814+
self.assertEqual("Line", line)
815+
816+
def _testReadlineAfterReadNoNewline(self):
817+
self.cli_file.write("End Of Line")
818+
792819
def testClosedAttr(self):
793820
self.assert_(not self.serv_file.closed)
794821

0 commit comments

Comments
 (0)