| /* Test program for a read-phase / write-phase explicit hand-over. |
| Copyright (C) 2017-2018 Free Software Foundation, Inc. |
| |
| The GNU C Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public License as |
| published by the Free Software Foundation; either version 2.1 of the |
| License, or (at your option) any later version. |
| |
| The GNU C Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; see the file COPYING.LIB. If |
| not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include <errno.h> |
| #include <error.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <time.h> |
| #include <atomic.h> |
| #include <support/xthread.h> |
| |
| /* We realy want to set threads to 2 to reproduce this issue. The goal |
| is to have one primary writer and a single reader, and to hit the |
| bug that happens in the interleaving of those two phase transitions. |
| However, on most hardware, adding a second writer seems to help the |
| interleaving happen slightly more often, say 20% of the time. On a |
| 16 core ppc64 machine this fails 100% of the time with an unpatched |
| glibc. On a 8 core x86_64 machine this fails ~93% of the time, but |
| it doesn't fail at all on a 4 core system, so having available |
| unloaded cores makes a big difference in reproducibility. On an 8 |
| core qemu/kvm guest the reproducer reliability drops to ~10%. */ |
| #define THREADS 3 |
| |
| #define KIND PTHREAD_RWLOCK_PREFER_READER_NP |
| |
| static pthread_rwlock_t lock; |
| static int done = 0; |
| |
| static void* |
| tf (void* arg) |
| { |
| while (atomic_load_relaxed (&done) == 0) |
| { |
| int rcnt = 0; |
| int wcnt = 100; |
| if ((uintptr_t) arg == 0) |
| { |
| rcnt = 1; |
| wcnt = 1; |
| } |
| |
| do |
| { |
| if (wcnt) |
| { |
| xpthread_rwlock_wrlock (&lock); |
| xpthread_rwlock_unlock (&lock); |
| wcnt--; |
| } |
| if (rcnt) |
| { |
| xpthread_rwlock_rdlock (&lock); |
| xpthread_rwlock_unlock (&lock); |
| rcnt--; |
| } |
| } |
| while ((atomic_load_relaxed (&done) == 0) && (rcnt + wcnt > 0)); |
| |
| } |
| return NULL; |
| } |
| |
| |
| |
| static int |
| do_test (void) |
| { |
| pthread_t thr[THREADS]; |
| int n; |
| pthread_rwlockattr_t attr; |
| |
| xpthread_rwlockattr_init (&attr); |
| xpthread_rwlockattr_setkind_np (&attr, KIND); |
| |
| xpthread_rwlock_init (&lock, &attr); |
| |
| /* Make standard error the same as standard output. */ |
| dup2 (1, 2); |
| |
| /* Make sure we see all message, even those on stdout. */ |
| setvbuf (stdout, NULL, _IONBF, 0); |
| |
| for (n = 0; n < THREADS; ++n) |
| thr[n] = xpthread_create (NULL, tf, (void *) (uintptr_t) n); |
| |
| struct timespec delay; |
| delay.tv_sec = 10; |
| delay.tv_nsec = 0; |
| nanosleep (&delay, NULL); |
| atomic_store_relaxed (&done, 1); |
| |
| /* Wait for all the threads. */ |
| for (n = 0; n < THREADS; ++n) |
| xpthread_join (thr[n]); |
| |
| return 0; |
| } |
| |
| #include <support/test-driver.c> |