Issue #7523: Add SOCK_CLOEXEC and SOCK_NONBLOCK to the socket module,

where supported by the system.  Patch by Nikita Vetoshkin.
This commit is contained in:
Antoine Pitrou 2010-10-14 15:05:38 +00:00
parent f4061dac60
commit b1c5496738
9 changed files with 137 additions and 12 deletions

View File

@ -157,6 +157,21 @@ The module :mod:`socket` exports the following constants and functions:
:func:`socket`. (Only :const:`SOCK_STREAM` and :const:`SOCK_DGRAM` appear to be
generally useful.)
.. data:: SOCK_CLOEXEC
SOCK_NONBLOCK
These two constants, if defined, can be combined with the socket types and
allow you to set some flags atomically (thus avoiding possible race
conditions and the need for separate calls).
.. seealso::
`Secure File Descriptor Handling <http://udrepper.livejournal.com/20407.html>`_
for a more thorough explanation.
Availability: Linux >= 2.6.27.
.. versionadded:: 3.2
.. data:: SO_*
SOMAXCONN

View File

@ -692,7 +692,8 @@ class BaseTestAPI(unittest.TestCase):
s = asyncore.dispatcher()
s.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.assertEqual(s.socket.family, socket.AF_INET)
self.assertEqual(s.socket.type, socket.SOCK_STREAM)
SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0)
self.assertEqual(s.socket.type, socket.SOCK_STREAM | SOCK_NONBLOCK)
def test_bind(self):
s1 = asyncore.dispatcher()

View File

@ -17,6 +17,10 @@ import contextlib
from weakref import proxy
import signal
import math
try:
import fcntl
except ImportError:
fcntl = False
def try_address(host, port=0, family=socket.AF_INET):
"""Try to bind a socket on the given host:port and return True
@ -902,6 +906,26 @@ class NonBlockingTCPTests(ThreadedTCPSocketTest):
def _testSetBlocking(self):
pass
if hasattr(socket, "SOCK_NONBLOCK"):
def testInitNonBlocking(self):
# reinit server socket
self.serv.close()
self.serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM |
socket.SOCK_NONBLOCK)
self.port = support.bind_port(self.serv)
self.serv.listen(1)
# actual testing
start = time.time()
try:
self.serv.accept()
except socket.error:
pass
end = time.time()
self.assertTrue((end - start) < 1.0, "Error creating with non-blocking mode.")
def _testInitNonBlocking(self):
pass
def testAccept(self):
# Testing non-blocking accept
self.serv.setblocking(0)
@ -1801,6 +1825,56 @@ class ContextManagersTest(ThreadedTCPSocketTest):
self.assertTrue(sock._closed)
self.assertRaises(socket.error, sock.sendall, b'foo')
@unittest.skipUnless(hasattr(socket, "SOCK_CLOEXEC"),
"SOCK_CLOEXEC not defined")
@unittest.skipUnless(fcntl, "module fcntl not available")
class CloexecConstantTest(unittest.TestCase):
def test_SOCK_CLOEXEC(self):
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM | socket.SOCK_CLOEXEC)
self.assertTrue(s.type & socket.SOCK_CLOEXEC)
self.assertTrue(fcntl.fcntl(s, fcntl.F_GETFD) & fcntl.FD_CLOEXEC)
@unittest.skipUnless(hasattr(socket, "SOCK_NONBLOCK"),
"SOCK_NONBLOCK not defined")
class NonblockConstantTest(unittest.TestCase):
def checkNonblock(self, s, nonblock=True, timeout=0.0):
if nonblock:
self.assertTrue(s.type & socket.SOCK_NONBLOCK)
self.assertEqual(s.gettimeout(), timeout)
else:
self.assertFalse(s.type & socket.SOCK_NONBLOCK)
self.assertEqual(s.gettimeout(), None)
def test_SOCK_NONBLOCK(self):
# a lot of it seems silly and redundant, but I wanted to test that
# changing back and forth worked ok
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM | socket.SOCK_NONBLOCK)
self.checkNonblock(s)
s.setblocking(1)
self.checkNonblock(s, False)
s.setblocking(0)
self.checkNonblock(s)
s.settimeout(None)
self.checkNonblock(s, False)
s.settimeout(2.0)
self.checkNonblock(s, timeout=2.0)
s.setblocking(1)
self.checkNonblock(s, False)
# defaulttimeout
t = socket.getdefaulttimeout()
socket.setdefaulttimeout(0.0)
self.checkNonblock(socket.socket())
socket.setdefaulttimeout(None)
self.checkNonblock(socket.socket(), False)
socket.setdefaulttimeout(2.0)
self.checkNonblock(socket.socket(), timeout=2.0)
socket.setdefaulttimeout(None)
self.checkNonblock(socket.socket(), False)
socket.setdefaulttimeout(t)
def test_main():
tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest,
@ -1820,6 +1894,8 @@ def test_main():
NetworkConnectionAttributesTest,
NetworkConnectionBehaviourTest,
ContextManagersTest,
CloexecConstantTest,
NonblockConstantTest
])
if hasattr(socket, "socketpair"):
tests.append(BasicSocketPairTest)

View File

@ -846,6 +846,7 @@ Alexandre Vassalotti
Frank Vercruesse
Mike Verdone
Jaap Vermeulen
Nikita Vetoshkin
Al Vezza
Jacques A. Vidrine
John Viega

View File

@ -21,12 +21,14 @@ Core and Builtins
Library
-------
- Issue #Issue10063: file:// scheme will stop accessing remote hosts via ftp
- Issue #7523: Add SOCK_CLOEXEC and SOCK_NONBLOCK to the socket module,
where supported by the system. Patch by Nikita Vetoshkin.
- Issue #10063: file:// scheme will stop accessing remote hosts via ftp
protocol. file:// urls had fallback to access remote hosts via ftp. This was
not correct, change is made to raise a URLError when a remote host is tried
to access via file:// scheme.
- Issue #1710703: Write structures for an empty ZIP archive when a ZipFile is
created in modes 'a' or 'w' and then closed without adding any files. Raise
BadZipfile (rather than IOError) when opening small non-ZIP files.

View File

@ -615,6 +615,12 @@ internal_setblocking(PySocketSockObject *s, int block)
#ifndef MS_WINDOWS
int delay_flag;
#endif
#ifdef SOCK_NONBLOCK
if (block)
s->sock_type &= (~SOCK_NONBLOCK);
else
s->sock_type |= SOCK_NONBLOCK;
#endif
Py_BEGIN_ALLOW_THREADS
#ifndef MS_WINDOWS
@ -764,12 +770,18 @@ init_sockobject(PySocketSockObject *s,
s->sock_family = family;
s->sock_type = type;
s->sock_proto = proto;
s->sock_timeout = defaulttimeout;
s->errorhandler = &set_error;
#ifdef SOCK_NONBLOCK
if (type & SOCK_NONBLOCK)
s->sock_timeout = 0.0;
else
#endif
{
s->sock_timeout = defaulttimeout;
if (defaulttimeout >= 0.0)
internal_setblocking(s, 0);
}
}
@ -1645,7 +1657,9 @@ sock_accept(PySocketSockObject *s)
PyObject *addr = NULL;
PyObject *res = NULL;
int timeout;
#ifdef HAVE_ACCEPT4
int flags = 0;
#endif
if (!getsockaddrlen(s, &addrlen))
return NULL;
memset(&addrbuf, 0, addrlen);
@ -1656,8 +1670,15 @@ sock_accept(PySocketSockObject *s)
BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
timeout = internal_select_ex(s, 0, interval);
if (!timeout)
if (!timeout) {
#ifdef HAVE_ACCEPT4
/* inherit socket flags and use accept4 call */
flags = s->sock_type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
newfd = accept4(s->sock_fd, SAS2SA(&addrbuf), &addrlen, flags);
#else
newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen);
#endif /* HAVE_ACCEPT4 */
}
Py_END_ALLOW_THREADS
if (timeout == 1) {
@ -4599,6 +4620,12 @@ PyInit__socket(void)
#if defined(SOCK_RDM)
PyModule_AddIntConstant(m, "SOCK_RDM", SOCK_RDM);
#endif
#ifdef SOCK_CLOEXEC
PyModule_AddIntConstant(m, "SOCK_CLOEXEC", SOCK_CLOEXEC);
#endif
#ifdef SOCK_NONBLOCK
PyModule_AddIntConstant(m, "SOCK_NONBLOCK", SOCK_NONBLOCK);
#endif
#ifdef SO_DEBUG
PyModule_AddIntConstant(m, "SO_DEBUG", SO_DEBUG);

4
configure vendored
View File

@ -1,5 +1,5 @@
#! /bin/sh
# From configure.in Revision: 85349 .
# From configure.in Revision: 85422 .
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.65 for python 3.2.
#
@ -9302,7 +9302,7 @@ fi
$as_echo "MACHDEP_OBJS" >&6; }
# checks for library functions
for ac_func in alarm setitimer getitimer bind_textdomain_codeset chown \
for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \
gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \
getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \

View File

@ -2550,7 +2550,7 @@ fi
AC_MSG_RESULT(MACHDEP_OBJS)
# checks for library functions
AC_CHECK_FUNCS(alarm setitimer getitimer bind_textdomain_codeset chown \
AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
clock confstr ctermid execv fchmod fchown fork fpathconf ftime ftruncate \
gai_strerror getgroups getlogin getloadavg getpeername getpgid getpid \
getpriority getresuid getresgid getpwent getspnam getspent getsid getwd \

View File

@ -40,6 +40,9 @@
the case on Motorola V4 (R40V4.2) */
#undef GETTIMEOFDAY_NO_TZ
/* Define to 1 if you have the `accept4' function. */
#undef HAVE_ACCEPT4
/* Define to 1 if you have the `acosh' function. */
#undef HAVE_ACOSH