Skip to content

Commit 34dc0f4

Browse files
committed
Issue python#22117: The signal modules uses the new _PyTime_t API
* Add _PyTime_AsTimespec() * Add unit tests for _PyTime_AsTimespec()
1 parent 7181dec commit 34dc0f4

5 files changed

Lines changed: 88 additions & 15 deletions

File tree

Include/pytime.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t,
145145
struct timeval *tv,
146146
_PyTime_round_t round);
147147

148+
#ifdef HAVE_CLOCK_GETTIME
149+
/* Convert a timestamp to a timespec structure (nanosecond resolution).
150+
Raise an exception and return -1 on error, return 0 on success. */
151+
PyAPI_FUNC(int) _PyTime_AsTimespec(_PyTime_t t, struct timespec *ts);
152+
#endif
153+
148154
/* Get the current time from the system clock.
149155
* Fill clock information if info is not NULL.
150156
* Raise an exception and return -1 on error, return 0 on success.

Lib/test/test_time.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
import threading
1111
except ImportError:
1212
threading = None
13+
try:
14+
import _testcapi
15+
except ImportError:
16+
_testcapi = None
17+
1318

1419
# Max year is only limited by the size of C int.
1520
SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4
@@ -768,7 +773,8 @@ def test_short_times(self):
768773
self.assertIs(lt.tm_zone, None)
769774

770775

771-
@support.cpython_only
776+
@unittest.skipUnless(_testcapi is not None,
777+
'need the _testcapi module')
772778
class TestPyTime_t(unittest.TestCase):
773779
def test_FromSecondsObject(self):
774780
from _testcapi import PyTime_FromSecondsObject
@@ -896,6 +902,27 @@ def test_AsSecondsDouble(self):
896902
self.assertEqual(PyTime_AsSecondsDouble(nanoseconds),
897903
seconds)
898904

905+
@unittest.skipUnless(hasattr(_testcapi, 'PyTime_AsTimespec'),
906+
'need _testcapi.PyTime_AsTimespec')
907+
def test_timespec(self):
908+
from _testcapi import PyTime_AsTimespec
909+
for ns, ts in (
910+
# nanoseconds
911+
(0, (0, 0)),
912+
(1, (0, 1)),
913+
(-1, (-1, 999999999)),
914+
915+
# seconds
916+
(2 * SEC_TO_NS, (2, 0)),
917+
(-3 * SEC_TO_NS, (-3, 0)),
918+
919+
# seconds + nanoseconds
920+
(1234567890, (1, 234567890)),
921+
(-1234567890, (-2, 765432110)),
922+
):
923+
with self.subTest(nanoseconds=ns, timespec=ts):
924+
self.assertEqual(PyTime_AsTimespec(ns), ts)
925+
899926

900927
if __name__ == "__main__":
901928
unittest.main()

Modules/_testcapimodule.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3408,6 +3408,23 @@ test_pytime_assecondsdouble(PyObject *self, PyObject *args)
34083408
return PyFloat_FromDouble(d);
34093409
}
34103410

3411+
#ifdef HAVE_CLOCK_GETTIME
3412+
static PyObject *
3413+
test_PyTime_AsTimespec(PyObject *self, PyObject *args)
3414+
{
3415+
PY_LONG_LONG ns;
3416+
_PyTime_t t;
3417+
struct timespec ts;
3418+
3419+
if (!PyArg_ParseTuple(args, "L", &ns))
3420+
return NULL;
3421+
t = _PyTime_FromNanoseconds(ns);
3422+
if (_PyTime_AsTimespec(t, &ts) == -1)
3423+
return NULL;
3424+
return Py_BuildValue("Nl", _PyLong_FromTime_t(ts.tv_sec), ts.tv_nsec);
3425+
}
3426+
#endif
3427+
34113428

34123429
static PyMethodDef TestMethods[] = {
34133430
{"raise_exception", raise_exception, METH_VARARGS},
@@ -3573,6 +3590,9 @@ static PyMethodDef TestMethods[] = {
35733590
return_result_with_error, METH_NOARGS},
35743591
{"PyTime_FromSecondsObject", test_pytime_fromsecondsobject, METH_VARARGS},
35753592
{"PyTime_AsSecondsDouble", test_pytime_assecondsdouble, METH_VARARGS},
3593+
#ifdef HAVE_CLOCK_GETTIME
3594+
{"PyTime_AsTimespec", test_PyTime_AsTimespec, METH_VARARGS},
3595+
#endif
35763596
{NULL, NULL} /* sentinel */
35773597
};
35783598

Modules/signalmodule.c

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -966,16 +966,18 @@ Returns a struct_siginfo containing information about the signal.");
966966
static PyObject *
967967
signal_sigtimedwait(PyObject *self, PyObject *args)
968968
{
969-
PyObject *signals;
970-
double timeout, frac;
969+
PyObject *signals, *timeout_obj;
971970
struct timespec ts;
972971
sigset_t set;
973972
siginfo_t si;
974973
int res;
975-
_PyTime_timeval deadline, monotonic;
974+
_PyTime_t timeout, deadline, monotonic;
975+
976+
if (!PyArg_ParseTuple(args, "OO:sigtimedwait",
977+
&signals, &timeout_obj))
978+
return NULL;
976979

977-
if (!PyArg_ParseTuple(args, "Od:sigtimedwait",
978-
&signals, &timeout))
980+
if (_PyTime_FromSecondsObject(&timeout, timeout_obj, _PyTime_ROUND_UP) < 0)
979981
return NULL;
980982

981983
if (timeout < 0) {
@@ -986,14 +988,11 @@ signal_sigtimedwait(PyObject *self, PyObject *args)
986988
if (iterable_to_sigset(signals, &set))
987989
return NULL;
988990

989-
_PyTime_monotonic(&deadline);
990-
_PyTime_AddDouble(&deadline, timeout, _PyTime_ROUND_UP);
991+
deadline = _PyTime_GetMonotonicClock() + timeout;
991992

992993
do {
993-
frac = fmod(timeout, 1.0);
994-
timeout = floor(timeout);
995-
ts.tv_sec = (long)timeout;
996-
ts.tv_nsec = (long)(frac*1e9);
994+
if (_PyTime_AsTimespec(timeout, &ts) < 0)
995+
return NULL;
997996

998997
Py_BEGIN_ALLOW_THREADS
999998
res = sigtimedwait(&set, &si, &ts);
@@ -1013,9 +1012,9 @@ signal_sigtimedwait(PyObject *self, PyObject *args)
10131012
if (PyErr_CheckSignals())
10141013
return NULL;
10151014

1016-
_PyTime_monotonic(&monotonic);
1017-
timeout = _PyTime_INTERVAL(monotonic, deadline);
1018-
if (timeout <= 0.0)
1015+
monotonic = _PyTime_GetMonotonicClock();
1016+
timeout = deadline - monotonic;
1017+
if (timeout <= 0)
10191018
break;
10201019
} while (1);
10211020

Python/pytime.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,27 @@ _PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round)
572572
return 0;
573573
}
574574

575+
#ifdef HAVE_CLOCK_GETTIME
576+
int
577+
_PyTime_AsTimespec(_PyTime_t t, struct timespec *ts)
578+
{
579+
_PyTime_t sec, nsec;
580+
sec = t / SEC_TO_NS;
581+
nsec = t % SEC_TO_NS;
582+
if (nsec < 0) {
583+
nsec += SEC_TO_NS;
584+
sec -= 1;
585+
}
586+
ts->tv_sec = (time_t)sec;
587+
if ((_PyTime_t)ts->tv_sec != sec) {
588+
_PyTime_overflow();
589+
return -1;
590+
}
591+
ts->tv_nsec = nsec;
592+
return 0;
593+
}
594+
#endif
595+
575596
static int
576597
pygettimeofday_new(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
577598
{

0 commit comments

Comments
 (0)