MEDIUM: debug: make the thread dumper not rely on a thread mask anymore

The thread mask is too short to dump more than 64 bits. Thus here we're
using a different approach with two counters, one for the next thread ID
to dump (which always exists, as it's looked up), and the second one for
the number of threads done dumping. This allows to dump threads in ascending
order then to let them wait for all others to be done, then to leave without
the risk of an overlapping dump until the done count is null again.

This allows to remove threads_to_dump which was the last non-FD variable
using a global thread mask.
This commit is contained in:
Willy Tarreau 2022-07-01 19:08:56 +02:00
parent 1229ef312d
commit 00c27b50c0

View File

@ -47,10 +47,14 @@
#include <import/ist.h> #include <import/ist.h>
/* mask of threads still having to dump, used to respect ordering. Only used /* The dump state is madee of:
* when USE_THREAD_DUMP is set. * - num_thread+1 on the lowest 16 bits
* - a counter of done on the 16 high bits
* Initiating a dump consists in setting it to 1. A thread finished dumping
* adds 1<<16 and sets the lowest 15 bits to the ID of the next thread to
* dump + 1. Only used when USE_THREAD_DUMP is set.
*/ */
static volatile unsigned long threads_to_dump = 0; static volatile unsigned int thread_dump_state = 0;
unsigned int debug_commands_issued = 0; unsigned int debug_commands_issued = 0;
/* dumps a backtrace of the current thread that is appended to buffer <buf>. /* dumps a backtrace of the current thread that is appended to buffer <buf>.
@ -1304,13 +1308,14 @@ static unsigned int thread_dump_tid;
*/ */
struct buffer *thread_dump_buffer = NULL; struct buffer *thread_dump_buffer = NULL;
/* initiates a thread dump */
void ha_thread_dump_all_to_trash() void ha_thread_dump_all_to_trash()
{ {
unsigned long old; unsigned long old;
while (1) { while (1) {
old = 0; old = 0;
if (HA_ATOMIC_CAS(&threads_to_dump, &old, all_threads_mask)) if (HA_ATOMIC_CAS(&thread_dump_state, &old, 1))
break; break;
ha_thread_relax(); ha_thread_relax();
} }
@ -1324,11 +1329,13 @@ void ha_thread_dump_all_to_trash()
void debug_handler(int sig, siginfo_t *si, void *arg) void debug_handler(int sig, siginfo_t *si, void *arg)
{ {
int harmless = is_thread_harmless(); int harmless = is_thread_harmless();
uint next;
uint done;
/* first, let's check it's really for us and that we didn't just get /* first, let's check it's really for us and that we didn't just get
* a spurious DEBUGSIG. * a spurious DEBUGSIG.
*/ */
if (!(threads_to_dump & tid_bit)) if (!_HA_ATOMIC_LOAD(&thread_dump_state))
return; return;
/* There are 4 phases in the dump process: /* There are 4 phases in the dump process:
@ -1343,40 +1350,51 @@ void debug_handler(int sig, siginfo_t *si, void *arg)
if (!harmless) if (!harmless)
thread_harmless_now(); thread_harmless_now();
while (threads_to_dump & (tid_bit - 1)) while ((_HA_ATOMIC_LOAD(&thread_dump_state) & 0xFFFF) < (tid + 1))
ha_thread_relax(); ha_thread_relax();
if (!harmless) if (!harmless)
thread_harmless_end(); thread_harmless_end();
/* dump if needed */ /* dump if needed */
if (threads_to_dump & tid_bit) {
if (thread_dump_buffer) if (thread_dump_buffer)
ha_thread_dump(thread_dump_buffer, tid, thread_dump_tid); ha_thread_dump(thread_dump_buffer, tid, thread_dump_tid);
if ((threads_to_dump & all_threads_mask) == tid_bit) {
/* last one */ /* figure which is the next thread ID to dump among enabled ones */
HA_ATOMIC_STORE(&threads_to_dump, all_threads_mask); for (next = tid + 1; next < global.nbthread; next++) {
thread_dump_buffer = NULL; if (unlikely(next >= MAX_THREADS)) {
} /* just to please gcc 6.5 who guesses the ranges wrong. */
else continue;
HA_ATOMIC_AND(&threads_to_dump, ~tid_bit);
} }
/* now wait for all others to finish dumping. The last one will set all if (ha_thread_info[next].tg &&
* bits again to broadcast the leaving condition so we'll see ourselves ha_thread_info[next].tg->threads_enabled & ha_thread_info[next].ltid_bit)
* present again. This way the threads_to_dump variable never passes to break;
* zero until all visitors have stopped waiting. }
/* atomically set the next to next+1 or 0, and increment the done counter */
done = (HA_ATOMIC_LOAD(&thread_dump_state) >> 16) + 1;
done <<= 16;
if (next < global.nbthread)
done += next + 1;
else
thread_dump_buffer = NULL; // was the last one
HA_ATOMIC_STORE(&thread_dump_state, done);
/* now wait for all others to finish dumping: the lowest part will turn
* to zero. Then all others decrement the done part.
*/ */
if (!harmless) if (!harmless)
thread_harmless_now(); thread_harmless_now();
while (!(threads_to_dump & tid_bit)) while ((_HA_ATOMIC_LOAD(&thread_dump_state) & 0xFFFF) != 0)
ha_thread_relax(); ha_thread_relax();
if (!harmless) if (!harmless)
thread_harmless_end(); thread_harmless_end();
HA_ATOMIC_AND(&threads_to_dump, ~tid_bit); HA_ATOMIC_SUB(&thread_dump_state, 0x10000);
/* mark the current thread as stuck to detect it upon next invocation /* mark the current thread as stuck to detect it upon next invocation
* if it didn't move. * if it didn't move.