【问题标题】:How can I find out the source of this glibc backtrace originating with clone()?如何找出源自 clone() 的这个 glibc 回溯的来源?
【发布时间】:2018-09-17 00:27:17
【问题描述】:

此回溯来自多线程应用程序中的死锁情况。 其他死锁线程锁定在对 malloc() 的调用内部,并出现 正在等待这个线程。

我不明白是什么创建了这个线程,因为它在调用之前就死锁了 我的应用程序中的任何功能:

Thread 6 (Thread 0x7ff69d43a700 (LWP 14191)):
#0  0x00007ff6a2932eec in __lll_lock_wait_private () from /usr/lib64/libc.so.6
#1  0x00007ff6a299460d in _L_lock_27 () from /usr/lib64/libc.so.6
#2  0x00007ff6a29945bd in arena_thread_freeres () from /usr/lib64/libc.so.6
#3  0x00007ff6a2994662 in __libc_thread_freeres () from /usr/lib64/libc.so.6
#4  0x00007ff6a3875e38 in start_thread () from /usr/lib64/libpthread.so.0
#5  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

clone() 用于实现 fork()、pthread_create() 以及可能的其他函数。请参阅herehere

如何确定此跟踪是否来自fork()pthread_create()、信号处理程序或其他?我只需要挖掘 glibc 代码,还是可以使用 gdb 或其他工具?为什么这个线程需要内部 glibc 锁?这将有助于确定死锁的原因。

其他信息和研究:

malloc() 是线程安全的,但不是可重入的(递归安全的)(参见thisthis,因此 malloc() 也不是异步信号安全的。我们没有为这个过程,所以我知道我们不会从信号处理程序中调用 malloc()。死锁的线程永远不会调用递归函数,回调是在一个新线程中处理的,所以我认为我们不需要担心关于这里的重入。(也许我错了?)

当产生许多回调以发出信号(最终杀死)不同的进程时,就会发生这种死锁。回调在它们自己的线程中产生。

我们是否可能以不安全的方式使用 malloc?

可能相关:

glibc malloc internals

Malloc inside of signal handler causes deadlock.

How are signal handlers delivered in a multi-threaded application?

glibc fork/malloc deadlock bug 已在 glibc-2.17-162.el7 中修复。这看起来很相似,但不是我的错误 - 我使用的是 glibc 的固定版本。

(我未能成功创建一个最小的、完整的、可验证的示例。不幸的是,重现的唯一方法是使用应用程序 (Slurm),而且很难重现。)

编辑: 这是所有线程的回溯。线程 6 是我最初发布的跟踪。线程 1 正在等待 pthread_join()。线程 2-5 在调用 malloc() 后被锁定。线程 7 正在侦听消息并在新线程(线程 2-5)中产生回调。这些将是最终会向其他进程发出信号的回调。

Thread 7 (Thread 0x7ff69e672700 (LWP 12650)):
#0  0x00007ff6a291aa3d in poll () from /usr/lib64/libc.so.6
#1  0x00007ff6a3c09064 in _poll_internal (shutdown_time=<optimized out>, nfds=2,
    pfds=0x7ff6980009f0) at ../../../../slurm/src/common/eio.c:364
#2  eio_handle_mainloop (eio=0xf1a970) at ../../../../slurm/src/common/eio.c:328
#3  0x000000000041ce78 in _msg_thr_internal (job_arg=0xf07760)
    at ../../../../../slurm/src/slurmd/slurmstepd/req.c:245
#4  0x00007ff6a3875e25 in start_thread () from /usr/lib64/libpthread.so.0
#5  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

Thread 6 (Thread 0x7ff69d43a700 (LWP 14191)):
#0  0x00007ff6a2932eec in __lll_lock_wait_private () from /usr/lib64/libc.so.6
#1  0x00007ff6a299460d in _L_lock_27 () from /usr/lib64/libc.so.6
#2  0x00007ff6a29945bd in arena_thread_freeres () from /usr/lib64/libc.so.6
#3  0x00007ff6a2994662 in __libc_thread_freeres () from /usr/lib64/libc.so.6
#4  0x00007ff6a3875e38 in start_thread () from /usr/lib64/libpthread.so.0
#5  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

Thread 5 (Thread 0x7ff69e773700 (LWP 22471)):
#0  0x00007ff6a2932eec in __lll_lock_wait_private () from /usr/lib64/libc.so.6
#1  0x00007ff6a28af7d8 in _L_lock_1579 () from /usr/lib64/libc.so.6
#2  0x00007ff6a28a7ca0 in arena_get2.isra.3 () from /usr/lib64/libc.so.6
#3  0x00007ff6a28ad0fe in malloc () from /usr/lib64/libc.so.6
#4  0x00007ff6a3c02e60 in slurm_xmalloc (size=size@entry=24, clear=clear@entry=false,
    file=file@entry=0x7ff6a3c1f1f0 "../../../../slurm/src/common/pack.c",
    line=line@entry=152, func=func@entry=0x7ff6a3c1f4a6 <__func__.7843> "init_buf")
    at ../../../../slurm/src/common/xmalloc.c:86
#5  0x00007ff6a3b2e5b7 in init_buf (size=16384)
    at ../../../../slurm/src/common/pack.c:152
#6  0x000000000041caab in _handle_accept (arg=0x0)
    at ../../../../../slurm/src/slurmd/slurmstepd/req.c:384
#7  0x00007ff6a3875e25 in start_thread () from /usr/lib64/libpthread.so.0
#8  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

Thread 4 (Thread 0x7ff6a4086700 (LWP 5633)):
#0  0x00007ff6a2932eec in __lll_lock_wait_private () from /usr/lib64/libc.so.6
#1  0x00007ff6a28af7d8 in _L_lock_1579 () from /usr/lib64/libc.so.6
#2  0x00007ff6a28a7ca0 in arena_get2.isra.3 () from /usr/lib64/libc.so.6
#3  0x00007ff6a28ad0fe in malloc () from /usr/lib64/libc.so.6
#4  0x00007ff6a3c02e60 in slurm_xmalloc (size=size@entry=24, clear=clear@entry=false,
    file=file@entry=0x7ff6a3c1f1f0 "../../../../slurm/src/common/pack.c",
    line=line@entry=152, func=func@entry=0x7ff6a3c1f4a6 <__func__.7843> "init_buf")
    at ../../../../slurm/src/common/xmalloc.c:86
#5  0x00007ff6a3b2e5b7 in init_buf (size=16384)
    at ../../../../slurm/src/common/pack.c:152
#6  0x000000000041caab in _handle_accept (arg=0x0)
    at ../../../../../slurm/src/slurmd/slurmstepd/req.c:384
#7  0x00007ff6a3875e25 in start_thread () from /usr/lib64/libpthread.so.0
#8  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

Thread 3 (Thread 0x7ff69d53b700 (LWP 12963)):
#0  0x00007ff6a2932eec in __lll_lock_wait_private () from /usr/lib64/libc.so.6
#1  0x00007ff6a28af7d8 in _L_lock_1579 () from /usr/lib64/libc.so.6
#2  0x00007ff6a28a7ca0 in arena_get2.isra.3 () from /usr/lib64/libc.so.6
#3  0x00007ff6a28ad0fe in malloc () from /usr/lib64/libc.so.6
#4  0x00007ff6a3c02e60 in slurm_xmalloc (size=size@entry=24, clear=clear@entry=false,
    file=file@entry=0x7ff6a3c1f1f0 "../../../../slurm/src/common/pack.c",
    line=line@entry=152, func=func@entry=0x7ff6a3c1f4a6 <__func__.7843> "init_buf")
    at ../../../../slurm/src/common/xmalloc.c:86
#5  0x00007ff6a3b2e5b7 in init_buf (size=16384)
    at ../../../../slurm/src/common/pack.c:152
#6  0x000000000041caab in _handle_accept (arg=0x0)
    at ../../../../../slurm/src/slurmd/slurmstepd/req.c:384
#7  0x00007ff6a3875e25 in start_thread () from /usr/lib64/libpthread.so.0
#8  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

Thread 2 (Thread 0x7ff69f182700 (LWP 19734)):
#0  0x00007ff6a2932eec in __lll_lock_wait_private () from /usr/lib64/libc.so.6
#1  0x00007ff6a28af7d8 in _L_lock_1579 () from /usr/lib64/libc.so.6
#2  0x00007ff6a28a7ca0 in arena_get2.isra.3 () from /usr/lib64/libc.so.6
#3  0x00007ff6a28ad0fe in malloc () from /usr/lib64/libc.so.6
#4  0x00007ff6a3c02e60 in slurm_xmalloc (size=size@entry=24, clear=clear@entry=false,
    file=file@entry=0x7ff6a3c1f1f0 "../../../../slurm/src/common/pack.c",
    line=line@entry=152, func=func@entry=0x7ff6a3c1f4a6 <__func__.7843> "init_buf")
    at ../../../../slurm/src/common/xmalloc.c:86
#5  0x00007ff6a3b2e5b7 in init_buf (size=16384)
    at ../../../../slurm/src/common/pack.c:152
#6  0x000000000041caab in _handle_accept (arg=0x0)
    at ../../../../../slurm/src/slurmd/slurmstepd/req.c:384
#7  0x00007ff6a3875e25 in start_thread () from /usr/lib64/libpthread.so.0
#8  0x00007ff6a292534d in clone () from /usr/lib64/libc.so.6

Thread 1 (Thread 0x7ff6a4088880 (LWP 12616)):
#0  0x00007ff6a3876f57 in pthread_join () from /usr/lib64/libpthread.so.0
#1  0x000000000041084a in _wait_for_io (job=0xf07760)
    at ../../../../../slurm/src/slurmd/slurmstepd/mgr.c:2219
#2  job_manager (job=job@entry=0xf07760)
    at ../../../../../slurm/src/slurmd/slurmstepd/mgr.c:1397
#3  0x000000000040ca07 in main (argc=1, argv=0x7fffacab93d8)
    at ../../../../../slurm/src/slurmd/slurmstepd/slurmstepd.c:172

【问题讨论】:

  • 如果题外话,请问这个问题在哪里合适?我以为是关于编程的,但我愿意接受建议。
  • 这个特定的堆栈跟踪调用 start_thread(),所以它源自 pthread_create。我不认为有很多聪明的方法可以使用 gdb 来查找来源 - 如果您不确定堆栈跟踪是否来自您正在调试的进程或者您是否处于子进程中,您可以检查进程 ID(这会是 fork() 的结果阻止)
  • 可能有助于确定哪个线程持有每个人都在等待的互斥锁 (stackoverflow.com/q/3483094)
  • 也许有价值的是你的线程 6 是一个即将退出的线程。它已经运行了您的代码/函数,并且该函数已经完成,导致线程终止 - 它在 glibc/pthreads 的内部清理处理程序中出现死锁。
  • @mgarey:那些低级锁不记录所有者。不过,您可能可以创建一个补丁版本的 glibc。

标签: c multithreading pthreads deadlock glibc


【解决方案1】:

回溯中出现start_thread() 表明这是一个pthread_create() 线程。

__libc_thread_freeres() 是 glibc 在线程退出时调用的函数,它调用一组回调来释放每个线程的内部状态。这表明您突出显示的线程正在退出过程中。

arena_thread_freeres() 是这些回调之一。它用于 malloc arena 分配器,它将空闲列表从退出线程的私有区域移动到全局空闲列表。为此,它必须使用保护全局空闲列表的锁(这是arena.c 中的list_lock)。

突出显示的线程(线程 6)似乎是这个锁被阻塞了。

arena 分配器安装pthread_atfork() 处理程序,这些处理程序在fork() 处理开始时锁定列表锁,并在结束时解锁它。这意味着当 other pthread_atfork() 处理程序正在运行时,所有其他线程都会阻塞在这个锁上。

您是否正在安装自己的 pthread_atfork() 处理程序?似乎其中之一可能导致您的僵局。

【讨论】:

  • 优秀的答案。感谢您提供有关每个 glibc 内部函数的所有信息,以及它对我的应用程序的意义。 Slurm 确实有许多 pthread_atfork() 处理程序。我不记得这个过程是否使用它们,但我怀疑它确实如此。我会检查的。
  • 我已经接受了这个答案。它不仅回答了我最初的问题,还帮助我在调试程序方面取得了进展。即使在将所有fork() 调用从进程中取出(以及所有pthread_atfork() 处理程序)之后,死锁仍然发生(虽然不太常见)。我只能在CentOS 7 glibc 2.17-* 上重现这个。我可以' t 在 Ubuntu 16.04 glibc 2.23 上重现,例如,即使在过程中留下了 fork() 调用。我怀疑是 glibc 错误。
  • 原来这个bug是pthread_cancel使用异步取消类型引起的。本质上,我们认为 pthread_cancel 在线程退出时取消了线程,即当它持有竞技场锁时。因此,所有其他线程在调用 malloc 或退出时都会在 arena lock 上死锁,因为它由不再存在的线程持有。
  • 有趣的是,我们无法在 Debian 上重现它。 RedHat 对 glibc 2.17 的补丁似乎使这种竞争状况更有可能发生。所以,不是 glibc 错误。这很有趣,因为异步取消代码自 2002 年以来就已经存在。我们都不知道为什么将它放在首位,但是当我们最终找到它时,我们知道它可能是罪魁祸首。摆脱 pthread_cancel 并用好的互斥锁、pthread_cond_wait 和 pthread_cond_signal 替换它可以解决问题。
  • 我同意这种做法。线程取消 - 说不。
猜你喜欢
  • 2016-03-04
  • 2022-09-24
  • 2012-03-31
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 2020-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多