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

Skip to content

Commit 643cd68

Browse files
committed
Issue #13964: signal.sigtimedwait() timeout is now a float instead of a tuple
Add a private API to convert an int or float to a C timespec structure.
1 parent 1c13f84 commit 643cd68

7 files changed

Lines changed: 106 additions & 20 deletions

File tree

Doc/library/signal.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,11 @@ The :mod:`signal` module defines the following functions:
369369
.. versionadded:: 3.3
370370

371371

372-
.. function:: sigtimedwait(sigset, (timeout_sec, timeout_nsec))
372+
.. function:: sigtimedwait(sigset, timeout)
373373

374-
Like :func:`sigtimedwait`, but takes a tuple of ``(seconds, nanoseconds)``
375-
as an additional argument specifying a timeout. If both *timeout_sec* and
376-
*timeout_nsec* are specified as :const:`0`, a poll is performed. Returns
377-
:const:`None` if a timeout occurs.
374+
Like :func:`sigwaitinfo`, but takes an additional *timeout* argument
375+
specifying a timeout. If *timeout* is specified as :const:`0`, a poll is
376+
performed. Returns :const:`None` if a timeout occurs.
378377

379378
Availability: Unix (see the man page :manpage:`sigtimedwait(2)` for further
380379
information).

Include/pytime.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#define Py_PYTIME_H
44

55
#include "pyconfig.h" /* include for defines */
6+
#include "object.h"
67

78
/**************************************************************************
89
Symbols and macros to supply platform-independent interfaces to time related
@@ -37,6 +38,16 @@ do { \
3738
((tv_end.tv_sec - tv_start.tv_sec) + \
3839
(tv_end.tv_usec - tv_start.tv_usec) * 0.000001)
3940

41+
#ifndef Py_LIMITED_API
42+
/* Convert a number of seconds, int or float, to a timespec structure.
43+
nsec is always in the range [0; 999999999]. For example, -1.2 is converted
44+
to (-2, 800000000). */
45+
PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
46+
PyObject *obj,
47+
time_t *sec,
48+
long *nsec);
49+
#endif
50+
4051
/* Dummy to force linking. */
4152
PyAPI_FUNC(void) _PyTime_Init(void);
4253

Lib/test/test_signal.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ def test_sigtimedwait(self):
662662
self.wait_helper(signal.SIGALRM, '''
663663
def test(signum):
664664
signal.alarm(1)
665-
info = signal.sigtimedwait([signum], (10, 1000))
665+
info = signal.sigtimedwait([signum], 10.1000)
666666
if info.si_signo != signum:
667667
raise Exception('info.si_signo != %s' % signum)
668668
''')
@@ -675,7 +675,7 @@ def test_sigtimedwait_poll(self):
675675
def test(signum):
676676
import os
677677
os.kill(os.getpid(), signum)
678-
info = signal.sigtimedwait([signum], (0, 0))
678+
info = signal.sigtimedwait([signum], 0)
679679
if info.si_signo != signum:
680680
raise Exception('info.si_signo != %s' % signum)
681681
''')
@@ -685,7 +685,7 @@ def test(signum):
685685
def test_sigtimedwait_timeout(self):
686686
self.wait_helper(signal.SIGALRM, '''
687687
def test(signum):
688-
received = signal.sigtimedwait([signum], (1, 0))
688+
received = signal.sigtimedwait([signum], 1.0)
689689
if received is not None:
690690
raise Exception("received=%r" % (received,))
691691
''')
@@ -694,9 +694,7 @@ def test(signum):
694694
'need signal.sigtimedwait()')
695695
def test_sigtimedwait_negative_timeout(self):
696696
signum = signal.SIGALRM
697-
self.assertRaises(ValueError, signal.sigtimedwait, [signum], (-1, -1))
698-
self.assertRaises(ValueError, signal.sigtimedwait, [signum], (0, -1))
699-
self.assertRaises(ValueError, signal.sigtimedwait, [signum], (-1, 0))
697+
self.assertRaises(ValueError, signal.sigtimedwait, [signum], -1.0)
700698

701699
@unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
702700
'need signal.sigwaitinfo()')

Lib/test/test_time.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,12 +497,31 @@ class TestStrftime4dyear(_TestStrftimeYear, _Test4dYear):
497497
pass
498498

499499

500+
class TestPytime(unittest.TestCase):
501+
def test_timespec(self):
502+
from _testcapi import pytime_object_to_timespec
503+
for obj, timespec in (
504+
(0, (0, 0)),
505+
(-1, (-1, 0)),
506+
(-1.0, (-1, 0)),
507+
(-1e-9, (-1, 999999999)),
508+
(-1.2, (-2, 800000000)),
509+
(1.123456789, (1, 123456789)),
510+
):
511+
self.assertEqual(pytime_object_to_timespec(obj), timespec)
512+
513+
for invalid in (-(2 ** 100), -(2.0 ** 100.0), 2 ** 100, 2.0 ** 100.0):
514+
self.assertRaises(OverflowError, pytime_object_to_timespec, invalid)
515+
516+
517+
500518
def test_main():
501519
support.run_unittest(
502520
TimeTestCase,
503521
TestLocale,
504522
TestAsctime4dyear,
505-
TestStrftime4dyear)
523+
TestStrftime4dyear,
524+
TestPytime)
506525

507526
if __name__ == "__main__":
508527
test_main()

Modules/_testcapimodule.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2323,6 +2323,24 @@ run_in_subinterp(PyObject *self, PyObject *args)
23232323
return PyLong_FromLong(r);
23242324
}
23252325

2326+
static PyObject *
2327+
test_pytime_object_to_timespec(PyObject *self, PyObject *args)
2328+
{
2329+
PyObject *obj;
2330+
time_t sec;
2331+
long nsec;
2332+
if (!PyArg_ParseTuple(args, "O:pytime_object_to_timespec", &obj))
2333+
return NULL;
2334+
if (_PyTime_ObjectToTimespec(obj, &sec, &nsec) == -1)
2335+
return NULL;
2336+
#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
2337+
return Py_BuildValue("Ll", (PY_LONG_LONG)sec, nsec);
2338+
#else
2339+
assert(sizeof(time_t) <= sizeof(long));
2340+
return Py_BuildValue("ll", (long)sec, nsec);
2341+
#endif
2342+
}
2343+
23262344

23272345
static PyMethodDef TestMethods[] = {
23282346
{"raise_exception", raise_exception, METH_VARARGS},
@@ -2412,6 +2430,7 @@ static PyMethodDef TestMethods[] = {
24122430
METH_NOARGS},
24132431
{"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS},
24142432
{"run_in_subinterp", run_in_subinterp, METH_VARARGS},
2433+
{"pytime_object_to_timespec", test_pytime_object_to_timespec, METH_VARARGS},
24152434
{NULL, NULL} /* sentinel */
24162435
};
24172436

Modules/signalmodule.c

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -783,16 +783,11 @@ signal_sigtimedwait(PyObject *self, PyObject *args)
783783
siginfo_t si;
784784
int err;
785785

786-
if (!PyArg_ParseTuple(args, "OO:sigtimedwait", &signals, &timeout))
786+
if (!PyArg_ParseTuple(args, "OO:sigtimedwait",
787+
&signals, &timeout))
787788
return NULL;
788789

789-
if (!PyTuple_Check(timeout) || PyTuple_Size(timeout) != 2) {
790-
PyErr_SetString(PyExc_TypeError,
791-
"sigtimedwait() arg 2 must be a tuple "
792-
"(timeout_sec, timeout_nsec)");
793-
return NULL;
794-
} else if (!PyArg_ParseTuple(timeout, "ll:sigtimedwait",
795-
&(buf.tv_sec), &(buf.tv_nsec)))
790+
if (_PyTime_ObjectToTimespec(timeout, &buf.tv_sec, &buf.tv_nsec) == -1)
796791
return NULL;
797792

798793
if (buf.tv_sec < 0 || buf.tv_nsec < 0) {

Python/pytime.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,51 @@ _PyTime_gettimeofday(_PyTime_timeval *tp)
7070
#endif /* MS_WINDOWS */
7171
}
7272

73+
int
74+
_PyTime_ObjectToTimespec(PyObject *obj, time_t *sec, long *nsec)
75+
{
76+
if (PyFloat_Check(obj)) {
77+
double d, intpart, floatpart, err;
78+
79+
d = PyFloat_AsDouble(obj);
80+
floatpart = modf(d, &intpart);
81+
if (floatpart < 0) {
82+
floatpart = 1.0 + floatpart;
83+
intpart -= 1.0;
84+
}
85+
86+
*sec = (time_t)intpart;
87+
err = intpart - (double)*sec;
88+
if (err <= -1.0 || err >= 1.0)
89+
goto overflow;
90+
91+
floatpart *= 1e9;
92+
*nsec = (long)floatpart;
93+
return 0;
94+
}
95+
else {
96+
#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
97+
*sec = PyLong_AsLongLong(obj);
98+
#else
99+
assert(sizeof(time_t) <= sizeof(long));
100+
*sec = PyLong_AsLong(obj);
101+
#endif
102+
if (*sec == -1 && PyErr_Occurred()) {
103+
if (PyErr_ExceptionMatches(PyExc_OverflowError))
104+
goto overflow;
105+
else
106+
return -1;
107+
}
108+
*nsec = 0;
109+
return 0;
110+
}
111+
112+
overflow:
113+
PyErr_SetString(PyExc_OverflowError,
114+
"timestamp out of range for platform time_t");
115+
return -1;
116+
}
117+
73118
void
74119
_PyTime_Init()
75120
{

0 commit comments

Comments
 (0)