diff options
Diffstat (limited to 'sysdeps/pthread')
-rw-r--r-- | sysdeps/pthread/Makefile | 2 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock-tryrdlock-stall.c | 355 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock-trywrlock-stall.c | 108 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock1.c | 116 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock13.c | 70 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock16.c | 182 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock4.c | 189 | ||||
-rw-r--r-- | sysdeps/pthread/tst-rwlock5.c | 84 |
8 files changed, 1106 insertions, 0 deletions
diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile index 6b49e33bdc..cceaa91837 100644 --- a/sysdeps/pthread/Makefile +++ b/sysdeps/pthread/Makefile @@ -52,6 +52,8 @@ tests += tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \ tst-cond-except \ tst-mutex1 tst-mutex2 tst-mutex3 tst-mutex4 tst-mutex6 tst-mutex10 \ tst-once1 tst-once2 tst-once3 tst-once4 \ + tst-rwlock1 tst-rwlock4 tst-rwlock5 tst-rwlock13 tst-rwlock16 \ + tst-rwlock-tryrdlock-stall tst-rwlock-trywrlock-stall \ tst-spin1 tst-spin2 tst-spin3 tst-spin4 tests += tst-oncex3 tst-oncex4 diff --git a/sysdeps/pthread/tst-rwlock-tryrdlock-stall.c b/sysdeps/pthread/tst-rwlock-tryrdlock-stall.c new file mode 100644 index 0000000000..bb2a64f301 --- /dev/null +++ b/sysdeps/pthread/tst-rwlock-tryrdlock-stall.c @@ -0,0 +1,355 @@ +/* Bug 23844: Test for pthread_rwlock_tryrdlock stalls. + Copyright (C) 2019-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +/* For a full analysis see comment: + https://sourceware.org/bugzilla/show_bug.cgi?id=23844#c14 + + Provided here for reference: + + --- Analysis of pthread_rwlock_tryrdlock() stall --- + A read lock begins to execute. + + In __pthread_rwlock_rdlock_full: + + We can attempt a read lock, but find that the lock is + in a write phase (PTHREAD_RWLOCK_WRPHASE, or WP-bit + is set), and the lock is held by a primary writer + (PTHREAD_RWLOCK_WRLOCKED is set). In this case we must + wait for explicit hand over from the writer to us or + one of the other waiters. The read lock threads are + about to execute: + + 341 r = (atomic_fetch_add_acquire (&rwlock->__data.__readers, + 342 (1 << PTHREAD_RWLOCK_READER_SHIFT)) + 343 + (1 << PTHREAD_RWLOCK_READER_SHIFT)); + + An unlock beings to execute. + + Then in __pthread_rwlock_wrunlock: + + 547 unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers); + ... + 549 while (!atomic_compare_exchange_weak_release + 550 (&rwlock->__data.__readers, &r, + 551 ((r ^ PTHREAD_RWLOCK_WRLOCKED) + 552 ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0 + 553 : PTHREAD_RWLOCK_WRPHASE)))) + 554 { + ... + 556 } + + We clear PTHREAD_RWLOCK_WRLOCKED, and if there are + no readers so we leave the lock in PTHRAD_RWLOCK_WRPHASE. + + Back in the read lock. + + The read lock adjusts __readres as above. + + 383 while ((r & PTHREAD_RWLOCK_WRPHASE) != 0 + 384 && (r & PTHREAD_RWLOCK_WRLOCKED) == 0) + 385 { + ... + 390 if (atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers, &r, + 391 r ^ PTHREAD_RWLOCK_WRPHASE)) + 392 { + + And then attemps to start the read phase. + + Assume there happens to be a tryrdlock at this point, noting + that PTHREAD_RWLOCK_WRLOCKED is clear, and PTHREAD_RWLOCK_WRPHASE + is 1. So the try lock attemps to start the read phase. + + In __pthread_rwlock_tryrdlock: + + 44 if ((r & PTHREAD_RWLOCK_WRPHASE) == 0) + 45 { + ... + 49 if (((r & PTHREAD_RWLOCK_WRLOCKED) != 0) + 50 && (rwlock->__data.__flags + 51 == PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)) + 52 return EBUSY; + 53 rnew = r + (1 << PTHREAD_RWLOCK_READER_SHIFT); + 54 } + ... + 89 while (!atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers, + 90 &r, rnew)); + + And succeeds. + + Back in the write unlock: + + 557 if ((r >> PTHREAD_RWLOCK_READER_SHIFT) != 0) + 558 { + ... + 563 if ((atomic_exchange_relaxed (&rwlock->__data.__wrphase_futex, 0) + 564 & PTHREAD_RWLOCK_FUTEX_USED) != 0) + 565 futex_wake (&rwlock->__data.__wrphase_futex, INT_MAX, private); + 566 } + + We note that PTHREAD_RWLOCK_FUTEX_USED is non-zero + and don't wake anyone. This is OK because we handed + over to the trylock. It will be the trylock's responsibility + to wake any waiters. + + Back in the read lock: + + The read lock fails to install PTHRAD_REWLOCK_WRPHASE as 0 because + the __readers value was adjusted by the trylock, and so it falls through + to waiting on the lock for explicit handover from either a new writer + or a new reader. + + 448 int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex, + 449 1 | PTHREAD_RWLOCK_FUTEX_USED, + 450 abstime, private); + + We use PTHREAD_RWLOCK_FUTEX_USED to indicate the futex + is in use. + + At this point we have readers waiting on the read lock + to unlock. The wrlock is done. The trylock is finishing + the installation of the read phase. + + 92 if ((r & PTHREAD_RWLOCK_WRPHASE) != 0) + 93 { + ... + 105 atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 0); + 106 } + + The trylock does note that we were the one that + installed the read phase, but the comments are not + correct, the execution ordering above shows that + readers might indeed be waiting, and they are. + + The atomic_store_relaxed throws away PTHREAD_RWLOCK_FUTEX_USED, + and the waiting reader is never worken becuase as noted + above it is conditional on the futex being used. + + The solution is for the trylock thread to inspect + PTHREAD_RWLOCK_FUTEX_USED and wake the waiting readers. + + --- Analysis of pthread_rwlock_trywrlock() stall --- + + A write lock begins to execute, takes the write lock, + and then releases the lock... + + In pthread_rwlock_wrunlock(): + + 547 unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers); + ... + 549 while (!atomic_compare_exchange_weak_release + 550 (&rwlock->__data.__readers, &r, + 551 ((r ^ PTHREAD_RWLOCK_WRLOCKED) + 552 ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0 + 553 : PTHREAD_RWLOCK_WRPHASE)))) + 554 { + ... + 556 } + + ... leaving it in the write phase with zero readers + (the case where we leave the write phase in place + during a write unlock). + + A write trylock begins to execute. + + In __pthread_rwlock_trywrlock: + + 40 while (((r & PTHREAD_RWLOCK_WRLOCKED) == 0) + 41 && (((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0) + 42 || (prefer_writer && ((r & PTHREAD_RWLOCK_WRPHASE) != 0)))) + 43 { + + The lock is not locked. + + There are no readers. + + 45 if (atomic_compare_exchange_weak_acquire ( + 46 &rwlock->__data.__readers, &r, + 47 r | PTHREAD_RWLOCK_WRPHASE | PTHREAD_RWLOCK_WRLOCKED)) + + We atomically install the write phase and we take the + exclusive write lock. + + 48 { + 49 atomic_store_relaxed (&rwlock->__data.__writers_futex, 1); + + We get this far. + + A reader lock begins to execute. + + In pthread_rwlock_rdlock: + + 437 for (;;) + 438 { + 439 while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex)) + 440 | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED)) + 441 { + 442 int private = __pthread_rwlock_get_private (rwlock); + 443 if (((wpf & PTHREAD_RWLOCK_FUTEX_USED) == 0) + 444 && (!atomic_compare_exchange_weak_relaxed + 445 (&rwlock->__data.__wrphase_futex, + 446 &wpf, wpf | PTHREAD_RWLOCK_FUTEX_USED))) + 447 continue; + 448 int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex, + 449 1 | PTHREAD_RWLOCK_FUTEX_USED, + 450 abstime, private); + + We are in a write phase, so the while() on line 439 is true. + + The value of wpf does not have PTHREAD_RWLOCK_FUTEX_USED set + since this is the first reader to lock. + + The atomic operation sets wpf with PTHREAD_RELOCK_FUTEX_USED + on the expectation that this reader will be woken during + the handoff. + + Back in pthread_rwlock_trywrlock: + + 50 atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 1); + 51 atomic_store_relaxed (&rwlock->__data.__cur_writer, + 52 THREAD_GETMEM (THREAD_SELF, tid)); + 53 return 0; + 54 } + ... + 57 } + + We write 1 to __wrphase_futex discarding PTHREAD_RWLOCK_FUTEX_USED, + and so in the unlock we will not awaken the waiting reader. + + The solution to this is to realize that if we did not start the write + phase we need not write 1 or any other value to __wrphase_futex. + This ensures that any readers (which saw __wrphase_futex != 0) can + set PTHREAD_RWLOCK_FUTEX_USED and this can be used at unlock to + wake them. + + If we installed the write phase then all other readers are looping + here: + + In __pthread_rwlock_rdlock_full: + + 437 for (;;) + 438 { + 439 while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex)) + 440 | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED)) + 441 { + ... + 508 } + + waiting for the write phase to be installed or removed before they + can begin waiting on __wrphase_futex (part of the algorithm), or + taking a concurrent read lock, and thus we can safely write 1 to + __wrphase_futex. + + If we did not install the write phase then the readers may already + be waiting on the futex, the original writer wrote 1 to __wrphase_futex + as part of starting the write phase, and we cannot also write 1 + without loosing the PTHREAD_RWLOCK_FUTEX_USED bit. + + --- + + Summary for the pthread_rwlock_tryrdlock() stall: + + The stall is caused by pthread_rwlock_tryrdlock failing to check + that PTHREAD_RWLOCK_FUTEX_USED is set in the __wrphase_futex futex + and then waking the futex. + + The fix for bug 23844 ensures that waiters on __wrphase_futex are + correctly woken. Before the fix the test stalls as readers can + wait forever on __wrphase_futex. */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <support/xthread.h> +#include <errno.h> + +/* We need only one lock to reproduce the issue. We will need multiple + threads to get the exact case where we have a read, try, and unlock + all interleaving to produce the case where the readers are waiting + and the try fails to wake them. */ +pthread_rwlock_t onelock; + +/* The number of threads is arbitrary but empirically chosen to have + enough threads that we see the condition where waiting readers are + not woken by a successful tryrdlock. */ +#define NTHREADS 32 + +_Atomic int do_exit; + +void * +run_loop (void *arg) +{ + int i = 0, ret; + while (!do_exit) + { + /* Arbitrarily choose if we are the writer or reader. Choose a + high enough ratio of readers to writers to make it likely + that readers block (and eventually are susceptable to + stalling). + + If we are a writer, take the write lock, and then unlock. + If we are a reader, try the lock, then lock, then unlock. */ + if ((i % 8) != 0) + xpthread_rwlock_wrlock (&onelock); + else + { + if ((ret = pthread_rwlock_tryrdlock (&onelock)) != 0) + { + if (ret == EBUSY) + xpthread_rwlock_rdlock (&onelock); + else + exit (EXIT_FAILURE); + } + } + /* Thread does some work and then unlocks. */ + xpthread_rwlock_unlock (&onelock); + i++; + } + return NULL; +} + +int +do_test (void) +{ + int i; + pthread_t tids[NTHREADS]; + xpthread_rwlock_init (&onelock, NULL); + for (i = 0; i < NTHREADS; i++) + tids[i] = xpthread_create (NULL, run_loop, NULL); + /* Run for some amount of time. Empirically speaking exercising + the stall via pthread_rwlock_tryrdlock is much harder, and on + a 3.5GHz 4 core x86_64 VM system it takes somewhere around + 20-200s to stall, approaching 100% stall past 200s. We can't + wait that long for a regression test so we just test for 20s, + and expect the stall to happen with a 5-10% chance (enough for + developers to see). */ + sleep (20); + /* Then exit. */ + printf ("INFO: Exiting...\n"); + do_exit = 1; + /* If any readers stalled then we will timeout waiting for them. */ + for (i = 0; i < NTHREADS; i++) + xpthread_join (tids[i]); + printf ("INFO: Done.\n"); + xpthread_rwlock_destroy (&onelock); + printf ("PASS: No pthread_rwlock_tryrdlock stalls detected.\n"); + return 0; +} + +#define TIMEOUT 30 +#include <support/test-driver.c> diff --git a/sysdeps/pthread/tst-rwlock-trywrlock-stall.c b/sysdeps/pthread/tst-rwlock-trywrlock-stall.c new file mode 100644 index 0000000000..b4e60c5af3 --- /dev/null +++ b/sysdeps/pthread/tst-rwlock-trywrlock-stall.c @@ -0,0 +1,108 @@ +/* Bug 23844: Test for pthread_rwlock_trywrlock stalls. + Copyright (C) 2019-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +/* For a full analysis see comments in tst-rwlock-tryrdlock-stall.c. + + Summary for the pthread_rwlock_trywrlock() stall: + + The stall is caused by pthread_rwlock_trywrlock setting + __wrphase_futex futex to 1 and loosing the + PTHREAD_RWLOCK_FUTEX_USED bit. + + The fix for bug 23844 ensures that waiters on __wrphase_futex are + correctly woken. Before the fix the test stalls as readers can + wait forever on __wrphase_futex. */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <support/xthread.h> +#include <errno.h> + +/* We need only one lock to reproduce the issue. We will need multiple + threads to get the exact case where we have a read, try, and unlock + all interleaving to produce the case where the readers are waiting + and the try clears the PTHREAD_RWLOCK_FUTEX_USED bit and a + subsequent unlock fails to wake them. */ +pthread_rwlock_t onelock; + +/* The number of threads is arbitrary but empirically chosen to have + enough threads that we see the condition where waiting readers are + not woken by a successful unlock. */ +#define NTHREADS 32 + +_Atomic int do_exit; + +void * +run_loop (void *arg) +{ + int i = 0, ret; + while (!do_exit) + { + /* Arbitrarily choose if we are the writer or reader. Choose a + high enough ratio of readers to writers to make it likely + that readers block (and eventually are susceptable to + stalling). + + If we are a writer, take the write lock, and then unlock. + If we are a reader, try the lock, then lock, then unlock. */ + if ((i % 8) != 0) + { + if ((ret = pthread_rwlock_trywrlock (&onelock)) != 0) + { + if (ret == EBUSY) + xpthread_rwlock_wrlock (&onelock); + else + exit (EXIT_FAILURE); + } + } + else + xpthread_rwlock_rdlock (&onelock); + /* Thread does some work and then unlocks. */ + xpthread_rwlock_unlock (&onelock); + i++; + } + return NULL; +} + +int +do_test (void) +{ + int i; + pthread_t tids[NTHREADS]; + xpthread_rwlock_init (&onelock, NULL); + for (i = 0; i < NTHREADS; i++) + tids[i] = xpthread_create (NULL, run_loop, NULL); + /* Run for some amount of time. The pthread_rwlock_tryrwlock stall + is very easy to trigger and happens in seconds under the test + conditions. */ + sleep (10); + /* Then exit. */ + printf ("INFO: Exiting...\n"); + do_exit = 1; + /* If any readers stalled then we will timeout waiting for them. */ + for (i = 0; i < NTHREADS; i++) + xpthread_join (tids[i]); + printf ("INFO: Done.\n"); + xpthread_rwlock_destroy (&onelock); + printf ("PASS: No pthread_rwlock_tryrwlock stalls detected.\n"); + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/pthread/tst-rwlock1.c b/sysdeps/pthread/tst-rwlock1.c new file mode 100644 index 0000000000..d14d3036a2 --- /dev/null +++ b/sysdeps/pthread/tst-rwlock1.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2002. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <pthread.h> +#include <stdio.h> + + +static int +do_test (void) +{ + pthread_rwlock_t r; + + if (pthread_rwlock_init (&r, NULL) != 0) + { + puts ("rwlock_init failed"); + return 1; + } + puts ("rwlock_init succeeded"); + + if (pthread_rwlock_rdlock (&r) != 0) + { + puts ("1st rwlock_rdlock failed"); + return 1; + } + puts ("1st rwlock_rdlock succeeded"); + + if (pthread_rwlock_rdlock (&r) != 0) + { + puts ("2nd rwlock_rdlock failed"); + return 1; + } + puts ("2nd rwlock_rdlock succeeded"); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("1st rwlock_unlock failed"); + return 1; + } + puts ("1st rwlock_unlock succeeded"); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("2nd rwlock_unlock failed"); + return 1; + } + puts ("2nd rwlock_unlock succeeded"); + + if (pthread_rwlock_wrlock (&r) != 0) + { + puts ("1st rwlock_wrlock failed"); + return 1; + } + puts ("1st rwlock_wrlock succeeded"); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("3rd rwlock_unlock failed"); + return 1; + } + puts ("3rd rwlock_unlock succeeded"); + + if (pthread_rwlock_wrlock (&r) != 0) + { + puts ("2nd rwlock_wrlock failed"); + return 1; + } + puts ("2nd rwlock_wrlock succeeded"); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("4th rwlock_unlock failed"); + return 1; + } + puts ("4th rwlock_unlock succeeded"); + + if (pthread_rwlock_rdlock (&r) != 0) + { + puts ("3rd rwlock_rdlock failed"); + return 1; + } + puts ("3rd rwlock_rdlock succeeded"); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("5th rwlock_unlock failed"); + return 1; + } + puts ("5th rwlock_unlock succeeded"); + + if (pthread_rwlock_destroy (&r) != 0) + { + puts ("rwlock_destroy failed"); + return 1; + } + puts ("rwlock_destroy succeeded"); + + return 0; +} + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" diff --git a/sysdeps/pthread/tst-rwlock13.c b/sysdeps/pthread/tst-rwlock13.c new file mode 100644 index 0000000000..749ec4b621 --- /dev/null +++ b/sysdeps/pthread/tst-rwlock13.c @@ -0,0 +1,70 @@ +/* Copyright (C) 2004-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <pthread.h> +#include <stdio.h> +#include <string.h> + + +static int +do_test (void) +{ + pthread_rwlock_t r; + int ret; + + memset (&r, 0xaa, sizeof (r)); + if ((ret = pthread_rwlock_init (&r, NULL)) != 0) + { + printf ("rwlock_init failed: %d\n", ret); + return 1; + } + + if ((ret = pthread_rwlock_rdlock (&r)) != 0) + { + printf ("rwlock_rdlock failed: %d\n", ret); + return 1; + } + + if ((ret = pthread_rwlock_unlock (&r)) != 0) + { + printf ("rwlock_unlock failed: %d\n", ret); + return 1; + } + + if ((ret = pthread_rwlock_wrlock (&r)) != 0) + { + printf ("rwlock_wrlock failed: %d\n", ret); + return 1; + } + + if ((ret = pthread_rwlock_unlock (&r)) != 0) + { + printf ("second rwlock_unlock failed: %d\n", ret); + return 1; + } + + if ((ret = pthread_rwlock_destroy (&r)) != 0) + { + printf ("second rwlock_destroy failed: %d\n", ret); + return 1; + } + + return 0; +} + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" diff --git a/sysdeps/pthread/tst-rwlock16.c b/sysdeps/pthread/tst-rwlock16.c new file mode 100644 index 0000000000..29720c9d8d --- /dev/null +++ b/sysdeps/pthread/tst-rwlock16.c @@ -0,0 +1,182 @@ +/* Copyright (C) 2015-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +/* This tests that with a reader-preferring rwlock, all readers are woken if + one reader "steals" lock ownership from a blocked writer. */ + +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <semaphore.h> +#include <unistd.h> + +/* If we strictly prefer writers over readers, a program must not expect + that, in the presence of concurrent writers, one reader will also acquire + the lock when another reader has already done so. Thus, use the + default rwlock type that does not strictly prefer writers. */ +static pthread_rwlock_t r = PTHREAD_RWLOCK_INITIALIZER; + +static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +/* Avoid using glibc-internal atomic operations. */ +static sem_t stop; +static int consumer_stop = 0; + +static void * +writer (void *arg) +{ + int s; + + do + { + if (pthread_rwlock_wrlock (&r) != 0) + { + puts ("wrlock failed"); + exit (EXIT_FAILURE); + } + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("unlock failed"); + exit (EXIT_FAILURE); + } + sem_getvalue (&stop, &s); + } + while (s == 0); + return NULL; +} + +static void * +reader_producer (void *arg) +{ + int s; + + do + { + if (pthread_rwlock_rdlock (&r) != 0) + { + puts ("rdlock reader failed"); + exit (EXIT_FAILURE); + } + + sem_getvalue (&stop, &s); + + pthread_mutex_lock (&m); + if (s != 0) + consumer_stop = 1; + pthread_cond_signal (&cv); + pthread_mutex_unlock (&m); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("unlock reader failed"); + exit (EXIT_FAILURE); + } + } + while (s == 0); + puts ("producer finished"); + return NULL; +} + +static void * +reader_consumer (void *arg) +{ + int s; + + do + { + if (pthread_rwlock_rdlock (&r) != 0) + { + puts ("rdlock reader failed"); + exit (EXIT_FAILURE); + } + + pthread_mutex_lock (&m); + s = consumer_stop; + if (s == 0) + pthread_cond_wait (&cv, &m); + pthread_mutex_unlock (&m); + + if (pthread_rwlock_unlock (&r) != 0) + { + puts ("unlock reader failed"); + exit (EXIT_FAILURE); + } + } + while (s == 0); + puts ("consumer finished"); + return NULL; +} + + +static int +do_test (void) +{ + pthread_t w1, w2, rp, rc; + + if (pthread_create (&w1, NULL, writer, NULL) != 0) + { + puts ("create failed"); + return 1; + } + if (pthread_create (&w2, NULL, writer, NULL) != 0) + { + puts ("create failed"); + return 1; + } + if (pthread_create (&rc, NULL, reader_consumer, NULL) != 0) + { + puts ("create failed"); + return 1; + } + if (pthread_create (&rp, NULL, reader_producer, NULL) != 0) + { + puts ("create failed"); + return 1; + } + + sleep (2); + sem_post (&stop); + + if (pthread_join (w1, NULL) != 0) + { + puts ("w1 join failed"); + return 1; + } + if (pthread_join (w2, NULL) != 0) + { + puts ("w2 join failed"); + return 1; + } + if (pthread_join (rp, NULL) != 0) + { + puts ("reader_producer join failed"); + return 1; + } + if (pthread_join (rc, NULL) != 0) + { + puts ("reader_consumer join failed"); + return 1; + } + + return 0; +} + + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" diff --git a/sysdeps/pthread/tst-rwlock4.c b/sysdeps/pthread/tst-rwlock4.c new file mode 100644 index 0000000000..479107a5f2 --- /dev/null +++ b/sysdeps/pthread/tst-rwlock4.c @@ -0,0 +1,189 @@ +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2002. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/wait.h> + + +static int +do_test (void) +{ + size_t ps = sysconf (_SC_PAGESIZE); + char tmpfname[] = "/tmp/tst-rwlock4.XXXXXX"; + char data[ps]; + void *mem; + int fd; + pthread_rwlock_t *r; + pthread_rwlockattr_t a; + pid_t pid; + char *p; + int err; + int s; + + fd = mkstemp (tmpfname); + if (fd == -1) + { + printf ("cannot open temporary file: %m\n"); + return 1; + } + + /* Make sure it is always removed. */ + unlink (tmpfname); + + /* Create one page of data. */ + memset (data, '\0', ps); + + /* Write the data to the file. */ + if (write (fd, data, ps) != (ssize_t) ps) + { + puts ("short write"); + return 1; + } + + mem = mmap (NULL, ps, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mem == MAP_FAILED) + { + printf ("mmap failed: %m\n"); + return 1; + } + + r = (pthread_rwlock_t *) (((uintptr_t) mem + __alignof (pthread_rwlock_t)) + & ~(__alignof (pthread_rwlock_t) - 1)); + p = (char *) (r + 1); + + if (pthread_rwlockattr_init (&a) != 0) + { + puts ("rwlockattr_init failed"); + return 1; + } + + if (pthread_rwlockattr_getpshared (&a, &s) != 0) + { + puts ("1st rwlockattr_getpshared failed"); + return 1; + } + + if (s != PTHREAD_PROCESS_PRIVATE) + { + puts ("default pshared value wrong"); + return 1; + } + + if (pthread_rwlockattr_setpshared (&a, PTHREAD_PROCESS_SHARED) != 0) + { + puts ("rwlockattr_setpshared failed"); + return 1; + } + + if (pthread_rwlockattr_getpshared (&a, &s) != 0) + { + puts ("2nd rwlockattr_getpshared failed"); + return 1; + } + + if (s != PTHREAD_PROCESS_SHARED) + { + puts ("pshared value after setpshared call wrong"); + return 1; + } + + if (pthread_rwlock_init (r, &a) != 0) + { + puts ("rwlock_init failed"); + return 1; + } + + if (pthread_rwlock_rdlock (r) != 0) + { + puts ("rwlock_rdlock failed"); + return 1; + } + + if (pthread_rwlockattr_destroy (&a) != 0) + { + puts ("rwlockattr_destroy failed"); + return 1; + } + + err = pthread_rwlock_trywrlock (r); + if (err == 0) + { + puts ("rwlock_trywrlock succeeded"); + return 1; + } + else if (err != EBUSY) + { + puts ("rwlock_trywrlock didn't return EBUSY"); + return 1; + } + + *p = 0; + + puts ("going to fork now"); + pid = fork (); + if (pid == -1) + { + puts ("fork failed"); + return 1; + } + else if (pid == 0) + { + /* Play some lock ping-pong. It's our turn to unlock first. */ + if ((*p)++ != 0) + { + puts ("child: *p != 0"); + return 1; + } + + if (pthread_rwlock_unlock (r) != 0) + { + puts ("child: 1st rwlock_unlock failed"); + return 1; + } + + puts ("child done"); + } + else + { + if (pthread_rwlock_wrlock (r) != 0) + { + puts ("parent: rwlock_wrlock failed"); + return 1; + } + + if (*p != 1) + { + puts ("*p != 1"); + return 1; + } + + puts ("parent done"); + } + + return 0; +} + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" diff --git a/sysdeps/pthread/tst-rwlock5.c b/sysdeps/pthread/tst-rwlock5.c new file mode 100644 index 0000000000..c589764196 --- /dev/null +++ b/sysdeps/pthread/tst-rwlock5.c @@ -0,0 +1,84 @@ +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2002. + + 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; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static int do_test (void); + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" + +static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; +static pthread_rwlock_t r; + + +static void * +tf (void *arg) +{ + if (pthread_rwlock_wrlock (&r) == 0) + { + puts ("child: rwlock_wrlock succeeded"); + exit (1); + } + + puts ("child: rwlock_wrlock returned"); + + exit (1); +} + + +static int +do_test (void) +{ + pthread_t th; + + if (pthread_rwlock_init (&r, NULL) != 0) + { + puts ("rwlock_init failed"); + return 1; + } + + if (pthread_rwlock_wrlock (&r) != 0) + { + puts ("rwlock_wrlock failed"); + return 1; + } + + if (pthread_mutex_lock (&m) != 0) + { + puts ("mutex_lock failed"); + return 1; + } + + if (pthread_create (&th, NULL, tf, NULL) != 0) + { + puts ("create failed"); + return 1; + } + + delayed_exit (1); + /* This call should never return. */ + xpthread_mutex_lock (&m); + + puts ("2nd mutex_lock returned"); + return 1; +} |