classification
Title: Strange behavior for socket.timeout
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.2
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: ned.deily, roysmith
Priority: normal Keywords:

Created on 2010-11-20 20:54 by roysmith, last changed 2010-11-21 02:21 by ned.deily. This issue is now closed.

Files
File name Uploaded Description Edit
test-issue7322.py roysmith, 2010-11-20 20:54 test case
Messages (4)
msg121769 - (view) Author: Roy Smith (roysmith) Date: 2010-11-20 20:54
While investigating issue7322, I wrote a test case to demonstrate the issue.  I made a mistake and called settimeout() on the wrong socket, but the result appears to demonstrate a different bug.  When I run the attached test-issue7322.py on my OSX-10.6.5 machine, using...

Python 3.2a4+ (py3k:86538, Nov 19 2010, 20:52:31) 
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin

I get the output below.  The bug is that readline() is returning, even though I never wrote a newline on the other side of the connection.  If I comment out the settimeout() call, it hangs in the readline() call, as expected.

While I admit it makes no sense to call settimeout() on the listening socket, doing so certainly should not cause this behavior.

Note: I also tried this on Ubuntu linux (with python built from the same 3.2a4+ sources).  I cannot reproduce the problem there.


issue7322$ ./test-issue7322.py 
F/Users/roy/python/py3k/Lib/unittest/case.py:382: ResourceWarning: unclosed <socket.socket object, fd=3, family=2, type=1, proto=0>
  result.addFailure(self, sys.exc_info())
/Users/roy/python/py3k/Lib/unittest/case.py:382: ResourceWarning: unclosed <socket.socket object, fd=4, family=2, type=1, proto=0>
  result.addFailure(self, sys.exc_info())
/Users/roy/python/py3k/Lib/socket.py:324: ResourceWarning: unclosed <socket.socket object, fd=5, family=2, type=1, proto=0>
  self._sock = None

======================================================================
FAIL: test_foo (__main__.Test_Issue7322)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test-issue7322.py", line 22, in test_foo
    self.fail("readline() returned '%s'" % line)
AssertionError: readline() returned 'hello'

----------------------------------------------------------------------
Ran 1 test in 0.026s

FAILED (failures=1)
[49547 refs]
msg121830 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2010-11-21 01:46
There is a difference in behavior in the accept() socket call between Linux and BSD systems (including OS X). As documented in the Debian Linux man page for accept(2):  "On Linux, the new socket returned by accept() does not inherit file status flags such as O_NONBLOCK and O_ASYNC from the listening socket.  This behavior differs from the canonical BSD sockets implementation.   Portable  programs should not rely on inheritance or noninheritance of file status flags and always explicitly set all required flags on the socket returned from accept()."

Calling the settimeout method with a positive, non-zero value causes Modules/setsocketmodule.c to set O_NONBLOCK on the listening socket.  On BSDish systems, the accepted socket for the incoming connection inherits that attribute, which causes the recv(2) socket call to return EAGAIN when the received bytes are consumed rather than block, which causes the socket modules SocketIO.readinto() to return None up the chain, which ultimately causes io.TextIOWrapper.readline() to return all that it has received regardless of whether a newline character was received.

It is for that reason that socket.makefile() is documented: "The socket must be in blocking mode (it can not have a timeout)."  Because of the inheritance behavior, the test case fails to adhere to that.
 
http://docs.python.org/py3k/library/socket.html#socket.socket.makefile
msg121834 - (view) Author: Roy Smith (roysmith) Date: 2010-11-21 02:12
Thank you for the detailed analysis.  That certainly explains what I observed.

Would it make sense for socket.makefile() to check to see if the socket is in blocking mode (assuming there is some reliable/portable way to perform this check), and raise some exception if it is not?
msg121837 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2010-11-21 02:21
As I commented over on Issue7322, I think any such test would be not perfect since I believe the blocking status of the socket could be changed at any time, at least on Unix-y systems.  So I'm +0 on whether adding a test in makefile() is worth it, presuming it can be done on all platforms.  If you feel like trying it, I'd recommend adding it to Issue7322 and see what others think.  It certainly wouldn't hurt if it can be done.
History
Date User Action Args
2010-11-21 02:21:10ned.deilysetmessages: + msg121837
2010-11-21 02:12:31roysmithsetmessages: + msg121834
2010-11-21 01:46:39ned.deilysetstatus: open -> closed

nosy: + ned.deily
messages: + msg121830

resolution: not a bug
2010-11-20 20:54:12roysmithcreate