Race in pthread_mutex_lock while promoting to PTHREAD_MUTEX_ELISION_NP.

Stefan Liebler stli@linux.vnet.ibm.com
Thu May 24 14:39:00 GMT 2018


Hi,

while looking for a possible reason for "pthread_mutex_lock.c:79: 
__pthread_mutex_lock: Assertion `mutex->__data.__owner == 0' failed.", 
I've found a race condition if pthread_mutex_lock() is called for the 
"first time" by multiple threads.
Note: Lock elision has to be supported and enabled (export 
GLIBC_TUNABLES=glibc.elision.enable=1).

The attached test program creates threads which runs multiple iterations 
of the following sequence:
-pthread_mutex_init(): called only by one thread. Initializing with 
default mutex attributes.
-pthread_mutex_lock()
-pthread_mutex_unlock()
-pthread_mutex_destroy(): called only by one thread.

Here are some code snippets in order to follow the explanation below:
-nptl/pthread_mutex_lock.c:
  62int
  63__pthread_mutex_lock (pthread_mutex_t *mutex)
  64{
  65  unsigned int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);
  ...
  73  if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_NP))
  74    {
  75      FORCE_ELISION (mutex, goto elision);
  76    simple:
  77      /* Normal mutex.  */
  78      LLL_MUTEX_LOCK (mutex);
  79      assert (mutex->__data.__owner == 0);
  80    }
  81#ifdef HAVE_ELISION
  82  else if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_ELISION_NP))
  83    {
  84  elision: __attribute__((unused))
  85      /* This case can never happen on a system without elision, 

  86         as the mutex type initialization functions will not 

  87         allow to set the elision flags.  */
  88      /* Don't record owner or users for elision case.  This is a 

  89         tail call.  */
  90      return LLL_MUTEX_LOCK_ELISION (mutex);
  91    }
  92#endif
...
156  /* Record the ownership.  */
157  mutex->__data.__owner = id;
158#ifndef NO_INCR
159  ++mutex->__data.__nusers;
160#endif


-sysdeps/unix/sysv/linux/s390/force-elision.h:
Note: This file is identical to the equivalent files for x86 and powerpc!
19/* Automatically enable elision for existing user lock kinds.  */
20#define FORCE_ELISION(m, s) \
21  if (__pthread_force_elision \
22      && (m->__data.__kind & PTHREAD_MUTEX_ELISION_FLAGS_NP) == 0) \
23    { \
24      mutex->__data.__kind |= PTHREAD_MUTEX_ELISION_NP; \
25      s; \
26    }

-nptl/pthread_mutex_unlock.c:
  36__pthread_mutex_unlock_usercnt (pthread_mutex_t *mutex, int decr)
  37{
  38  int type = PTHREAD_MUTEX_TYPE_ELISION (mutex);
...
  43  if (__builtin_expect (type, PTHREAD_MUTEX_TIMED_NP)
  44      == PTHREAD_MUTEX_TIMED_NP)
  45    {
  46      /* Always reset the owner field.  */
  47    normal:
  48      mutex->__data.__owner = 0;
  49      if (decr)
  50        /* One less user.  */
  51        --mutex->__data.__nusers;
...
  60  else if (__glibc_likely (type == PTHREAD_MUTEX_TIMED_ELISION_NP))
  61    {
  62      /* Don't reset the owner/users fields for elision.  */
  63      return lll_unlock_elision (mutex->__data.__lock, 
mutex->__data.__elision,
  64                                      PTHREAD_MUTEX_PSHARED (mutex));

-nptl/pthread_mutex_destroy.c:
26__pthread_mutex_destroy (pthread_mutex_t *mutex)
27{
28  LIBC_PROBE (mutex_destroy, 1, mutex);
29
30  if ((mutex->__data.__kind & PTHREAD_MUTEX_ROBUST_NORMAL_NP) == 0
31      && mutex->__data.__nusers != 0)
32    return EBUSY;



Assumption: Two threads are calling pthread_mutex_lock at the same time 
and have already loaded the mutex-type in line 65 with the type 
PTHREAD_MUTEX_TIMED_NP.

Thread 1 promotes the mutex to PTHREAD_MUTEX_ELISION_NP (see 
FORCE_ELISION in line 24) and jumps to LLL_MUTEX_LOCK_ELISION (see line 90).

Thread 2 is checking the mutex type in FORCE_ELISION (line 22).
As the mutex-type is already set to PTHREAD_MUTEX_ELISION_NP, the 
condition is false and the "Normal mutex" (line 77) is processed.
Note: The "normal mutex" records the ownership (see line 156).

Both threads are calling pthread_mutex_unlock and are loading mutex-type 
in line 38 with the type PTHREAD_MUTEX_TIMED_ELISION_NP.
Thus the ownership is not resetted (see line 62)!

The call to pthread_mutex_destroy is returning with EBUSY
as __nusers == 1 (see line 31).
Note: Although thread 2 has successfully called pthread_mutex_unlock, it 
is marked as the current owner of the mutex.

If a third thread has also loaded the mutex-type PTHREAD_MUTEX_TIMED_NP 
in pthread_mutex_lock, then the "assert (mutex->__data.__owner == 0)" in 
line 79 is triggered.



When I am running the attached test program on s390x, I can trigger the 
case "pthread_mutex_destroy()==EBUSY" within some thousand iterations.
And I could trigger the assertion while single-stepping in GDB.

Can somebody test and confirm this on x86, power, s390?

How can we solve this issue?

Bye
Stefan
-------------- next part --------------
A non-text attachment was scrubbed...
Name: tst_pthread_lock_assertion.c
Type: text/x-csrc
Size: 3961 bytes
Desc: not available
URL: <http://sourceware.org/pipermail/libc-alpha/attachments/20180524/5de33eaf/attachment.bin>


More information about the Libc-alpha mailing list