1 /* Bug 23844: Test for pthread_rwlock_tryrdlock stalls.
2 Copyright (C) 2019-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
19 /* For a full analysis see comment:
20 https://sourceware.org/bugzilla/show_bug.cgi?id=23844#c14
22 Provided here for reference:
24 --- Analysis of pthread_rwlock_tryrdlock() stall ---
25 A read lock begins to execute.
27 In __pthread_rwlock_rdlock_full:
29 We can attempt a read lock, but find that the lock is
30 in a write phase (PTHREAD_RWLOCK_WRPHASE, or WP-bit
31 is set), and the lock is held by a primary writer
32 (PTHREAD_RWLOCK_WRLOCKED is set). In this case we must
33 wait for explicit hand over from the writer to us or
34 one of the other waiters. The read lock threads are
37 341 r = (atomic_fetch_add_acquire (&rwlock->__data.__readers,
38 342 (1 << PTHREAD_RWLOCK_READER_SHIFT))
39 343 + (1 << PTHREAD_RWLOCK_READER_SHIFT));
41 An unlock beings to execute.
43 Then in __pthread_rwlock_wrunlock:
45 547 unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
47 549 while (!atomic_compare_exchange_weak_release
48 550 (&rwlock->__data.__readers, &r,
49 551 ((r ^ PTHREAD_RWLOCK_WRLOCKED)
50 552 ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0
51 553 : PTHREAD_RWLOCK_WRPHASE))))
56 We clear PTHREAD_RWLOCK_WRLOCKED, and if there are
57 no readers so we leave the lock in PTHRAD_RWLOCK_WRPHASE.
59 Back in the read lock.
61 The read lock adjusts __readres as above.
63 383 while ((r & PTHREAD_RWLOCK_WRPHASE) != 0
64 384 && (r & PTHREAD_RWLOCK_WRLOCKED) == 0)
67 390 if (atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers, &r,
68 391 r ^ PTHREAD_RWLOCK_WRPHASE))
71 And then attempts to start the read phase.
73 Assume there happens to be a tryrdlock at this point, noting
74 that PTHREAD_RWLOCK_WRLOCKED is clear, and PTHREAD_RWLOCK_WRPHASE
75 is 1. So the try lock attempts to start the read phase.
77 In __pthread_rwlock_tryrdlock:
79 44 if ((r & PTHREAD_RWLOCK_WRPHASE) == 0)
82 49 if (((r & PTHREAD_RWLOCK_WRLOCKED) != 0)
83 50 && (rwlock->__data.__flags
84 51 == PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))
86 53 rnew = r + (1 << PTHREAD_RWLOCK_READER_SHIFT);
89 89 while (!atomic_compare_exchange_weak_acquire (&rwlock->__data.__readers,
94 Back in the write unlock:
96 557 if ((r >> PTHREAD_RWLOCK_READER_SHIFT) != 0)
99 563 if ((atomic_exchange_relaxed (&rwlock->__data.__wrphase_futex, 0)
100 564 & PTHREAD_RWLOCK_FUTEX_USED) != 0)
101 565 futex_wake (&rwlock->__data.__wrphase_futex, INT_MAX, private);
104 We note that PTHREAD_RWLOCK_FUTEX_USED is non-zero
105 and don't wake anyone. This is OK because we handed
106 over to the trylock. It will be the trylock's responsibility
109 Back in the read lock:
111 The read lock fails to install PTHRAD_REWLOCK_WRPHASE as 0 because
112 the __readers value was adjusted by the trylock, and so it falls through
113 to waiting on the lock for explicit handover from either a new writer
116 448 int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex,
117 449 1 | PTHREAD_RWLOCK_FUTEX_USED,
118 450 abstime, private);
120 We use PTHREAD_RWLOCK_FUTEX_USED to indicate the futex
123 At this point we have readers waiting on the read lock
124 to unlock. The wrlock is done. The trylock is finishing
125 the installation of the read phase.
127 92 if ((r & PTHREAD_RWLOCK_WRPHASE) != 0)
130 105 atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 0);
133 The trylock does note that we were the one that
134 installed the read phase, but the comments are not
135 correct, the execution ordering above shows that
136 readers might indeed be waiting, and they are.
138 The atomic_store_relaxed throws away PTHREAD_RWLOCK_FUTEX_USED,
139 and the waiting reader is never worken because as noted
140 above it is conditional on the futex being used.
142 The solution is for the trylock thread to inspect
143 PTHREAD_RWLOCK_FUTEX_USED and wake the waiting readers.
145 --- Analysis of pthread_rwlock_trywrlock() stall ---
147 A write lock begins to execute, takes the write lock,
148 and then releases the lock...
150 In pthread_rwlock_wrunlock():
152 547 unsigned int r = atomic_load_relaxed (&rwlock->__data.__readers);
154 549 while (!atomic_compare_exchange_weak_release
155 550 (&rwlock->__data.__readers, &r,
156 551 ((r ^ PTHREAD_RWLOCK_WRLOCKED)
157 552 ^ ((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0 ? 0
158 553 : PTHREAD_RWLOCK_WRPHASE))))
163 ... leaving it in the write phase with zero readers
164 (the case where we leave the write phase in place
165 during a write unlock).
167 A write trylock begins to execute.
169 In __pthread_rwlock_trywrlock:
171 40 while (((r & PTHREAD_RWLOCK_WRLOCKED) == 0)
172 41 && (((r >> PTHREAD_RWLOCK_READER_SHIFT) == 0)
173 42 || (prefer_writer && ((r & PTHREAD_RWLOCK_WRPHASE) != 0))))
176 The lock is not locked.
178 There are no readers.
180 45 if (atomic_compare_exchange_weak_acquire (
181 46 &rwlock->__data.__readers, &r,
182 47 r | PTHREAD_RWLOCK_WRPHASE | PTHREAD_RWLOCK_WRLOCKED))
184 We atomically install the write phase and we take the
185 exclusive write lock.
188 49 atomic_store_relaxed (&rwlock->__data.__writers_futex, 1);
192 A reader lock begins to execute.
194 In pthread_rwlock_rdlock:
198 439 while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex))
199 440 | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED))
201 442 int private = __pthread_rwlock_get_private (rwlock);
202 443 if (((wpf & PTHREAD_RWLOCK_FUTEX_USED) == 0)
203 444 && (!atomic_compare_exchange_weak_relaxed
204 445 (&rwlock->__data.__wrphase_futex,
205 446 &wpf, wpf | PTHREAD_RWLOCK_FUTEX_USED)))
207 448 int err = futex_abstimed_wait (&rwlock->__data.__wrphase_futex,
208 449 1 | PTHREAD_RWLOCK_FUTEX_USED,
209 450 abstime, private);
211 We are in a write phase, so the while() on line 439 is true.
213 The value of wpf does not have PTHREAD_RWLOCK_FUTEX_USED set
214 since this is the first reader to lock.
216 The atomic operation sets wpf with PTHREAD_RELOCK_FUTEX_USED
217 on the expectation that this reader will be woken during
220 Back in pthread_rwlock_trywrlock:
222 50 atomic_store_relaxed (&rwlock->__data.__wrphase_futex, 1);
223 51 atomic_store_relaxed (&rwlock->__data.__cur_writer,
224 52 THREAD_GETMEM (THREAD_SELF, tid));
230 We write 1 to __wrphase_futex discarding PTHREAD_RWLOCK_FUTEX_USED,
231 and so in the unlock we will not awaken the waiting reader.
233 The solution to this is to realize that if we did not start the write
234 phase we need not write 1 or any other value to __wrphase_futex.
235 This ensures that any readers (which saw __wrphase_futex != 0) can
236 set PTHREAD_RWLOCK_FUTEX_USED and this can be used at unlock to
239 If we installed the write phase then all other readers are looping
242 In __pthread_rwlock_rdlock_full:
246 439 while (((wpf = atomic_load_relaxed (&rwlock->__data.__wrphase_futex))
247 440 | PTHREAD_RWLOCK_FUTEX_USED) == (1 | PTHREAD_RWLOCK_FUTEX_USED))
252 waiting for the write phase to be installed or removed before they
253 can begin waiting on __wrphase_futex (part of the algorithm), or
254 taking a concurrent read lock, and thus we can safely write 1 to
257 If we did not install the write phase then the readers may already
258 be waiting on the futex, the original writer wrote 1 to __wrphase_futex
259 as part of starting the write phase, and we cannot also write 1
260 without losing the PTHREAD_RWLOCK_FUTEX_USED bit.
264 Summary for the pthread_rwlock_tryrdlock() stall:
266 The stall is caused by pthread_rwlock_tryrdlock failing to check
267 that PTHREAD_RWLOCK_FUTEX_USED is set in the __wrphase_futex futex
268 and then waking the futex.
270 The fix for bug 23844 ensures that waiters on __wrphase_futex are
271 correctly woken. Before the fix the test stalls as readers can
272 wait forever on __wrphase_futex. */
278 #include <support/xthread.h>
281 /* We need only one lock to reproduce the issue. We will need multiple
282 threads to get the exact case where we have a read, try, and unlock
283 all interleaving to produce the case where the readers are waiting
284 and the try fails to wake them. */
285 pthread_rwlock_t onelock
;
287 /* The number of threads is arbitrary but empirically chosen to have
288 enough threads that we see the condition where waiting readers are
289 not woken by a successful tryrdlock. */
300 /* Arbitrarily choose if we are the writer or reader. Choose a
301 high enough ratio of readers to writers to make it likely
302 that readers block (and eventually are susceptable to
305 If we are a writer, take the write lock, and then unlock.
306 If we are a reader, try the lock, then lock, then unlock. */
308 xpthread_rwlock_wrlock (&onelock
);
311 if ((ret
= pthread_rwlock_tryrdlock (&onelock
)) != 0)
314 xpthread_rwlock_rdlock (&onelock
);
319 /* Thread does some work and then unlocks. */
320 xpthread_rwlock_unlock (&onelock
);
330 pthread_t tids
[NTHREADS
];
331 xpthread_rwlock_init (&onelock
, NULL
);
332 for (i
= 0; i
< NTHREADS
; i
++)
333 tids
[i
] = xpthread_create (NULL
, run_loop
, NULL
);
334 /* Run for some amount of time. Empirically speaking exercising
335 the stall via pthread_rwlock_tryrdlock is much harder, and on
336 a 3.5GHz 4 core x86_64 VM system it takes somewhere around
337 20-200s to stall, approaching 100% stall past 200s. We can't
338 wait that long for a regression test so we just test for 20s,
339 and expect the stall to happen with a 5-10% chance (enough for
340 developers to see). */
343 printf ("INFO: Exiting...\n");
345 /* If any readers stalled then we will timeout waiting for them. */
346 for (i
= 0; i
< NTHREADS
; i
++)
347 xpthread_join (tids
[i
]);
348 printf ("INFO: Done.\n");
349 xpthread_rwlock_destroy (&onelock
);
350 printf ("PASS: No pthread_rwlock_tryrdlock stalls detected.\n");
355 #include <support/test-driver.c>