[Bug 1888857] Re: deadlock in pthread_cond_signal under high contention

Anton Nikolaevsky 1888857 at bugs.launchpad.net
Sat Jul 25 11:53:51 UTC 2020


Thank you for the prompt reply!
I've installed the packages and left the test application running for the weekend. I'll write you back on Monday.

-- 
You received this bug notification because you are a member of Ubuntu
Foundations Bugs, which is subscribed to glibc in Ubuntu.
https://bugs.launchpad.net/bugs/1888857

Title:
  deadlock in pthread_cond_signal under high contention

Status in glibc package in Ubuntu:
  New

Bug description:
  Hello!

  I'm working on a large C++-based cross-platform project. I noticed that on arm64-based systems some of my processes sporadically became paralyzed by the deadlock hitting all the threads posting to the single boost::asio::io_service. I investigated the deadlock condition further and reduced the problem to the simple test application available at https://github.com/bezkrovatki/deadlock_in_pthread_cond_signal 
  There you may find there the test source code, the detailed description, the deadlock call stacks for all threads and theirs compact view as a call graph.
  In a short, in the test I have threads of two types:
    (1) producers - Np threads calling pthread_cond_signal after unlocking a mutex at a rate of Rp calls per second;
    (2) consumers - Nc threads calling pthread_cond_wait at a rate of Rc calls per second.
  Np, Rp and Rc can be specified with command line parameters, Nc is equal to the number of CPU cores of the particular system running the test. Once started on arm64-based multi-core device the test eventually gets all its threads blocked if the Np, Rp and Rc are enough to keep contention high around pthread_cond_singal calls.

  The deadlock can be workarounded by
  * reducing probability of concurring pthread_cond_singal calls by tuning Np, Rp and Rc;
  * moving pthread_cond_singal call under the lock

  Moreover, the deadlock can be broken by ptrace: attaching with
  debugger, generating dump with Google Breakpad and etc. makes the
  process revive. One time I was able to wake up the process from the
  deadlock with SIGSTOP/SIGCONT, however, the healing effect was very
  limited and the process returned into the deadlock state in a few
  seconds.

  I would like to note that a problem with symptoms that look similar
  was reported and fixed in kernel several years ago (see
  https://groups.google.com/forum/#!topic/mechanical-
  sympathy/QbmpZxp6C64 and
  https://github.com/torvalds/linux/commit/76835b0ebf8a7fe85beb03c75121419a7dec52f0).

  However, I believe this time the problem is on the NPTL implementation
  side because:

    * 100% of the observed deadlocks both in our product and the tests
  appear to have the same structure: single producer blocked in
  __condvar_quiesce_and_switch_g1, all other producers blocked in
  __condvar_acquire_lock, all consumers blocked in
  __pthread_cond_wait_common;

    * mutex misbehavior was never observed either in test or in my
  project;

    * wakeups by ptrace/signal simply mean waiting on a futex got
  interrupted and on the next iteration (if any) at least one of these
  call paths made progress after observing changed global state, which
  can be a side effect of the race in the userland as well as in the
  kernel;

    * while the mutex object is more contended than pthread_cond_signal
  related internal data of the condvar if I put the pthread_cond_signal
  call under the lock, I cannot reproduce the problem.

  I looked at the nptl source code
  (https://elixir.bootlin.com/glibc/glibc-2.27/source/nptl/pthread_cond_common.c#L280)
  a bit. I'm not burdened with a deep knowledge of the implemented
  algorithm and its dark corners. For me the observed deadlock looks
  quite probable from the source code.

    1) All producers (signalling threads) except one are blocked in
  __condvar_acquire_lock at pthread_cond_common.c:280, they are waiting for
  the single signalling thread which was lucky to succeed in acquiring
  the internal data lock.

    2) According to the comments lavishly sown around the code, that
  "lucky" signalling thread waits for the some of consumers (waiting
  threads) to leave G1 group to be able to close the group and make the
  group switch in
  __condvar_quiesce_and_switch_g1 at pthread_cond_common.c:412

    3) And all consumers (waiting threads) wait, of course, they wait
  for the producers to send a signal, see
  __pthread_cond_wait_common at pthread_cond_wait.c:502

    4) And if you watch the code around __pthread_cond_wait_common at pthread_cond_wait.c:502 carefully you can see that when the waiting for signals on the futex gets interrupted, the code wakes our "lucky" thread blocked in __condvar_quiesce_and_switch_g1 at pthread_cond_common.c:412 at first (by calling __condvar_dec_grefs at pthread_cond_wait.c:149 ) and only then re-evaluates the condition and returns to the waiting on the futex if necessary.
      This fact can explain how ptrace/signal allows to break the deadlock.

  --
  I posted the bug report here because the glibc's wiki strongly recommends to start from the distribution bug tracker. All arm64-based devices I tested were running Ubuntu 18.04.

  ProblemType: Bug
  DistroRelease: Ubuntu 18.04
  Package: libc6 2.27-3ubuntu1
  Uname: Linux 4.9.187-52 aarch64
  ApportVersion: 2.20.9-0ubuntu7.15
  Architecture: arm64
  Date: Fri Jul 24 14:05:57 2020
  Dependencies:
   gcc-8-base 8.3.0-6ubuntu1~18.04.1
   libc6 2.27-3ubuntu1
   libgcc1 1:8.3.0-6ubuntu1~18.04.1
  ProcEnviron:
   TERM=rxvt-unicode-256color
   PATH=(custom, no user)
   XDG_RUNTIME_DIR=<set>
   LANG=C.UTF-8
   SHELL=/bin/bash
  SourcePackage: glibc
  UpgradeStatus: No upgrade log present (probably fresh install)

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1888857/+subscriptions



More information about the foundations-bugs mailing list