# USE_WURFL : enable WURFL detection library from Scientiamobile
# USE_SYSTEMD : enable sd_notify() support.
# USE_OBSOLETE_LINKER : use when the linker fails to emit __start_init/__stop_init
+# USE_THREAD_DUMP : use the more advanced thread state dump system. Automatic.
#
# Options can be forced by specifying "USE_xxx=1" or can be disabled by using
# "USE_xxx=" (empty string).
USE_GETADDRINFO USE_OPENSSL USE_LUA USE_FUTEX USE_ACCEPT4 \
USE_MY_ACCEPT4 USE_ZLIB USE_SLZ USE_CPU_AFFINITY USE_TFO USE_NS \
USE_DL USE_RT USE_DEVICEATLAS USE_51DEGREES USE_WURFL USE_SYSTEMD \
- USE_OBSOLETE_LINKER USE_PRCTL
+ USE_OBSOLETE_LINKER USE_PRCTL USE_THREAD_DUMP
#### Target system options
# Depending on the target platform, some options are set, as well as some
set_target_defaults = $(call default_opts, \
USE_POLL USE_TPROXY USE_LIBCRYPT USE_DL USE_RT USE_CRYPT_H USE_NETFILTER \
USE_CPU_AFFINITY USE_THREAD USE_EPOLL USE_FUTEX USE_LINUX_TPROXY \
- USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL ASSUME_SPLICE_WORKS)
+ USE_ACCEPT4 USE_LINUX_SPLICE USE_PRCTL ASSUME_SPLICE_WORKS USE_THREAD_DUMP)
endif
# Solaris 8 and above
show threads
Dumps some internal states and structures for each thread, that may be useful
to help developers understand a problem. The output tries to be readable by
- showing one block per thread, with a bit more info for the current thread.
- The output format is purposely not documented so that it can easily evolve
- as new needs are identified, without having to maintain any backwards
+ showing one block per thread. When haproxy is built with USE_THREAD_DUMP=1,
+ an advanced dump mechanism involving thread signals is used so that each
+ thread can dump its own state in turn. Without this option, the thread
+ processing the command shows all its details but the other ones are less
+ detailed. A stat ('*') is displayed in front of the thread handling the
+ command. The output format is purposely not documented so that it can easily
+ evolve as new needs are identified, without having to maintain any backwards
compatibility, and just like with "show activity", the values are only
meaningful with the code at hand.
thr = 0;
chunk_reset(&trash);
- while (thr < global.nbthread) {
- ha_thread_dump(&trash, thr, tid);
- thr++;
- }
+ ha_thread_dump_all_to_trash();
if (ci_putchk(si_ic(si), &trash) == -1) {
/* failed, try again */
return 1;
}
+#ifndef USE_THREAD_DUMP
+
+/* This function dumps all threads' state to the trash. This version is the
+ * most basic one, which doesn't inspect other threads.
+ */
+void ha_thread_dump_all_to_trash()
+{
+ unsigned int thr;
+
+ for (thr = 0; thr < global.nbthread; thr++)
+ ha_thread_dump(&trash, thr, tid);
+}
+
+#else /* below USE_THREAD_DUMP is set */
+
+/* The signal to trigger a debug dump on a thread is SIGPWR */
+#define DEBUGSIG SIGPWR
+
+/* mask of threads still having to dump, used to respect ordering */
+static volatile unsigned long threads_to_dump;
+
+/* ID of the thread requesting the dump */
+static unsigned int thread_dump_tid;
+
+/* points to the buffer where the dump functions should write. It must
+ * have already been initialized by the requester. Nothing is done if
+ * it's NULL.
+ */
+struct buffer *thread_dump_buffer = NULL;
+
+void ha_thread_dump_all_to_trash()
+{
+ __maybe_unused unsigned int thr;
+ unsigned long old;
+
+ while (1) {
+ old = 0;
+ if (HA_ATOMIC_CAS(&threads_to_dump, &old, all_threads_mask))
+ break;
+ ha_thread_relax();
+ }
+
+ thread_dump_buffer = &trash;
+ thread_dump_tid = tid;
+
+#ifdef USE_THREAD
+ for (thr = 0; thr < global.nbthread; thr++) {
+ if (thr != tid)
+ pthread_kill(threads[thr], DEBUGSIG);
+ }
+#endif
+ /* dump ourselves last */
+ raise(DEBUGSIG);
+}
+
+/* handles DEBUGSIG to dump the state of the thread it's working on */
+void debug_handler(int sig, siginfo_t *si, void *arg)
+{
+ /* 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 but ours
+ * 4- wait for zero and clear our bit if it's set
+ */
+
+ /* wait for all previous threads to finish first */
+ while (threads_to_dump & (tid_bit - 1))
+ ha_thread_relax();
+
+ /* dump if needed */
+ if (threads_to_dump & tid_bit) {
+ if (thread_dump_buffer)
+ ha_thread_dump(thread_dump_buffer, tid, thread_dump_tid);
+ if ((threads_to_dump & all_threads_mask) == tid_bit) {
+ /* last one */
+ HA_ATOMIC_STORE(&threads_to_dump, all_threads_mask & ~tid_bit);
+ thread_dump_buffer = NULL;
+ }
+ else
+ HA_ATOMIC_AND(&threads_to_dump, ~tid_bit);
+ }
+
+ /* now wait for all others to finish dumping. The last one will set all
+ * bits again to broadcast the leaving condition.
+ */
+ while (threads_to_dump & all_threads_mask) {
+ if (threads_to_dump & tid_bit)
+ HA_ATOMIC_AND(&threads_to_dump, ~tid_bit);
+ else
+ ha_thread_relax();
+ }
+}
+
+static int init_debug_per_thread()
+{
+ sigset_t set;
+
+ /* unblock the DEBUGSIG signal we intend to use */
+ sigemptyset(&set);
+ sigaddset(&set, DEBUGSIG);
+ ha_sigmask(SIG_UNBLOCK, &set, NULL);
+ return 1;
+}
+
+static int init_debug()
+{
+ struct sigaction sa;
+
+ sa.sa_handler = NULL;
+ sa.sa_sigaction = debug_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO;
+ sigaction(DEBUGSIG, &sa, NULL);
+ return 0;
+}
+
+REGISTER_POST_CHECK(init_debug);
+REGISTER_PER_THREAD_INIT(init_debug_per_thread);
+
+#endif /* USE_THREAD_DUMP */
+
/* register cli keywords */
static struct cli_kw_list cli_kws = {{ },{
{ { "show", "threads", NULL }, "show threads : show some threads debugging information", NULL, cli_io_handler_show_threads, NULL },