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.
This commit is contained in:
Victor Stinner 2014-09-02 23:18:25 +02:00
parent 9bb758cee7
commit ae58649721
17 changed files with 226 additions and 176 deletions

View File

@ -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 processes running for more than 49 days. On more recent versions of Windows
and on other operating systems, :func:`monotonic` is system-wide. and on other operating systems, :func:`monotonic` is system-wide.
Availability: Windows, Mac OS X, Linux, FreeBSD, OpenBSD, Solaris.
.. versionadded:: 3.3 .. versionadded:: 3.3
.. versionchanged:: 3.5
The function is now always available.
.. function:: perf_counter() .. function:: perf_counter()

View File

@ -238,6 +238,11 @@ socket
:meth:`socket.socket.send`. :meth:`socket.socket.send`.
(contributed by Giampaolo Rodola' in :issue:`17552`) (contributed by Giampaolo Rodola' in :issue:`17552`)
time
----
The :func:`time.monotonic` function is now always available (:issue`22043`).
wsgiref wsgiref
------- -------

View File

@ -91,6 +91,24 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec(
long *nsec, long *nsec,
_PyTime_round_t); _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. /* Initialize time.
Return 0 on success, raise an exception and return -1 on error. */ Return 0 on success, raise an exception and return -1 on error. */
PyAPI_FUNC(int) _PyTime_Init(void); PyAPI_FUNC(int) _PyTime_Init(void);

View File

@ -6,10 +6,7 @@ except ImportError:
import dummy_threading as threading import dummy_threading as threading
from collections import deque from collections import deque
from heapq import heappush, heappop from heapq import heappush, heappop
try: from time import monotonic as time
from time import monotonic as time
except ImportError:
from time import time
__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue'] __all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue']

View File

@ -35,10 +35,7 @@ try:
import threading import threading
except ImportError: except ImportError:
import dummy_threading as threading import dummy_threading as threading
try: from time import monotonic as _time
from time import monotonic as _time
except ImportError:
from time import time as _time
__all__ = ["scheduler"] __all__ = ["scheduler"]

View File

@ -136,10 +136,7 @@ try:
import threading import threading
except ImportError: except ImportError:
import dummy_threading as threading import dummy_threading as threading
try: from time import monotonic as time
from time import monotonic as time
except ImportError:
from time import time as time
__all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer", __all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer",
"ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler", "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler",

View File

@ -365,10 +365,7 @@ import signal
import builtins import builtins
import warnings import warnings
import errno import errno
try: from time import monotonic as _time
from time import monotonic as _time
except ImportError:
from time import time as _time
# Exception classes used by this module. # Exception classes used by this module.
class SubprocessError(Exception): pass class SubprocessError(Exception): pass

View File

@ -36,10 +36,7 @@ To do:
import sys import sys
import socket import socket
import selectors import selectors
try: from time import monotonic as _time
from time import monotonic as _time
except ImportError:
from time import time as _time
__all__ = ["Telnet"] __all__ = ["Telnet"]

View File

@ -8,10 +8,7 @@ from test import support
from time import sleep from time import sleep
import unittest import unittest
import unittest.mock import unittest.mock
try: from time import monotonic as time
from time import monotonic as time
except ImportError:
from time import time as time
try: try:
import resource import resource
except ImportError: except ImportError:

View File

@ -3,10 +3,7 @@
import sys as _sys import sys as _sys
import _thread import _thread
try: from time import monotonic as _time
from time import monotonic as _time
except ImportError:
from time import time as _time
from traceback import format_exc as _format_exc from traceback import format_exc as _format_exc
from _weakrefset import WeakSet from _weakrefset import WeakSet
from itertools import islice as _islice from itertools import islice as _islice

View File

@ -59,10 +59,7 @@ import gc
import dis import dis
import pickle import pickle
from warnings import warn as _warn from warnings import warn as _warn
try: from time import monotonic as _time
from time import monotonic as _time
except ImportError:
from time import time as _time
try: try:
import threading import threading

View File

@ -129,6 +129,11 @@ Core and Builtins
Library 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 - Issue #21527: Add a default number of workers to ThreadPoolExecutor equal
to 5 times the number of CPUs. Patch by Claudiu Popa. to 5 times the number of CPUs. Patch by Claudiu Popa.

View File

@ -57,7 +57,7 @@ acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds)
if (microseconds > 0) { if (microseconds > 0) {
_PyTime_gettimeofday(&endtime); _PyTime_monotonic(&endtime);
endtime.tv_sec += microseconds / (1000 * 1000); endtime.tv_sec += microseconds / (1000 * 1000);
endtime.tv_usec += 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 /* If we're using a timeout, recompute the timeout after processing
* signals, since those can take time. */ * signals, since those can take time. */
if (microseconds > 0) { if (microseconds > 0) {
_PyTime_gettimeofday(&curtime); _PyTime_monotonic(&curtime);
microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 + microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 +
(endtime.tv_usec - curtime.tv_usec)); (endtime.tv_usec - curtime.tv_usec));

View File

@ -25,7 +25,7 @@
#include "Python.h" #include "Python.h"
#include "frameobject.h" /* for PyFrame_ClearFreeList */ #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 */ /* Get an object's GC head */
#define AS_GC(o) ((PyGC_Head *)(o)-1) #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++) for (i = 0; i < NUM_GENERATIONS; i++)
PySys_FormatStderr(" %zd", PySys_FormatStderr(" %zd",
gc_list_size(GEN_HEAD(i))); gc_list_size(GEN_HEAD(i)));
_PyTime_gettimeofday(&t1); _PyTime_monotonic(&t1);
PySys_WriteStderr("\n"); PySys_WriteStderr("\n");
} }
@ -1025,7 +1025,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
} }
if (debug & DEBUG_STATS) { if (debug & DEBUG_STATS) {
_PyTime_timeval t2; _PyTime_timeval t2;
_PyTime_gettimeofday(&t2); _PyTime_monotonic(&t2);
if (m == 0 && n == 0) if (m == 0 && n == 0)
PySys_WriteStderr("gc: done"); PySys_WriteStderr("gc: done");

View File

@ -680,7 +680,7 @@ internal_select(PySocketSockObject *s, int writing)
double interval = s->sock_timeout; \ double interval = s->sock_timeout; \
int has_timeout = s->sock_timeout > 0.0; \ int has_timeout = s->sock_timeout > 0.0; \
if (has_timeout) { \ if (has_timeout) { \
_PyTime_gettimeofday(&now); \ _PyTime_monotonic(&now); \
deadline = now; \ deadline = now; \
_PyTime_ADD_SECONDS(deadline, s->sock_timeout); \ _PyTime_ADD_SECONDS(deadline, s->sock_timeout); \
} \ } \
@ -691,7 +691,7 @@ internal_select(PySocketSockObject *s, int writing)
if (!has_timeout || \ if (!has_timeout || \
(!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \ (!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \
break; \ break; \
_PyTime_gettimeofday(&now); \ _PyTime_monotonic(&now); \
interval = _PyTime_INTERVAL(now, deadline); \ interval = _PyTime_INTERVAL(now, deadline); \
} \ } \
} \ } \

View File

@ -37,10 +37,6 @@
#endif /* MS_WINDOWS */ #endif /* MS_WINDOWS */
#endif /* !__WATCOMC__ || __QNX__ */ #endif /* !__WATCOMC__ || __QNX__ */
#if defined(__APPLE__)
#include <mach/mach_time.h>
#endif
/* Forward declarations */ /* Forward declarations */
static int floatsleep(double); static int floatsleep(double);
static PyObject* floattime(_Py_clock_info_t *info); 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."); should not be relied on.");
#endif /* HAVE_WORKING_TZSET */ #endif /* HAVE_WORKING_TZSET */
#if defined(MS_WINDOWS) || defined(__APPLE__) \ static PyObject *
|| (defined(HAVE_CLOCK_GETTIME) \
&& (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC)))
#define PYMONOTONIC
#endif
#ifdef PYMONOTONIC
static PyObject*
pymonotonic(_Py_clock_info_t *info) pymonotonic(_Py_clock_info_t *info)
{ {
#if defined(MS_WINDOWS) _PyTime_timeval tv;
static ULONGLONG (*GetTickCount64) (void) = NULL; if (_PyTime_monotonic_info(&tv, info) < 0) {
static ULONGLONG (CALLBACK *Py_GetTickCount64)(void); assert(info != NULL);
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);
return NULL; return NULL;
} }
return PyFloat_FromDouble((double)tv.tv_sec + tv.tv_usec * 1e-6);
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
} }
static PyObject * static PyObject *
@ -1027,7 +916,6 @@ PyDoc_STRVAR(monotonic_doc,
"monotonic() -> float\n\ "monotonic() -> float\n\
\n\ \n\
Monotonic clock, cannot go backward."); Monotonic clock, cannot go backward.");
#endif /* PYMONOTONIC */
static PyObject* static PyObject*
perf_counter(_Py_clock_info_t *info) perf_counter(_Py_clock_info_t *info)
@ -1035,20 +923,7 @@ perf_counter(_Py_clock_info_t *info)
#ifdef WIN32_PERF_COUNTER #ifdef WIN32_PERF_COUNTER
return win_perf_counter(info); return win_perf_counter(info);
#else #else
return pymonotonic(info);
#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);
#endif #endif
} }
@ -1216,10 +1091,8 @@ time_get_clock_info(PyObject *self, PyObject *args)
else if (strcmp(name, "clock") == 0) else if (strcmp(name, "clock") == 0)
obj = pyclock(&info); obj = pyclock(&info);
#endif #endif
#ifdef PYMONOTONIC
else if (strcmp(name, "monotonic") == 0) else if (strcmp(name, "monotonic") == 0)
obj = pymonotonic(&info); obj = pymonotonic(&info);
#endif
else if (strcmp(name, "perf_counter") == 0) else if (strcmp(name, "perf_counter") == 0)
obj = perf_counter(&info); obj = perf_counter(&info);
else if (strcmp(name, "process_time") == 0) else if (strcmp(name, "process_time") == 0)
@ -1411,9 +1284,7 @@ static PyMethodDef time_methods[] = {
#ifdef HAVE_WORKING_TZSET #ifdef HAVE_WORKING_TZSET
{"tzset", time_tzset, METH_NOARGS, tzset_doc}, {"tzset", time_tzset, METH_NOARGS, tzset_doc},
#endif #endif
#ifdef PYMONOTONIC
{"monotonic", time_monotonic, METH_NOARGS, monotonic_doc}, {"monotonic", time_monotonic, METH_NOARGS, monotonic_doc},
#endif
{"process_time", time_process_time, METH_NOARGS, process_time_doc}, {"process_time", time_process_time, METH_NOARGS, process_time_doc},
{"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc}, {"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc},
{"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc}, {"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc},

View File

@ -3,6 +3,14 @@
#include <windows.h> #include <windows.h>
#endif #endif
#if defined(__APPLE__)
#include <mach/mach_time.h> /* mach_absolute_time(), mach_timebase_info() */
#endif
#ifdef MS_WINDOWS
static OSVERSIONINFOEX winver;
#endif
static int static int
pygettimeofday(_PyTime_timeval *tp, _Py_clock_info_t *info, int raise) 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); 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 static void
error_time_t_overflow(void) error_time_t_overflow(void)
{ {
@ -245,8 +407,21 @@ int
_PyTime_Init(void) _PyTime_Init(void)
{ {
_PyTime_timeval tv; _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 */ /* ensure that the system clock works */
if (_PyTime_gettimeofday_info(&tv, NULL) < 0) if (_PyTime_gettimeofday_info(&tv, NULL) < 0)
return -1; return -1;
/* ensure that the operating system provides a monotonic clock */
if (_PyTime_monotonic_info(&tv, NULL) < 0)
return -1;
return 0; return 0;
} }