mirror of
https://git.savannah.gnu.org/git/gnulib.git
synced 2025-08-16 01:22:18 +03:00
The rationale is: 1) Read-preferring read-write locks are prone to writer starvation if the number of reader threads multiplied by the percentage of time they have the lock held is too high. 2) Write- preferring read-write locks are the only reliable way to avoid this. 3) There have been reports of 'test-lock' hanging on glibc systems http://lists.gnu.org/archive/html/bug-gnulib/2017-01/msg00009.html, and glibc indeed implements read-preferring rwlocks by default, see http://man7.org/linux/man-pages/man3/pthread_rwlockattr_setkind_np.3.html and https://sourceware.org/bugzilla/show_bug.cgi?id=13701 . * m4/pthread_rwlock_rdlock.m4: New file. * m4/lock.m4 (gl_LOCK): Invoke gl_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER. * lib/glthread/lock.h [USE_POSIX_THREADS]: Test HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER. Use a different implementation of rwlock initialization on glibc systems without HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER. Use a different implementation of rwlocks altogether on non-glibc systems without HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER. [USE_PTH_THREADS]: Use a different implementation of rwlocks altogether. * lib/glthread/lock.c [USE_POSIX_THREADS] (glthread_rwlock_init_for_glibc): New function. [USE_POSIX_THREADS] (glthread_rwlock_rdlock_multithreaded): Update comment. [USE_PTH_THREADS]: New implementation of rwlocks. [USE_WINDOWS_THREADS] (glthread_rwlock_rdlock_func): Prefer writers over readers. * modules/lock (Files): Add m4/pthread_rwlock_rdlock.m4. (Depends-on): Add 'extensions'. * tests/test-rwlock1.c: New file. * lock-tests (Files): Add it. (Depends-on): Add usleep. (Makefile.am): Add test-rwlock1 to the tests.
158 lines
5.3 KiB
C
158 lines
5.3 KiB
C
/* Test of glthread_rwlock_rdlock function.
|
|
Copyright (C) 2017 Free Software Foundation, Inc.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
/* Written by Bruno Haible <bruno@clisp.org>, 2005.
|
|
Inspired by
|
|
https://github.com/linux-test-project/ltp/blob/master/testcases/open_posix_testsuite/conformance/interfaces/pthread_rwlock_rdlock/2-2.c
|
|
by Intel Corporation. */
|
|
|
|
#include <config.h>
|
|
|
|
#include "glthread/lock.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "glthread/thread.h"
|
|
#include "glthread/yield.h"
|
|
|
|
/* Verify that in a situation where
|
|
- an rwlock is taken by a reader and has a writer waiting,
|
|
- an additional reader requests the lock,
|
|
- the waiting writer and the requesting reader threads have the same
|
|
priority,
|
|
the requesting reader thread gets blocked, so that at some point the
|
|
waiting writer can acquire the lock.
|
|
Without such a guarantee, when there a N readers and each of the readers
|
|
spends more than 1/Nth of the time with the lock held, there is a high
|
|
probability that the waiting writer will not get the lock in a given finite
|
|
time, a phenomenon called "writer starvation".
|
|
Without such a guarantee, applications have a hard time avoiding writer
|
|
starvation.
|
|
|
|
POSIX:2008 makes this requirement only for implementations that support TPS
|
|
(Thread Priority Scheduling) and only for the scheduling policies SCHED_FIFO
|
|
and SCHED_RR, see
|
|
http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_rdlock.html
|
|
but test verifies the guarantee regardless of TPS and regardless of
|
|
scheduling policy. */
|
|
|
|
#define SUCCEED() exit (0)
|
|
#define FAILURE() exit (1)
|
|
#define UNEXPECTED(n) (fprintf (stderr, "Unexpected outcome %d\n", n), abort ())
|
|
|
|
/* The main thread creates the waiting writer and the requesting reader threads
|
|
in the default way; this guarantees that they have the same priority.
|
|
We can reuse the main thread as first reader thread. */
|
|
|
|
static gl_rwlock_t lock;
|
|
static gl_thread_t reader1;
|
|
static gl_thread_t writer;
|
|
static gl_thread_t reader2;
|
|
static gl_thread_t timer;
|
|
/* Used to pass control from writer to reader2 and from reader2 to timer,
|
|
as in a relay race.
|
|
Passing control from one running thread to another running thread
|
|
is most likely faster than to create the second thread. */
|
|
static gl_lock_t baton;
|
|
|
|
static void *
|
|
timer_func (void *ignored)
|
|
{
|
|
/* Step 13 (can be before or after step 12):
|
|
The timer thread takes the baton, then waits a moment to make sure
|
|
it can tell whether the second reader thread is blocked at step 12. */
|
|
if (glthread_lock_lock (&baton))
|
|
UNEXPECTED (13);
|
|
usleep (100000);
|
|
/* By the time we get here, it's clear that the second reader thread is
|
|
blocked at step 12. This is the desired behaviour. */
|
|
SUCCEED ();
|
|
}
|
|
|
|
static void *
|
|
reader2_func (void *ignored)
|
|
{
|
|
int err;
|
|
|
|
/* Step 8 (can be before or after step 7):
|
|
The second reader thread takes the baton, then waits a moment to make sure
|
|
the writer thread has reached step 7. */
|
|
if (glthread_lock_lock (&baton))
|
|
UNEXPECTED (8);
|
|
usleep (100000);
|
|
/* Step 9 omitted. */
|
|
/* Step 10: Launch a timer, to test whether the next call blocks. */
|
|
if (glthread_create (&timer, timer_func, NULL))
|
|
UNEXPECTED (10);
|
|
/* Step 11: Release the baton. */
|
|
if (glthread_lock_unlock (&baton))
|
|
UNEXPECTED (11);
|
|
/* Step 12: The second reader thread requests the lock. */
|
|
err = glthread_rwlock_rdlock (&lock);
|
|
if (err == 0)
|
|
FAILURE ();
|
|
else
|
|
UNEXPECTED (12);
|
|
}
|
|
|
|
static void *
|
|
writer_func (void *ignored)
|
|
{
|
|
/* Step 4: Take the baton, so that the second reader thread does not go ahead
|
|
too early. */
|
|
if (glthread_lock_lock (&baton))
|
|
UNEXPECTED (4);
|
|
/* Step 5: Create the second reader thread. */
|
|
if (glthread_create (&reader2, reader2_func, NULL))
|
|
UNEXPECTED (5);
|
|
/* Step 6: Release the baton. */
|
|
if (glthread_lock_unlock (&baton))
|
|
UNEXPECTED (6);
|
|
/* Step 7: The writer thread requests the lock. */
|
|
if (glthread_rwlock_wrlock (&lock))
|
|
UNEXPECTED (7);
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
main ()
|
|
{
|
|
reader1 = gl_thread_self ();
|
|
|
|
/* Step 1: The main thread initializes the lock and the baton. */
|
|
if (glthread_rwlock_init (&lock))
|
|
UNEXPECTED (1);
|
|
if (glthread_lock_init (&baton))
|
|
UNEXPECTED (1);
|
|
/* Step 2: The main thread acquires the lock as a reader. */
|
|
if (glthread_rwlock_rdlock (&lock))
|
|
UNEXPECTED (2);
|
|
/* Step 3: Create the writer thread. */
|
|
if (glthread_create (&writer, writer_func, NULL))
|
|
UNEXPECTED (3);
|
|
/* Job done. Go to sleep. */
|
|
for (;;)
|
|
{
|
|
/* In cooperative threads implementations (Pth), give other threads
|
|
a chance to run. */
|
|
gl_thread_yield ();
|
|
sleep (1);
|
|
}
|
|
}
|