Skip to content

Commit 89f9004

Browse files
committed
add pruning of dead pthreads
1 parent bac223f commit 89f9004

4 files changed

Lines changed: 62 additions & 9 deletions

File tree

‎src/vmprof_common.c‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ ssize_t insert_thread(pthread_t tid, ssize_t i)
229229
{
230230
assert(signal_type == SIGALRM);
231231
i = search_thread(tid, i);
232-
if (i > 0)
232+
if (i >= 0)
233233
return -1;
234234
if (thread_count == threads_size) {
235235
threads_size += threads_size_step;
@@ -268,6 +268,45 @@ ssize_t remove_threads(void)
268268
return 0;
269269
}
270270

271+
#ifndef RPYTHON_VMPROF
272+
static int python_thread_is_alive(pthread_t tid)
273+
{
274+
unsigned long ident = (unsigned long)tid; /* as PyThread_get_thread_ident */
275+
PyInterpreterState *istate = PyInterpreterState_Head();
276+
PyThreadState *state;
277+
while (istate != NULL) {
278+
state = PyInterpreterState_ThreadHead(istate);
279+
while (state != NULL) {
280+
if (state->thread_id == ident)
281+
return 1;
282+
state = PyThreadState_Next(state);
283+
}
284+
istate = PyInterpreterState_Next(istate);
285+
}
286+
return 0;
287+
}
288+
289+
void prune_dead_threads(void)
290+
{
291+
/* pthread_kill() on a thread that has exited and been joined is
292+
undefined behaviour, and segfaults on glibc because the thread
293+
descriptor lives on the thread's freed stack. Nothing removes an
294+
exited thread from 'threads' by itself, so before broadcasting drop
295+
every registered thread whose Python thread state is gone: CPython
296+
deletes it before the thread exits. Called from the signal handler,
297+
under the spinlock and the SIGSEGV guard that also protects the
298+
thread state lookup. */
299+
size_t i = 0;
300+
while (i < thread_count) {
301+
if (python_thread_is_alive(threads[i])) {
302+
i++;
303+
} else {
304+
remove_thread(threads[i], i);
305+
}
306+
}
307+
}
308+
#endif
309+
271310
int broadcast_signal_for_threads(void)
272311
{
273312
int done = 1;
@@ -279,7 +318,9 @@ int broadcast_signal_for_threads(void)
279318
if (pthread_equal(tid, self)) {
280319
done = 0;
281320
} else if (pthread_kill(tid, SIGALRM)) {
321+
/* the last entry is moved into slot i, look at it next */
282322
remove_thread(tid, i);
323+
continue;
283324
}
284325
i++;
285326
}

‎src/vmprof_common.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,7 @@ int vmprof_get_itimer_type(void);
101101
#ifdef VMPROF_UNIX
102102
int broadcast_signal_for_threads(void);
103103
int is_main_thread(void);
104+
#ifndef RPYTHON_VMPROF
105+
void prune_dead_threads(void);
106+
#endif
104107
#endif

‎src/vmprof_unix.c‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,23 +207,25 @@ void sigprof_handler(int sig_nr, siginfo_t* info, void *ucontext)
207207
while (__sync_lock_test_and_set(&spinlock, 1)) {
208208
}
209209

210-
#ifdef VMPROF_UNIX
211210
// SIGNAL ABUSE AHEAD
212211
// On linux, the prof timer will deliver the signal to the thread which triggered the timer,
213212
// because these timers are based on process and system time, and as such, are thread-aware.
214213
// For the real timer, the signal gets delivered to the main thread, seemingly always.
215214
// Consequently if we want to sample multiple threads, we need to forward this signal.
216-
if (vmprof_get_signal_type() == SIGALRM) {
217-
if (is_main_thread() && broadcast_signal_for_threads()) {
218-
__sync_lock_release(&spinlock);
219-
return;
220-
}
221-
}
215+
int broadcast = 0;
216+
#ifdef VMPROF_UNIX
217+
broadcast = (vmprof_get_signal_type() == SIGALRM) && is_main_thread();
222218
#endif
223219

224220
prevhandler = signal(SIGSEGV, &segfault_handler);
225221
int fault_code = setjmp(restore_point);
226222
if (fault_code == 0) {
223+
#ifdef VMPROF_UNIX
224+
if (broadcast) {
225+
// walks the thread states, hence inside the guard
226+
prune_dead_threads();
227+
}
228+
#endif
227229
pthread_self();
228230
tstate = _get_pystate_for_this_thread();
229231
} else {
@@ -232,6 +234,14 @@ void sigprof_handler(int sig_nr, siginfo_t* info, void *ucontext)
232234
return;
233235
}
234236
signal(SIGSEGV, prevhandler);
237+
238+
#ifdef VMPROF_UNIX
239+
if (broadcast && broadcast_signal_for_threads()) {
240+
// the main thread itself is not registered: forwarded only
241+
__sync_lock_release(&spinlock);
242+
return;
243+
}
244+
#endif
235245
__sync_lock_release(&spinlock);
236246
#endif
237247

‎vmprof/test/test_run.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ def test_insert_other_real_time_thread(insert_foo, remove_bar):
323323

324324
@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names")
325325
@pytest.mark.skipif("sys.platform == 'win32'")
326-
@pytest.mark.skip("seems to crash")
327326
def test_vmprof_real_time_many_threads():
328327
import threading
329328
prof = vmprof.Profiler()

0 commit comments

Comments
 (0)