From ae58649721ec898ea4a101b0861e16fff3511cfa Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 2 Sep 2014 23:18:25 +0200 Subject: Issue #22043: time.monotonic() is now always available threading.Lock.acquire(), threading.RLock.acquire() and socket operations now use a monotonic clock, instead of the system clock, when a timeout is used. --- Doc/library/time.rst | 4 +- Doc/whatsnew/3.5.rst | 5 ++ Include/pytime.h | 18 +++++ Lib/queue.py | 5 +- Lib/sched.py | 5 +- Lib/socketserver.py | 5 +- Lib/subprocess.py | 5 +- Lib/telnetlib.py | 5 +- Lib/test/test_selectors.py | 5 +- Lib/threading.py | 5 +- Lib/trace.py | 5 +- Misc/NEWS | 5 ++ Modules/_threadmodule.c | 4 +- Modules/gcmodule.c | 6 +- Modules/socketmodule.c | 4 +- Modules/timemodule.c | 141 ++---------------------------------- Python/pytime.c | 175 +++++++++++++++++++++++++++++++++++++++++++++ 17 files changed, 226 insertions(+), 176 deletions(-) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 64b5e04..7c3e4f0 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -315,9 +315,9 @@ The module defines the following functions and data items: processes running for more than 49 days. On more recent versions of Windows and on other operating systems, :func:`monotonic` is system-wide. - Availability: Windows, Mac OS X, Linux, FreeBSD, OpenBSD, Solaris. - .. versionadded:: 3.3 + .. versionchanged:: 3.5 + The function is now always available. .. function:: perf_counter() diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 28c2447..43972b0 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -238,6 +238,11 @@ socket :meth:`socket.socket.send`. (contributed by Giampaolo Rodola' in :issue:`17552`) +time +---- + +The :func:`time.monotonic` function is now always available (:issue`22043`). + wsgiref ------- diff --git a/Include/pytime.h b/Include/pytime.h index 0f969b3..7a14456 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -91,6 +91,24 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec( long *nsec, _PyTime_round_t); +/* 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 never fails. _PyTime_Init() ensures that a monotonic clock + is available and works. */ +PyAPI_FUNC(void) _PyTime_monotonic( + _PyTime_timeval *tp); + +/* Similar to _PyTime_monotonic(), fill also info (if set) with information of + the function used to get the time. + + Return 0 on success, raise an exception and return -1 on error. */ +PyAPI_FUNC(int) _PyTime_monotonic_info( + _PyTime_timeval *tp, + _Py_clock_info_t *info); + /* Initialize time. Return 0 on success, raise an exception and return -1 on error. */ PyAPI_FUNC(int) _PyTime_Init(void); diff --git a/Lib/queue.py b/Lib/queue.py index 3cee36b..572425e 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -6,10 +6,7 @@ except ImportError: import dummy_threading as threading from collections import deque from heapq import heappush, heappop -try: - from time import monotonic as time -except ImportError: - from time import time +from time import monotonic as time __all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue'] diff --git a/Lib/sched.py b/Lib/sched.py index 2e6b00a..409d126 100644 --- a/Lib/sched.py +++ b/Lib/sched.py @@ -35,10 +35,7 @@ try: import threading except ImportError: import dummy_threading as threading -try: - from time import monotonic as _time -except ImportError: - from time import time as _time +from time import monotonic as _time __all__ = ["scheduler"] diff --git a/Lib/socketserver.py b/Lib/socketserver.py index b585640..3d58590 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -136,10 +136,7 @@ try: import threading except ImportError: import dummy_threading as threading -try: - from time import monotonic as time -except ImportError: - from time import time as time +from time import monotonic as time __all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer", "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler", diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 6dfa40b..bb7d0dc 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -365,10 +365,7 @@ import signal import builtins import warnings import errno -try: - from time import monotonic as _time -except ImportError: - from time import time as _time +from time import monotonic as _time # Exception classes used by this module. class SubprocessError(Exception): pass diff --git a/Lib/telnetlib.py b/Lib/telnetlib.py index 0cacac8..eebb952 100644 --- a/Lib/telnetlib.py +++ b/Lib/telnetlib.py @@ -36,10 +36,7 @@ To do: import sys import socket import selectors -try: - from time import monotonic as _time -except ImportError: - from time import time as _time +from time import monotonic as _time __all__ = ["Telnet"] diff --git a/Lib/test/test_selectors.py b/Lib/test/test_selectors.py index c2eba15..544ea7b 100644 --- a/Lib/test/test_selectors.py +++ b/Lib/test/test_selectors.py @@ -8,10 +8,7 @@ from test import support from time import sleep import unittest import unittest.mock -try: - from time import monotonic as time -except ImportError: - from time import time as time +from time import monotonic as time try: import resource except ImportError: diff --git a/Lib/threading.py b/Lib/threading.py index 66620a9..2ce037e 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -3,10 +3,7 @@ import sys as _sys import _thread -try: - from time import monotonic as _time -except ImportError: - from time import time as _time +from time import monotonic as _time from traceback import format_exc as _format_exc from _weakrefset import WeakSet from itertools import islice as _islice diff --git a/Lib/trace.py b/Lib/trace.py index 1c888ac..fe84973 100755 --- a/Lib/trace.py +++ b/Lib/trace.py @@ -59,10 +59,7 @@ import gc import dis import pickle from warnings import warn as _warn -try: - from time import monotonic as _time -except ImportError: - from time import time as _time +from time import monotonic as _time try: import threading diff --git a/Misc/NEWS b/Misc/NEWS index a02e8a6..46c35ed 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -129,6 +129,11 @@ Core and Builtins Library ------- +- Issue #22043: time.monotonic() is now always available. + ``threading.Lock.acquire()``, ``threading.RLock.acquire()`` and socket + operations now use a monotonic clock, instead of the system clock, when a + timeout is used. + - Issue #21527: Add a default number of workers to ThreadPoolExecutor equal to 5 times the number of CPUs. Patch by Claudiu Popa. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index b68c177..8f59e03 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -57,7 +57,7 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) if (microseconds > 0) { - _PyTime_gettimeofday(&endtime); + _PyTime_monotonic(&endtime); endtime.tv_sec += microseconds / (1000 * 1000); endtime.tv_usec += microseconds % (1000 * 1000); } @@ -83,7 +83,7 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) /* If we're using a timeout, recompute the timeout after processing * signals, since those can take time. */ if (microseconds > 0) { - _PyTime_gettimeofday(&curtime); + _PyTime_monotonic(&curtime); microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 + (endtime.tv_usec - curtime.tv_usec)); diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 5e8e17b..142687b 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -25,7 +25,7 @@ #include "Python.h" #include "frameobject.h" /* for PyFrame_ClearFreeList */ -#include "pytime.h" /* for _PyTime_gettimeofday, _PyTime_INTERVAL */ +#include "pytime.h" /* for _PyTime_monotonic, _PyTime_INTERVAL */ /* Get an object's GC head */ #define AS_GC(o) ((PyGC_Head *)(o)-1) @@ -919,7 +919,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, for (i = 0; i < NUM_GENERATIONS; i++) PySys_FormatStderr(" %zd", gc_list_size(GEN_HEAD(i))); - _PyTime_gettimeofday(&t1); + _PyTime_monotonic(&t1); PySys_WriteStderr("\n"); } @@ -1025,7 +1025,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, } if (debug & DEBUG_STATS) { _PyTime_timeval t2; - _PyTime_gettimeofday(&t2); + _PyTime_monotonic(&t2); if (m == 0 && n == 0) PySys_WriteStderr("gc: done"); diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index abadd8a..db69d6e 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -680,7 +680,7 @@ internal_select(PySocketSockObject *s, int writing) double interval = s->sock_timeout; \ int has_timeout = s->sock_timeout > 0.0; \ if (has_timeout) { \ - _PyTime_gettimeofday(&now); \ + _PyTime_monotonic(&now); \ deadline = now; \ _PyTime_ADD_SECONDS(deadline, s->sock_timeout); \ } \ @@ -691,7 +691,7 @@ internal_select(PySocketSockObject *s, int writing) if (!has_timeout || \ (!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \ break; \ - _PyTime_gettimeofday(&now); \ + _PyTime_monotonic(&now); \ interval = _PyTime_INTERVAL(now, deadline); \ } \ } \ diff --git a/Modules/timemodule.c b/Modules/timemodule.c index c90d8f9..16f4f6d 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -37,10 +37,6 @@ #endif /* MS_WINDOWS */ #endif /* !__WATCOMC__ || __QNX__ */ -#if defined(__APPLE__) -#include -#endif - /* Forward declarations */ static int floatsleep(double); static PyObject* floattime(_Py_clock_info_t *info); @@ -899,122 +895,15 @@ the local timezone used by methods such as localtime, but this behaviour\n\ should not be relied on."); #endif /* HAVE_WORKING_TZSET */ -#if defined(MS_WINDOWS) || defined(__APPLE__) \ - || (defined(HAVE_CLOCK_GETTIME) \ - && (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC))) -#define PYMONOTONIC -#endif - -#ifdef PYMONOTONIC -static PyObject* +static PyObject * pymonotonic(_Py_clock_info_t *info) { -#if defined(MS_WINDOWS) - static ULONGLONG (*GetTickCount64) (void) = NULL; - static ULONGLONG (CALLBACK *Py_GetTickCount64)(void); - static int has_getickcount64 = -1; - double result; - - if (has_getickcount64 == -1) { - /* GetTickCount64() was added to Windows Vista */ - if (winver.dwMajorVersion >= 6) { - HINSTANCE hKernel32; - hKernel32 = GetModuleHandleW(L"KERNEL32"); - *(FARPROC*)&Py_GetTickCount64 = GetProcAddress(hKernel32, - "GetTickCount64"); - has_getickcount64 = (Py_GetTickCount64 != NULL); - } - else - has_getickcount64 = 0; - } - - if (has_getickcount64) { - ULONGLONG ticks; - ticks = Py_GetTickCount64(); - result = (double)ticks * 1e-3; - } - 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 = ldexp(n_overflow, 32); - result += ticks; - result *= 1e-3; - } - - if (info) { - DWORD timeAdjustment, timeIncrement; - BOOL isTimeAdjustmentDisabled, ok; - if (has_getickcount64) - info->implementation = "GetTickCount64()"; - else - info->implementation = "GetTickCount()"; - info->monotonic = 1; - ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, - &isTimeAdjustmentDisabled); - if (!ok) { - PyErr_SetFromWindowsErr(0); - return NULL; - } - info->resolution = timeIncrement * 1e-7; - info->adjustable = 0; - } - return PyFloat_FromDouble(result); - -#elif defined(__APPLE__) - static mach_timebase_info_data_t timebase; - uint64_t time; - double secs; - - 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(); - secs = (double)time * timebase.numer / timebase.denom * 1e-9; - if (info) { - info->implementation = "mach_absolute_time()"; - info->resolution = (double)timebase.numer / timebase.denom * 1e-9; - info->monotonic = 1; - info->adjustable = 0; - } - return PyFloat_FromDouble(secs); - -#elif defined(HAVE_CLOCK_GETTIME) && (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC)) - struct timespec tp; -#ifdef CLOCK_HIGHRES - const clockid_t clk_id = CLOCK_HIGHRES; - const char *function = "clock_gettime(CLOCK_HIGHRES)"; -#else - const clockid_t clk_id = CLOCK_MONOTONIC; - const char *function = "clock_gettime(CLOCK_MONOTONIC)"; -#endif - - if (clock_gettime(clk_id, &tp) != 0) { - PyErr_SetFromErrno(PyExc_OSError); + _PyTime_timeval tv; + if (_PyTime_monotonic_info(&tv, info) < 0) { + assert(info != NULL); return NULL; } - - if (info) { - struct timespec res; - info->monotonic = 1; - info->implementation = function; - info->adjustable = 0; - if (clock_getres(clk_id, &res) == 0) - info->resolution = res.tv_sec + res.tv_nsec * 1e-9; - else - info->resolution = 1e-9; - } - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); -#endif + return PyFloat_FromDouble((double)tv.tv_sec + tv.tv_usec * 1e-6); } static PyObject * @@ -1027,7 +916,6 @@ PyDoc_STRVAR(monotonic_doc, "monotonic() -> float\n\ \n\ Monotonic clock, cannot go backward."); -#endif /* PYMONOTONIC */ static PyObject* perf_counter(_Py_clock_info_t *info) @@ -1035,20 +923,7 @@ perf_counter(_Py_clock_info_t *info) #ifdef WIN32_PERF_COUNTER return win_perf_counter(info); #else - -#ifdef PYMONOTONIC - static int use_monotonic = 1; - - if (use_monotonic) { - PyObject *res = pymonotonic(info); - if (res != NULL) - return res; - use_monotonic = 0; - PyErr_Clear(); - } -#endif - return floattime(info); - + return pymonotonic(info); #endif } @@ -1216,10 +1091,8 @@ time_get_clock_info(PyObject *self, PyObject *args) else if (strcmp(name, "clock") == 0) obj = pyclock(&info); #endif -#ifdef PYMONOTONIC else if (strcmp(name, "monotonic") == 0) obj = pymonotonic(&info); -#endif else if (strcmp(name, "perf_counter") == 0) obj = perf_counter(&info); else if (strcmp(name, "process_time") == 0) @@ -1411,9 +1284,7 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_WORKING_TZSET {"tzset", time_tzset, METH_NOARGS, tzset_doc}, #endif -#ifdef PYMONOTONIC {"monotonic", time_monotonic, METH_NOARGS, monotonic_doc}, -#endif {"process_time", time_process_time, METH_NOARGS, process_time_doc}, {"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc}, {"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc}, diff --git a/Python/pytime.c b/Python/pytime.c index 395a432..9964195 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -3,6 +3,14 @@ #include #endif +#if defined(__APPLE__) +#include /* mach_absolute_time(), mach_timebase_info() */ +#endif + +#ifdef MS_WINDOWS +static OSVERSIONINFOEX winver; +#endif + static int pygettimeofday(_PyTime_timeval *tp, _Py_clock_info_t *info, int raise) { @@ -109,6 +117,160 @@ _PyTime_gettimeofday_info(_PyTime_timeval *tp, _Py_clock_info_t *info) return pygettimeofday(tp, info, 1); } +static int +pymonotonic(_PyTime_timeval *tp, _Py_clock_info_t *info, int raise) +{ +#ifdef Py_DEBUG + static _PyTime_timeval last = {-1, -1}; +#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->tv_sec = result / 1000; + tp->tv_usec = (result % 1000) * 1000; + + 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(); + + /* nanoseconds => microseconds */ + time /= 1000; + /* apply timebase factor */ + time *= timebase.numer; + time /= timebase.denom; + tp->tv_sec = time / (1000 * 1000); + tp->tv_usec = time % (1000 * 1000); + + 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; + } + tp->tv_sec = 0; + tp->tv_usec = 0; + 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; + } + tp->tv_sec = ts.tv_sec; + tp->tv_usec = ts.tv_nsec / 1000; +#endif + assert(0 <= tp->tv_usec && tp->tv_usec < 1000 * 1000); +#ifdef Py_DEBUG + /* monotonic clock cannot go backward */ + assert(tp->tv_sec > last.tv_sec + || (tp->tv_sec == last.tv_sec && tp->tv_usec >= last.tv_usec)); + last = *tp; +#endif + return 0; +} + +void +_PyTime_monotonic(_PyTime_timeval *tp) +{ + if (pymonotonic(tp, NULL, 0) < 0) { + /* cannot happen, _PyTime_Init() checks that pymonotonic() works */ + assert(0); + tp->tv_sec = 0; + tp->tv_usec = 0; + } +} + +int +_PyTime_monotonic_info(_PyTime_timeval *tp, _Py_clock_info_t *info) +{ + return pymonotonic(tp, info, 1); +} + static void error_time_t_overflow(void) { @@ -245,8 +407,21 @@ int _PyTime_Init(void) { _PyTime_timeval tv; + +#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) return -1; + + /* ensure that the operating system provides a monotonic clock */ + if (_PyTime_monotonic_info(&tv, NULL) < 0) + return -1; return 0; } -- cgit v0.12