diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 483c7f1a29f..d08347d90ea 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -1,4 +1,5 @@ from contextlib import contextmanager +import datetime import faulthandler import re import signal @@ -360,6 +361,7 @@ Current thread XXX: Raise an error if the output doesn't match the expect format. """ + timeout_str = str(datetime.timedelta(seconds=TIMEOUT)) code = """ import faulthandler import time @@ -399,7 +401,7 @@ if file is not None: count = loops if repeat: count *= 2 - header = 'Thread 0x[0-9a-f]+:\n' + header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str regex = expected_traceback(9, 20, header, count=count) self.assertRegex(trace, regex) else: diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 4edb16b7aee..2b353997601 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -50,6 +50,8 @@ static struct { int repeat; PyInterpreterState *interp; int exit; + char *header; + size_t header_len; /* The main thread always hold this lock. It is only released when faulthandler_thread() is interrupted until this thread exits, or at Python exit. */ @@ -424,6 +426,8 @@ faulthandler_thread(void *unused) /* get the thread holding the GIL, NULL if no thread hold the GIL */ current = _Py_atomic_load_relaxed(&_PyThreadState_Current); + write(thread.fd, thread.header, thread.header_len); + errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current); ok = (errmsg == NULL); @@ -449,6 +453,37 @@ cancel_dump_tracebacks_later(void) PyThread_acquire_lock(thread.cancel_event, 1); Py_CLEAR(thread.file); + if (thread.header) { + free(thread.header); + thread.header = NULL; + } +} + +static char* +format_timeout(double timeout) +{ + unsigned long us, sec, min, hour; + double intpart, fracpart; + char buffer[100]; + + fracpart = modf(timeout, &intpart); + sec = (unsigned long)intpart; + us = (unsigned long)(fracpart * 1e6); + min = sec / 60; + sec %= 60; + hour = min / 60; + min %= 60; + + if (us != 0) + PyOS_snprintf(buffer, sizeof(buffer), + "Timeout (%lu:%02lu:%02lu.%06lu)!\n", + hour, min, sec, us); + else + PyOS_snprintf(buffer, sizeof(buffer), + "Timeout (%lu:%02lu:%02lu)!\n", + hour, min, sec); + + return strdup(buffer); } static PyObject* @@ -463,17 +498,18 @@ faulthandler_dump_tracebacks_later(PyObject *self, int fd; int exit = 0; PyThreadState *tstate; + char *header; + size_t header_len; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "d|iOi:dump_tracebacks_later", kwlist, &timeout, &repeat, &file, &exit)) return NULL; - timeout *= 1e6; - if (timeout >= (double) PY_TIMEOUT_MAX) { + if ((timeout * 1e6) >= (double) PY_TIMEOUT_MAX) { PyErr_SetString(PyExc_OverflowError, "timeout value is too large"); return NULL; } - timeout_us = (PY_TIMEOUT_T)timeout; + timeout_us = (PY_TIMEOUT_T)(timeout * 1e6); if (timeout_us <= 0) { PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0"); return NULL; @@ -490,6 +526,12 @@ faulthandler_dump_tracebacks_later(PyObject *self, if (file == NULL) return NULL; + /* format the timeout */ + header = format_timeout(timeout); + if (header == NULL) + return PyErr_NoMemory(); + header_len = strlen(header); + /* Cancel previous thread, if running */ cancel_dump_tracebacks_later(); @@ -501,6 +543,8 @@ faulthandler_dump_tracebacks_later(PyObject *self, thread.repeat = repeat; thread.interp = tstate->interp; thread.exit = exit; + thread.header = header; + thread.header_len = header_len; /* Arm these locks to serve as events when released */ PyThread_acquire_lock(thread.running, 1); @@ -508,6 +552,8 @@ faulthandler_dump_tracebacks_later(PyObject *self, if (PyThread_start_new_thread(faulthandler_thread, NULL) == -1) { PyThread_release_lock(thread.running); Py_CLEAR(thread.file); + free(header); + thread.header = NULL; PyErr_SetString(PyExc_RuntimeError, "unable to start watchdog thread"); return NULL;