diff --git a/src/debug.c b/src/debug.c index 1dd446f78..45e91e068 100644 --- a/src/debug.c +++ b/src/debug.c @@ -47,13 +47,25 @@ #include -/* The dump state is madee of: - * - 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. +/* The dump state is made of: + * - num_thread on the lowest 15 bits + * - a SYNC flag on bit 15 (waiting for sync start) + * - number of participating threads on bits 16-30 + * Initiating a dump consists in setting it to SYNC and incrementing the + * num_thread part when entering the function. The first thread periodically + * recounts active threads and compares it to the ready ones, and clears SYNC + * and sets the number of participants to the value found, which serves as a + * start signal. A thread finished dumping looks up the TID of the next active + * thread after it and writes it in the lowest part. If there's none, it sets + * the thread counter to the number of participants and resets that part, + * which serves as an end-of-dump signal. All threads decrement the num_thread + * part. Then all threads wait for the value to reach zero. Only used when + * USE_THREAD_DUMP is set. */ +#define THREAD_DUMP_TMASK 0x00007FFFU +#define THREAD_DUMP_FSYNC 0x00008000U +#define THREAD_DUMP_PMASK 0x7FFF0000U + volatile unsigned int thread_dump_state = 0; unsigned int panic_started = 0; unsigned int debug_commands_issued = 0; @@ -1323,23 +1335,35 @@ void ha_thread_dump_all_to_trash() { unsigned int old; - /* initiate a dump starting from first thread. Use a CAS - * so that we don't wait if we're not the first one. + /* initiate a dump starting from first thread. Use a CAS so that we do + * not wait if we're not the first one, but we wait for a previous dump + * to finish. */ - old = 0; - HA_ATOMIC_CAS(&thread_dump_state, &old, 1); - + while (1) { + old = 0; + if (HA_ATOMIC_CAS(&thread_dump_state, &old, THREAD_DUMP_FSYNC)) + break; + ha_thread_relax(); + } thread_dump_buffer = &trash; thread_dump_tid = tid; ha_tkillall(DEBUGSIG); + + /* the call above contains a raise() so we're certain to return after + * returning from the sighandler, hence when the dump is complete. + */ } -/* handles DEBUGSIG to dump the state of the thread it's working on */ +/* handles DEBUGSIG to dump the state of the thread it's working on. This is + * appended at the end of thread_dump_buffer which must be protected against + * reentrance from different threads (a thread-local buffer works fine). + */ void debug_handler(int sig, siginfo_t *si, void *arg) { int harmless = is_thread_harmless(); + int running = 0; + uint prev; uint next; - uint done; /* first, let's check it's really for us and that we didn't just get * a spurious DEBUGSIG. @@ -1347,21 +1371,65 @@ void debug_handler(int sig, siginfo_t *si, void *arg) if (!_HA_ATOMIC_LOAD(&thread_dump_state)) return; - /* There are 4 phases in the dump process: - * 1- wait for our turn, i.e. when all lower bits are gone. - * 2- perform the action if our bit is set - * 3- remove our bit to let the next one go, unless we're - * the last one and have to put them all as a signal - * 4- wait out bit to re-appear, then clear it and quit. + /* There are 5 phases in the dump process: + * 1- wait for all threads to sync or the first one to start + * 2- wait for our turn, i.e. when tid appears in lower bits. + * 3- perform the action if our tid is there + * 4- pass tid to the number of the next thread to dump or + * reset running counter if we're last one. + * 5- wait for running to be zero and decrement the count */ /* wait for all previous threads to finish first */ if (!harmless) thread_harmless_now(); - while ((_HA_ATOMIC_LOAD(&thread_dump_state) & 0xFFFF) < (tid + 1)) + if (HA_ATOMIC_FETCH_ADD(&thread_dump_state, 1) == THREAD_DUMP_FSYNC) { + /* the first one which lands here is responsible for constantly + * recounting the number of active theads and switching from + * SYNC to DUMP. + */ + while (1) { + int first = -1; // first tid to dump + int thr; + + running = 0; + for (thr = 0; thr < global.nbthread; thr++) { + if (ha_thread_info[thr].tg->threads_enabled & ha_thread_info[thr].ltid_bit) { + running++; + if (first < 0) + first = thr; + } + } + + if ((HA_ATOMIC_LOAD(&thread_dump_state) & THREAD_DUMP_TMASK) == running) { + /* all threads are there, let's try to start */ + prev = THREAD_DUMP_FSYNC | running; + next = (running << 16) | first; + if (HA_ATOMIC_CAS(&thread_dump_state, &prev, next)) + break; + /* it failed! maybe a thread appeared late (e.g. during boot), let's + * recount. + */ + } + ha_thread_relax(); + } + } + + /* all threads: let's wait for the SYNC flag to disappear; tid is reset at + * the same time to the first valid tid to dump and pmask will reflect the + * number of participants. + */ + while (HA_ATOMIC_LOAD(&thread_dump_state) & THREAD_DUMP_FSYNC) ha_thread_relax(); + /* wait for our turn */ + while ((HA_ATOMIC_LOAD(&thread_dump_state) & THREAD_DUMP_TMASK) != tid) + ha_thread_relax(); + + /* make sure we don't count all that wait time against us */ + HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_STUCK); + if (!harmless) thread_harmless_end(); @@ -1369,7 +1437,11 @@ void debug_handler(int sig, siginfo_t *si, void *arg) if (thread_dump_buffer) ha_thread_dump(thread_dump_buffer, tid, thread_dump_tid); - /* figure which is the next thread ID to dump among enabled ones */ + /* figure which is the next thread ID to dump among enabled ones. Note + * that this relies on the fact that we're not creating new threads in + * the middle of a dump, which is normally granted by the harmless bits + * anyway. + */ for (next = tid + 1; next < global.nbthread; next++) { if (unlikely(next >= MAX_THREADS)) { /* just to please gcc 6.5 who guesses the ranges wrong. */ @@ -1381,15 +1453,18 @@ void debug_handler(int sig, siginfo_t *si, void *arg) break; } - /* 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 + /* if there are threads left to dump, we atomically set the next one, + * otherwise we'll clear dump and set the thread part to the number of + * threads that need to disappear. + */ + if (next < global.nbthread) { + next = (HA_ATOMIC_LOAD(&thread_dump_state) & THREAD_DUMP_PMASK) | next; + HA_ATOMIC_STORE(&thread_dump_state, next); + } else { thread_dump_buffer = NULL; // was the last one - - HA_ATOMIC_STORE(&thread_dump_state, done); + running = (HA_ATOMIC_LOAD(&thread_dump_state) & THREAD_DUMP_PMASK) >> 16; + HA_ATOMIC_STORE(&thread_dump_state, running); + } /* now wait for all others to finish dumping: the lowest part will turn * to zero. Then all others decrement the done part. @@ -1397,13 +1472,21 @@ void debug_handler(int sig, siginfo_t *si, void *arg) if (!harmless) thread_harmless_now(); - while ((_HA_ATOMIC_LOAD(&thread_dump_state) & 0xFFFF) != 0) + /* wait for everyone to finish*/ + while (HA_ATOMIC_LOAD(&thread_dump_state) & THREAD_DUMP_PMASK) ha_thread_relax(); + /* make sure we don't count all that wait time against us */ + HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_STUCK); + if (!harmless) thread_harmless_end(); - HA_ATOMIC_SUB(&thread_dump_state, 0x10000); + /* we're gone. Past this point anything can happen including another + * thread trying to re-trigger a dump, so thread_dump_buffer and + * thread_dump_tid may become invalid immediately after this call. + */ + HA_ATOMIC_SUB(&thread_dump_state, 1); /* mark the current thread as stuck to detect it upon next invocation * if it didn't move.