Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6d7df63

Browse files
committed
Issue #9950: Fix socket.sendall() crash or misbehaviour when a signal is
received. Now sendall() properly calls signal handlers if necessary, and retries sending if these returned successfully, including on sockets with a timeout.
1 parent 0ae3361 commit 6d7df63

3 files changed

Lines changed: 67 additions & 23 deletions

File tree

Lib/test/test_socket.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import contextlib
1717
from weakref import proxy
1818
import signal
19+
import math
1920

2021
def try_address(host, port=0, family=socket.AF_INET):
2122
"""Try to bind a socket on the given host:port and return True
@@ -655,6 +656,42 @@ def test_idna(self):
655656
# have a reverse entry yet
656657
# socket.gethostbyaddr('испытание.python.org')
657658

659+
def check_sendall_interrupted(self, with_timeout):
660+
# socketpair() is not stricly required, but it makes things easier.
661+
if not hasattr(signal, 'alarm') or not hasattr(socket, 'socketpair'):
662+
self.skipTest("signal.alarm and socket.socketpair required for this test")
663+
# Our signal handlers clobber the C errno by calling a math function
664+
# with an invalid domain value.
665+
def ok_handler(*args):
666+
self.assertRaises(ValueError, math.acosh, 0)
667+
def raising_handler(*args):
668+
self.assertRaises(ValueError, math.acosh, 0)
669+
1 // 0
670+
c, s = socket.socketpair()
671+
old_alarm = signal.signal(signal.SIGALRM, raising_handler)
672+
try:
673+
if with_timeout:
674+
# Just above the one second minimum for signal.alarm
675+
c.settimeout(1.5)
676+
with self.assertRaises(ZeroDivisionError):
677+
signal.alarm(1)
678+
c.sendall(b"x" * (1024**2))
679+
if with_timeout:
680+
signal.signal(signal.SIGALRM, ok_handler)
681+
signal.alarm(1)
682+
self.assertRaises(socket.timeout, c.sendall, b"x" * (1024**2))
683+
finally:
684+
signal.signal(signal.SIGALRM, old_alarm)
685+
c.close()
686+
s.close()
687+
688+
def test_sendall_interrupted(self):
689+
self.check_sendall_interrupted(False)
690+
691+
def test_sendall_interrupted_with_timeout(self):
692+
self.check_sendall_interrupted(True)
693+
694+
658695
@unittest.skipUnless(thread, 'Threading required for this test.')
659696
class BasicTCPTest(SocketConnectedTest):
660697

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ Core and Builtins
6868
Library
6969
-------
7070

71+
- Issue #9950: Fix socket.sendall() crash or misbehaviour when a signal is
72+
received. Now sendall() properly calls signal handlers if necessary,
73+
and retries sending if these returned successfully, including on sockets
74+
with a timeout.
75+
7176
- Issue #9947: logging: Fixed locking bug in stopListening.
7277

7378
- Issue #9945: logging: Fixed locking bugs in addHandler/removeHandler.

Modules/socketmodule.c

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2568,7 +2568,7 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
25682568
{
25692569
char *buf;
25702570
Py_ssize_t len, n = -1;
2571-
int flags = 0, timeout;
2571+
int flags = 0, timeout, saved_errno;
25722572
Py_buffer pbuf;
25732573

25742574
if (!PyArg_ParseTuple(args, "y*|i:sendall", &pbuf, &flags))
@@ -2581,42 +2581,44 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
25812581
return select_error();
25822582
}
25832583

2584-
Py_BEGIN_ALLOW_THREADS
25852584
do {
2585+
Py_BEGIN_ALLOW_THREADS
25862586
timeout = internal_select(s, 1);
25872587
n = -1;
2588-
if (timeout)
2589-
break;
2588+
if (!timeout) {
25902589
#ifdef __VMS
2591-
n = sendsegmented(s->sock_fd, buf, len, flags);
2590+
n = sendsegmented(s->sock_fd, buf, len, flags);
25922591
#else
2593-
n = send(s->sock_fd, buf, len, flags);
2592+
n = send(s->sock_fd, buf, len, flags);
25942593
#endif
2594+
}
2595+
Py_END_ALLOW_THREADS
2596+
if (timeout == 1) {
2597+
PyBuffer_Release(&pbuf);
2598+
PyErr_SetString(socket_timeout, "timed out");
2599+
return NULL;
2600+
}
2601+
/* PyErr_CheckSignals() might change errno */
2602+
saved_errno = errno;
2603+
/* We must run our signal handlers before looping again.
2604+
send() can return a successful partial write when it is
2605+
interrupted, so we can't restrict ourselves to EINTR. */
2606+
if (PyErr_CheckSignals()) {
2607+
PyBuffer_Release(&pbuf);
2608+
return NULL;
2609+
}
25952610
if (n < 0) {
2596-
#ifdef EINTR
2597-
/* We must handle EINTR here as there is no way for
2598-
* the caller to know how much was sent otherwise. */
2599-
if (errno == EINTR) {
2600-
/* Run signal handlers. If an exception was
2601-
* raised, abort and leave this socket in
2602-
* an unknown state. */
2603-
if (PyErr_CheckSignals())
2604-
return NULL;
2611+
/* If interrupted, try again */
2612+
if (saved_errno == EINTR)
26052613
continue;
2606-
}
2607-
#endif
2608-
break;
2614+
else
2615+
break;
26092616
}
26102617
buf += n;
26112618
len -= n;
26122619
} while (len > 0);
2613-
Py_END_ALLOW_THREADS
26142620
PyBuffer_Release(&pbuf);
26152621

2616-
if (timeout == 1) {
2617-
PyErr_SetString(socket_timeout, "timed out");
2618-
return NULL;
2619-
}
26202622
if (n < 0)
26212623
return s->errorhandler();
26222624

0 commit comments

Comments
 (0)