faulthandler: dump_tracebacks_later() displays also the timeout

This commit is contained in:
Victor Stinner 2011-04-08 13:39:59 +02:00
parent 941893291a
commit c790a5346d
2 changed files with 52 additions and 4 deletions

View File

@ -1,4 +1,5 @@
from contextlib import contextmanager from contextlib import contextmanager
import datetime
import faulthandler import faulthandler
import re import re
import signal import signal
@ -360,6 +361,7 @@ Current thread XXX:
Raise an error if the output doesn't match the expect format. Raise an error if the output doesn't match the expect format.
""" """
timeout_str = str(datetime.timedelta(seconds=TIMEOUT))
code = """ code = """
import faulthandler import faulthandler
import time import time
@ -399,7 +401,7 @@ if file is not None:
count = loops count = loops
if repeat: if repeat:
count *= 2 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) regex = expected_traceback(9, 20, header, count=count)
self.assertRegex(trace, regex) self.assertRegex(trace, regex)
else: else:

View File

@ -50,6 +50,8 @@ static struct {
int repeat; int repeat;
PyInterpreterState *interp; PyInterpreterState *interp;
int exit; int exit;
char *header;
size_t header_len;
/* The main thread always hold this lock. It is only released when /* The main thread always hold this lock. It is only released when
faulthandler_thread() is interrupted until this thread exits, or at faulthandler_thread() is interrupted until this thread exits, or at
Python exit. */ Python exit. */
@ -424,6 +426,8 @@ faulthandler_thread(void *unused)
/* get the thread holding the GIL, NULL if no thread hold the GIL */ /* get the thread holding the GIL, NULL if no thread hold the GIL */
current = _Py_atomic_load_relaxed(&_PyThreadState_Current); current = _Py_atomic_load_relaxed(&_PyThreadState_Current);
write(thread.fd, thread.header, thread.header_len);
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current); errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current);
ok = (errmsg == NULL); ok = (errmsg == NULL);
@ -449,6 +453,37 @@ cancel_dump_tracebacks_later(void)
PyThread_acquire_lock(thread.cancel_event, 1); PyThread_acquire_lock(thread.cancel_event, 1);
Py_CLEAR(thread.file); 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* static PyObject*
@ -463,17 +498,18 @@ faulthandler_dump_tracebacks_later(PyObject *self,
int fd; int fd;
int exit = 0; int exit = 0;
PyThreadState *tstate; PyThreadState *tstate;
char *header;
size_t header_len;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"d|iOi:dump_tracebacks_later", kwlist, "d|iOi:dump_tracebacks_later", kwlist,
&timeout, &repeat, &file, &exit)) &timeout, &repeat, &file, &exit))
return NULL; return NULL;
timeout *= 1e6; if ((timeout * 1e6) >= (double) PY_TIMEOUT_MAX) {
if (timeout >= (double) PY_TIMEOUT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout value is too large"); PyErr_SetString(PyExc_OverflowError, "timeout value is too large");
return NULL; return NULL;
} }
timeout_us = (PY_TIMEOUT_T)timeout; timeout_us = (PY_TIMEOUT_T)(timeout * 1e6);
if (timeout_us <= 0) { if (timeout_us <= 0) {
PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0"); PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0");
return NULL; return NULL;
@ -490,6 +526,12 @@ faulthandler_dump_tracebacks_later(PyObject *self,
if (file == NULL) if (file == NULL)
return 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 previous thread, if running */
cancel_dump_tracebacks_later(); cancel_dump_tracebacks_later();
@ -501,6 +543,8 @@ faulthandler_dump_tracebacks_later(PyObject *self,
thread.repeat = repeat; thread.repeat = repeat;
thread.interp = tstate->interp; thread.interp = tstate->interp;
thread.exit = exit; thread.exit = exit;
thread.header = header;
thread.header_len = header_len;
/* Arm these locks to serve as events when released */ /* Arm these locks to serve as events when released */
PyThread_acquire_lock(thread.running, 1); 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) { if (PyThread_start_new_thread(faulthandler_thread, NULL) == -1) {
PyThread_release_lock(thread.running); PyThread_release_lock(thread.running);
Py_CLEAR(thread.file); Py_CLEAR(thread.file);
free(header);
thread.header = NULL;
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_RuntimeError,
"unable to start watchdog thread"); "unable to start watchdog thread");
return NULL; return NULL;