Skip to content

Commit eef813b

Browse files
vstinnernierob
andauthored
[3.7] bpo-35189: Retry fnctl calls on EINTR (GH-10413) (GH-10678) (GH-10685)
* bpo-35189: Fix eintr_tester.py (GH-10637) Call setitimer() before each test method, instead of once per test case, to ensure that signals are sent in each test method. Previously, only the first method of a testcase class got signals. Changes: * Replace setUpClass() with setUp() and replace tearDownClass() with tearDown(). * tearDown() now ensures that at least one signal has been sent. * Replace support.run_unittest() with unittest.main() which has a nicer CLI and automatically discover test cases. (cherry picked from commit aac1f81) * bpo-35189: Retry fnctl calls on EINTR (GH-10413) Modify the following fnctl function to retry if interrupted by a signal (EINTR): flock, lockf, fnctl. (cherry picked from commit b409ffa) Co-Authored-By: nierob <[email protected]> (cherry picked from commit 56742f1)
1 parent 879f5f3 commit eef813b

File tree

3 files changed

+89
-43
lines changed

3 files changed

+89
-43
lines changed

Lib/test/eintrdata/eintr_tester.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import contextlib
1212
import faulthandler
13+
import fcntl
1314
import os
1415
import select
1516
import signal
@@ -44,27 +45,32 @@ class EINTRBaseTest(unittest.TestCase):
4445
# sleep_time > signal_period
4546
sleep_time = 0.2
4647

47-
@classmethod
48-
def setUpClass(cls):
49-
cls.orig_handler = signal.signal(signal.SIGALRM, lambda *args: None)
50-
signal.setitimer(signal.ITIMER_REAL, cls.signal_delay,
51-
cls.signal_period)
48+
def sighandler(self, signum, frame):
49+
self.signals += 1
5250

53-
# Issue #25277: Use faulthandler to try to debug a hang on FreeBSD
51+
def setUp(self):
52+
self.signals = 0
53+
self.orig_handler = signal.signal(signal.SIGALRM, self.sighandler)
54+
signal.setitimer(signal.ITIMER_REAL, self.signal_delay,
55+
self.signal_period)
56+
57+
# Use faulthandler as watchdog to debug when a test hangs
58+
# (timeout of 10 minutes)
5459
if hasattr(faulthandler, 'dump_traceback_later'):
5560
faulthandler.dump_traceback_later(10 * 60, exit=True,
5661
file=sys.__stderr__)
5762

58-
@classmethod
59-
def stop_alarm(cls):
63+
@staticmethod
64+
def stop_alarm():
6065
signal.setitimer(signal.ITIMER_REAL, 0, 0)
6166

62-
@classmethod
63-
def tearDownClass(cls):
64-
cls.stop_alarm()
65-
signal.signal(signal.SIGALRM, cls.orig_handler)
67+
def tearDown(self):
68+
self.stop_alarm()
69+
signal.signal(signal.SIGALRM, self.orig_handler)
6670
if hasattr(faulthandler, 'cancel_dump_traceback_later'):
6771
faulthandler.cancel_dump_traceback_later()
72+
# make sure that at least one signal has been received
73+
self.assertGreater(self.signals, 0)
6874

6975
def subprocess(self, *args, **kw):
7076
cmd_args = (sys.executable, '-c') + args
@@ -481,14 +487,43 @@ def test_devpoll(self):
481487
self.assertGreaterEqual(dt, self.sleep_time)
482488

483489

484-
def test_main():
485-
support.run_unittest(
486-
OSEINTRTest,
487-
SocketEINTRTest,
488-
TimeEINTRTest,
489-
SignalEINTRTest,
490-
SelectEINTRTest)
490+
class FNTLEINTRTest(EINTRBaseTest):
491+
def _lock(self, lock_func, lock_name):
492+
self.addCleanup(support.unlink, support.TESTFN)
493+
code = '\n'.join((
494+
"import fcntl, time",
495+
"with open('%s', 'wb') as f:" % support.TESTFN,
496+
" fcntl.%s(f, fcntl.LOCK_EX)" % lock_name,
497+
" time.sleep(%s)" % self.sleep_time))
498+
start_time = time.monotonic()
499+
proc = self.subprocess(code)
500+
with kill_on_error(proc):
501+
with open(support.TESTFN, 'wb') as f:
502+
while True: # synchronize the subprocess
503+
dt = time.monotonic() - start_time
504+
if dt > 60.0:
505+
raise Exception("failed to sync child in %.1f sec" % dt)
506+
try:
507+
lock_func(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
508+
lock_func(f, fcntl.LOCK_UN)
509+
time.sleep(0.01)
510+
except BlockingIOError:
511+
break
512+
# the child locked the file just a moment ago for 'sleep_time' seconds
513+
# that means that the lock below will block for 'sleep_time' minus some
514+
# potential context switch delay
515+
lock_func(f, fcntl.LOCK_EX)
516+
dt = time.monotonic() - start_time
517+
self.assertGreaterEqual(dt, self.sleep_time)
518+
self.stop_alarm()
519+
proc.wait()
520+
521+
def test_lockf(self):
522+
self._lock(fcntl.lockf, "lockf")
523+
524+
def test_flock(self):
525+
self._lock(fcntl.flock, "flock")
491526

492527

493528
if __name__ == "__main__":
494-
test_main()
529+
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Modify the following fnctl function to retry if interrupted by a signal
2+
(EINTR): flock, lockf, fnctl

Modules/fcntlmodule.c

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg)
6464
char *str;
6565
Py_ssize_t len;
6666
char buf[1024];
67+
int async_err = 0;
6768

6869
if (arg != NULL) {
6970
int parse_result;
@@ -75,12 +76,13 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg)
7576
return NULL;
7677
}
7778
memcpy(buf, str, len);
78-
Py_BEGIN_ALLOW_THREADS
79-
ret = fcntl(fd, code, buf);
80-
Py_END_ALLOW_THREADS
79+
do {
80+
Py_BEGIN_ALLOW_THREADS
81+
ret = fcntl(fd, code, buf);
82+
Py_END_ALLOW_THREADS
83+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
8184
if (ret < 0) {
82-
PyErr_SetFromErrno(PyExc_IOError);
83-
return NULL;
85+
return !async_err ? PyErr_SetFromErrno(PyExc_IOError) : NULL;
8486
}
8587
return PyBytes_FromStringAndSize(buf, len);
8688
}
@@ -95,12 +97,13 @@ fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg)
9597
}
9698
}
9799

98-
Py_BEGIN_ALLOW_THREADS
99-
ret = fcntl(fd, code, (int)int_arg);
100-
Py_END_ALLOW_THREADS
100+
do {
101+
Py_BEGIN_ALLOW_THREADS
102+
ret = fcntl(fd, code, (int)int_arg);
103+
Py_END_ALLOW_THREADS
104+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
101105
if (ret < 0) {
102-
PyErr_SetFromErrno(PyExc_IOError);
103-
return NULL;
106+
return !async_err ? PyErr_SetFromErrno(PyExc_IOError) : NULL;
104107
}
105108
return PyLong_FromLong((long)ret);
106109
}
@@ -283,11 +286,14 @@ fcntl_flock_impl(PyObject *module, int fd, int code)
283286
/*[clinic end generated code: output=84059e2b37d2fc64 input=b70a0a41ca22a8a0]*/
284287
{
285288
int ret;
289+
int async_err = 0;
286290

287291
#ifdef HAVE_FLOCK
288-
Py_BEGIN_ALLOW_THREADS
289-
ret = flock(fd, code);
290-
Py_END_ALLOW_THREADS
292+
do {
293+
Py_BEGIN_ALLOW_THREADS
294+
ret = flock(fd, code);
295+
Py_END_ALLOW_THREADS
296+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
291297
#else
292298

293299
#ifndef LOCK_SH
@@ -310,14 +316,15 @@ fcntl_flock_impl(PyObject *module, int fd, int code)
310316
return NULL;
311317
}
312318
l.l_whence = l.l_start = l.l_len = 0;
313-
Py_BEGIN_ALLOW_THREADS
314-
ret = fcntl(fd, (code & LOCK_NB) ? F_SETLK : F_SETLKW, &l);
315-
Py_END_ALLOW_THREADS
319+
do {
320+
Py_BEGIN_ALLOW_THREADS
321+
ret = fcntl(fd, (code & LOCK_NB) ? F_SETLK : F_SETLKW, &l);
322+
Py_END_ALLOW_THREADS
323+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
316324
}
317325
#endif /* HAVE_FLOCK */
318326
if (ret < 0) {
319-
PyErr_SetFromErrno(PyExc_IOError);
320-
return NULL;
327+
return !async_err ? PyErr_SetFromErrno(PyExc_IOError) : NULL;
321328
}
322329
Py_RETURN_NONE;
323330
}
@@ -363,6 +370,7 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
363370
/*[clinic end generated code: output=4985e7a172e7461a input=3a5dc01b04371f1a]*/
364371
{
365372
int ret;
373+
int async_err = 0;
366374

367375
#ifndef LOCK_SH
368376
#define LOCK_SH 1 /* shared lock */
@@ -407,13 +415,14 @@ fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj,
407415
return NULL;
408416
}
409417
l.l_whence = whence;
410-
Py_BEGIN_ALLOW_THREADS
411-
ret = fcntl(fd, (code & LOCK_NB) ? F_SETLK : F_SETLKW, &l);
412-
Py_END_ALLOW_THREADS
418+
do {
419+
Py_BEGIN_ALLOW_THREADS
420+
ret = fcntl(fd, (code & LOCK_NB) ? F_SETLK : F_SETLKW, &l);
421+
Py_END_ALLOW_THREADS
422+
} while (ret == -1 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
413423
}
414424
if (ret < 0) {
415-
PyErr_SetFromErrno(PyExc_IOError);
416-
return NULL;
425+
return !async_err ? PyErr_SetFromErrno(PyExc_IOError) : NULL;
417426
}
418427
Py_RETURN_NONE;
419428
}

0 commit comments

Comments
 (0)