diff --git a/Include/pytime.h b/Include/pytime.h index d46b17c6293..9cf1170d70b 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -111,6 +111,40 @@ _PyTime_AddDouble(_PyTime_timeval *tv, double interval, Return 0 on success, raise an exception and return -1 on error. */ PyAPI_FUNC(int) _PyTime_Init(void); +/****************** NEW _PyTime_t API **********************/ + +#ifdef PY_INT64_T +typedef PY_INT64_T _PyTime_t; +#else +# error "_PyTime_t need signed 64-bit integer type" +#endif + +/* Convert a Python float or int to a timetamp. + Raise an exception and return -1 on error, return 0 on success. */ +PyAPI_FUNC(int) _PyTime_FromObject(_PyTime_t *t, + PyObject *obj, + _PyTime_round_t round); + +/* Convert timestamp to a number of milliseconds (10^-3 seconds). */ +PyAPI_FUNC(_PyTime_t) +_PyTime_AsMilliseconds(_PyTime_t t, + _PyTime_round_t round); + +/* Convert a timestamp to a timeval structure. */ +PyAPI_FUNC(int) _PyTime_AsTimeval(_PyTime_t t, + struct timeval *tv, + _PyTime_round_t round); + +/* Get the time of a monotonic clock, i.e. a clock that cannot go backwards. + The clock is not affected by system clock updates. The reference point of + the returned value is undefined, so that only the difference between the + results of consecutive calls is valid. + + The function cannot fail. _PyTime_Init() ensures that a monotonic clock + is available and works. */ +PyAPI_FUNC(_PyTime_t) _PyTime_GetMonotonicClock(void); + + #ifdef __cplusplus } #endif diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 0c44d1259b7..e44e082c262 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -31,7 +31,7 @@ #endif /* !__WATCOMC__ || __QNX__ */ /* Forward declarations */ -static int floatsleep(double); +static int pysleep(_PyTime_t); static PyObject* floattime(_Py_clock_info_t *info); static PyObject * @@ -218,17 +218,17 @@ Return the resolution (precision) of the specified clock clk_id."); #endif /* HAVE_CLOCK_GETTIME */ static PyObject * -time_sleep(PyObject *self, PyObject *args) +time_sleep(PyObject *self, PyObject *obj) { - double secs; - if (!PyArg_ParseTuple(args, "d:sleep", &secs)) + _PyTime_t secs; + if (_PyTime_FromObject(&secs, obj, _PyTime_ROUND_UP)) return NULL; if (secs < 0) { PyErr_SetString(PyExc_ValueError, "sleep length must be non-negative"); return NULL; } - if (floatsleep(secs) != 0) + if (pysleep(secs) != 0) return NULL; Py_INCREF(Py_None); return Py_None; @@ -1258,7 +1258,7 @@ static PyMethodDef time_methods[] = { {"clock_settime", time_clock_settime, METH_VARARGS, clock_settime_doc}, {"clock_getres", time_clock_getres, METH_VARARGS, clock_getres_doc}, #endif - {"sleep", time_sleep, METH_VARARGS, sleep_doc}, + {"sleep", time_sleep, METH_O, sleep_doc}, {"gmtime", time_gmtime, METH_VARARGS, gmtime_doc}, {"localtime", time_localtime, METH_VARARGS, localtime_doc}, {"asctime", time_asctime, METH_VARARGS, asctime_doc}, @@ -1379,34 +1379,30 @@ floattime(_Py_clock_info_t *info) } -/* Implement floatsleep() for various platforms. +/* Implement pysleep() for various platforms. When interrupted (or when another error occurs), return -1 and set an exception; else return 0. */ static int -floatsleep(double secs) +pysleep(_PyTime_t secs) { - _PyTime_timeval deadline, monotonic; + _PyTime_t deadline, monotonic; #ifndef MS_WINDOWS struct timeval timeout; - double frac; int err = 0; #else - double millisecs; + _PyTime_t millisecs; unsigned long ul_millis; DWORD rc; HANDLE hInterruptEvent; #endif - _PyTime_monotonic(&deadline); - _PyTime_AddDouble(&deadline, secs, _PyTime_ROUND_UP); + deadline = _PyTime_GetMonotonicClock() + secs; do { #ifndef MS_WINDOWS - frac = fmod(secs, 1.0); - secs = floor(secs); - timeout.tv_sec = (long)secs; - timeout.tv_usec = (long)(frac*1e6); + if (_PyTime_AsTimeval(secs, &timeout, _PyTime_ROUND_UP) < 0) + return -1; Py_BEGIN_ALLOW_THREADS err = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &timeout); @@ -1420,7 +1416,7 @@ floatsleep(double secs) return -1; } #else - millisecs = secs * 1000.0; + millisecs = _PyTime_AsMilliseconds(secs, _PyTime_ROUND_UP); if (millisecs > (double)ULONG_MAX) { PyErr_SetString(PyExc_OverflowError, "sleep length is too large"); @@ -1453,9 +1449,9 @@ floatsleep(double secs) if (PyErr_CheckSignals()) return -1; - _PyTime_monotonic(&monotonic); - secs = _PyTime_INTERVAL(monotonic, deadline); - if (secs <= 0.0) + monotonic = _PyTime_GetMonotonicClock(); + secs = deadline - monotonic; + if (secs <= 00) break; /* retry with the recomputed delay */ } while (1); diff --git a/Python/pytime.c b/Python/pytime.c index 578569d4dd1..52bd958dbbe 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -7,12 +7,22 @@ #include /* mach_absolute_time(), mach_timebase_info() */ #endif +/* To millisecond (10^-3) */ #define SEC_TO_MS 1000 -#define MS_TO_US 1000 -#define US_TO_NS 1000 +/* To microseconds (10^-6) */ +#define MS_TO_US 1000 #define SEC_TO_US (SEC_TO_MS * MS_TO_US) +/* To nanoseconds (10^-9) */ +#define US_TO_NS 1000 +#define MS_TO_NS (MS_TO_US * US_TO_NS) +#define SEC_TO_NS (SEC_TO_MS * MS_TO_NS) + +#ifdef MS_WINDOWS +static OSVERSIONINFOEX winver; +#endif + static int pygettimeofday(_PyTime_timeval *tp, _Py_clock_info_t *info, int raise) { @@ -390,10 +400,305 @@ _PyTime_AddDouble(_PyTime_timeval *tv, double interval, _PyTime_round_t round) tv->tv_usec %= SEC_TO_US; } +/****************** NEW _PyTime_t API **********************/ + +static void +_PyTime_overflow(void) +{ + PyErr_SetString(PyExc_OverflowError, + "timestamp too large to convert to C _PyTime_t"); +} + +#if !defined(MS_WINDOWS) && !defined(__APPLE__) +static int +_PyTime_FromTimespec(_PyTime_t *tp, struct timespec *ts) +{ + _PyTime_t t; + t = (_PyTime_t)ts->tv_sec * SEC_TO_NS; + if (t / SEC_TO_NS != ts->tv_sec) { + _PyTime_overflow(); + return -1; + } + + t += ts->tv_nsec; + + *tp = t; + return 0; +} +#endif + +int +_PyTime_FromObject(_PyTime_t *t, PyObject *obj, _PyTime_round_t round) +{ + if (PyFloat_Check(obj)) { + double d, err; + + /* convert to a number of nanoseconds */ + d = PyFloat_AsDouble(obj); + d *= 1e9; + + /* FIXME: use sign */ + if (round == _PyTime_ROUND_UP) + d = ceil(d); + else + d = floor(d); + + *t = (_PyTime_t)d; + err = d - (double)*t; + if (fabs(err) >= 1.0) { + _PyTime_overflow(); + return -1; + } + return 0; + } + else { +#ifdef HAVE_LONG_LONG + PY_LONG_LONG sec; + sec = PyLong_AsLongLong(obj); + assert(sizeof(PY_LONG_LONG) <= sizeof(_PyTime_t)); +#else + long sec; + sec = PyLong_AsLong(obj); + assert(sizeof(PY_LONG_LONG) <= sizeof(_PyTime_t)); +#endif + if (sec == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) + _PyTime_overflow(); + return -1; + } + *t = sec * SEC_TO_NS; + if (*t / SEC_TO_NS != sec) { + _PyTime_overflow(); + return -1; + } + return 0; + } +} + +static _PyTime_t +_PyTime_Multiply(_PyTime_t t, unsigned int multiply, _PyTime_round_t round) +{ + _PyTime_t k; + if (multiply < SEC_TO_NS) { + k = SEC_TO_NS / multiply; + if (round == _PyTime_ROUND_UP) + return (t + k - 1) / k; + else + return t / k; + } + else { + k = multiply / SEC_TO_NS; + return t * k; + } +} + +_PyTime_t +_PyTime_AsMilliseconds(_PyTime_t t, _PyTime_round_t round) +{ + return _PyTime_Multiply(t, 1000, round); +} + +int +_PyTime_AsTimeval(_PyTime_t t, struct timeval *tv, _PyTime_round_t round) +{ + _PyTime_t secs, ns; + + secs = t / SEC_TO_NS; + ns = t % SEC_TO_NS; + +#ifdef MS_WINDOWS + /* On Windows, timeval.tv_sec is a long (32 bit), + whereas time_t can be 64-bit. */ + assert(sizeof(tv->tv_sec) == sizeof(long)); +#if SIZEOF_TIME_T > SIZEOF_LONG + if (secs > LONG_MAX) { + _PyTime_overflow(); + return -1; + } +#endif + tv->tv_sec = (long)secs; +#else + /* On OpenBSD 5.4, timeval.tv_sec is a long. + Example: long is 64-bit, whereas time_t is 32-bit. */ + tv->tv_sec = secs; + if ((_PyTime_t)tv->tv_sec != secs) { + _PyTime_overflow(); + return -1; + } +#endif + + if (round == _PyTime_ROUND_UP) + tv->tv_usec = (int)((ns + US_TO_NS - 1) / US_TO_NS); + else + tv->tv_usec = (int)(ns / US_TO_NS); + return 0; +} + +static int +pymonotonic_new(_PyTime_t *tp, _Py_clock_info_t *info, int raise) +{ +#ifdef Py_DEBUG + static int last_set = 0; + static _PyTime_t last = 0; +#endif +#if defined(MS_WINDOWS) + static ULONGLONG (*GetTickCount64) (void) = NULL; + static ULONGLONG (CALLBACK *Py_GetTickCount64)(void); + static int has_gettickcount64 = -1; + ULONGLONG result; + + assert(info == NULL || raise); + + if (has_gettickcount64 == -1) { + /* GetTickCount64() was added to Windows Vista */ + has_gettickcount64 = (winver.dwMajorVersion >= 6); + if (has_gettickcount64) { + HINSTANCE hKernel32; + hKernel32 = GetModuleHandleW(L"KERNEL32"); + *(FARPROC*)&Py_GetTickCount64 = GetProcAddress(hKernel32, + "GetTickCount64"); + assert(Py_GetTickCount64 != NULL); + } + } + + if (has_gettickcount64) { + result = Py_GetTickCount64(); + } + else { + static DWORD last_ticks = 0; + static DWORD n_overflow = 0; + DWORD ticks; + + ticks = GetTickCount(); + if (ticks < last_ticks) + n_overflow++; + last_ticks = ticks; + + result = (ULONGLONG)n_overflow << 32; + result += ticks; + } + + *tp = result * MS_TO_NS; + if (*tp / MS_TO_NS != result) { + if (raise) { + _PyTime_overflow(); + return -1; + } + /* Hello, time traveler! */ + assert(0); + } + + if (info) { + DWORD timeAdjustment, timeIncrement; + BOOL isTimeAdjustmentDisabled, ok; + if (has_gettickcount64) + info->implementation = "GetTickCount64()"; + else + info->implementation = "GetTickCount()"; + info->monotonic = 1; + ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, + &isTimeAdjustmentDisabled); + if (!ok) { + PyErr_SetFromWindowsErr(0); + return -1; + } + info->resolution = timeIncrement * 1e-7; + info->adjustable = 0; + } + +#elif defined(__APPLE__) + static mach_timebase_info_data_t timebase; + uint64_t time; + + if (timebase.denom == 0) { + /* According to the Technical Q&A QA1398, mach_timebase_info() cannot + fail: https://developer.apple.com/library/mac/#qa/qa1398/ */ + (void)mach_timebase_info(&timebase); + } + + time = mach_absolute_time(); + + /* apply timebase factor */ + time *= timebase.numer; + time /= timebase.denom; + + *tp = time; + + if (info) { + info->implementation = "mach_absolute_time()"; + info->resolution = (double)timebase.numer / timebase.denom * 1e-9; + info->monotonic = 1; + info->adjustable = 0; + } + +#else + struct timespec ts; +#ifdef CLOCK_HIGHRES + const clockid_t clk_id = CLOCK_HIGHRES; + const char *implementation = "clock_gettime(CLOCK_HIGHRES)"; +#else + const clockid_t clk_id = CLOCK_MONOTONIC; + const char *implementation = "clock_gettime(CLOCK_MONOTONIC)"; +#endif + + assert(info == NULL || raise); + + if (clock_gettime(clk_id, &ts) != 0) { + if (raise) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + return -1; + } + + if (info) { + struct timespec res; + info->monotonic = 1; + info->implementation = implementation; + info->adjustable = 0; + if (clock_getres(clk_id, &res) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + } + if (_PyTime_FromTimespec(tp, &ts) < 0) + return -1; +#endif +#ifdef Py_DEBUG + /* monotonic clock cannot go backward */ + assert(!last_set || last <= *tp); + last = *tp; + last_set = 1; +#endif + return 0; +} + +_PyTime_t +_PyTime_GetMonotonicClock(void) +{ + _PyTime_t t; + if (pymonotonic_new(&t, NULL, 0) < 0) { + /* cannot happen, _PyTime_Init() checks that pymonotonic_new() works */ + assert(0); + t = 0; + } + return t; +} + int _PyTime_Init(void) { _PyTime_timeval tv; + _PyTime_t t; + +#ifdef MS_WINDOWS + winver.dwOSVersionInfoSize = sizeof(winver); + if (!GetVersionEx((OSVERSIONINFO*)&winver)) { + PyErr_SetFromWindowsErr(0); + return -1; + } +#endif /* ensure that the system clock works */ if (_PyTime_gettimeofday_info(&tv, NULL) < 0) @@ -402,5 +707,9 @@ _PyTime_Init(void) /* ensure that the operating system provides a monotonic clock */ if (_PyTime_monotonic_info(&tv, NULL) < 0) return -1; + + /* ensure that the operating system provides a monotonic clock */ + if (pymonotonic_new(&t, NULL, 1) < 0) + return -1; return 0; }