aboutsummaryrefslogtreecommitdiff
path: root/sysdeps
diff options
context:
space:
mode:
authorSamuel Thibault <samuel.thibault@ens-lyon.org>2020-06-23 22:43:32 +0000
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2020-06-24 01:19:49 +0200
commiteca16db02d660242e709d4b8a11a2c5b94cca540 (patch)
treefda5e40a34d5d16179bff5718f0e80a89825e639 /sysdeps
parent3513d5af3d111f322cf2b64f2c7d415ea923bf25 (diff)
downloadglibc-eca16db02d660242e709d4b8a11a2c5b94cca540.zip
glibc-eca16db02d660242e709d4b8a11a2c5b94cca540.tar.gz
glibc-eca16db02d660242e709d4b8a11a2c5b94cca540.tar.bz2
htl: Make sem_*wait cancellations points
By aligning its implementation on pthread_cond_wait. * sysdeps/htl/sem-timedwait.c (cancel_ctx): New structure. (cancel_hook): New function. (__sem_timedwait_internal): Check for cancellation and register cancellation hook that wakes the thread up, and check again for cancellation on exit. * nptl/tst-cancel13.c, nptl/tst-cancelx13.c: Move to... * sysdeps/pthread/: ... here. * nptl/Makefile: Move corresponding references and rules to... * sysdeps/pthread/Makefile: ... here.
Diffstat (limited to 'sysdeps')
-rw-r--r--sysdeps/htl/sem-timedwait.c92
-rw-r--r--sysdeps/pthread/Makefile9
-rw-r--r--sysdeps/pthread/tst-cancel13.c127
-rw-r--r--sysdeps/pthread/tst-cancelx13.c1
4 files changed, 217 insertions, 12 deletions
diff --git a/sysdeps/htl/sem-timedwait.c b/sysdeps/htl/sem-timedwait.c
index 0b1b6d0..691fb7e 100644
--- a/sysdeps/htl/sem-timedwait.c
+++ b/sysdeps/htl/sem-timedwait.c
@@ -23,36 +23,98 @@
#include <pt-internal.h>
+struct cancel_ctx
+{
+ struct __pthread *wakeup;
+ sem_t *sem;
+};
+
+static void
+cancel_hook (void *arg)
+{
+ struct cancel_ctx *ctx = arg;
+ struct __pthread *wakeup = ctx->wakeup;
+ sem_t *sem = ctx->sem;
+ int unblock;
+
+ __pthread_spin_wait (&sem->__lock);
+ /* The thread only needs to be awaken if it's blocking or about to block.
+ If it was already unblocked, it's not queued any more. */
+ unblock = wakeup->prevp != NULL;
+ if (unblock)
+ __pthread_dequeue (wakeup);
+ __pthread_spin_unlock (&sem->__lock);
+
+ if (unblock)
+ __pthread_wakeup (wakeup);
+}
+
int
__sem_timedwait_internal (sem_t *restrict sem,
clockid_t clock_id,
const struct timespec *restrict timeout)
{
error_t err;
- int drain;
- struct __pthread *self;
+ int cancelled, oldtype, drain;
+ int ret = 0;
+
+ struct __pthread *self = _pthread_self ();
+ struct cancel_ctx ctx;
+ ctx.wakeup = self;
+ ctx.sem = sem;
+
+ /* Test for a pending cancellation request, switch to deferred mode for
+ safer resource handling, and prepare the hook to call in case we're
+ cancelled while blocking. Once CANCEL_LOCK is released, the cancellation
+ hook can be called by another thread at any time. Whatever happens,
+ this function must exit with MUTEX locked.
+
+ This function contains inline implementations of pthread_testcancel and
+ pthread_setcanceltype to reduce locking overhead. */
+ __pthread_mutex_lock (&self->cancel_lock);
+ cancelled = (self->cancel_state == PTHREAD_CANCEL_ENABLE)
+ && self->cancel_pending;
+
+ if (cancelled)
+ {
+ __pthread_mutex_unlock (&self->cancel_lock);
+ __pthread_exit (PTHREAD_CANCELED);
+ }
+
+ self->cancel_hook = cancel_hook;
+ self->cancel_hook_arg = &ctx;
+ oldtype = self->cancel_type;
+ if (oldtype != PTHREAD_CANCEL_DEFERRED)
+ self->cancel_type = PTHREAD_CANCEL_DEFERRED;
+
+ /* Add ourselves to the list of waiters. This is done while setting
+ the cancellation hook to simplify the cancellation procedure, i.e.
+ if the thread is queued, it can be cancelled, otherwise it is
+ already unblocked, progressing on the return path. */
__pthread_spin_wait (&sem->__lock);
if (sem->__value > 0)
/* Successful down. */
{
sem->__value--;
__pthread_spin_unlock (&sem->__lock);
- return 0;
+ goto out_locked;
}
if (timeout != NULL && ! valid_nanoseconds (timeout->tv_nsec))
{
errno = EINVAL;
- return -1;
+ ret = -1;
+ __pthread_spin_unlock (&sem->__lock);
+ goto out_locked;
}
/* Add ourselves to the queue. */
- self = _pthread_self ();
-
__pthread_enqueue (&sem->__queue, self);
__pthread_spin_unlock (&sem->__lock);
+ __pthread_mutex_unlock (&self->cancel_lock);
+
/* Block the thread. */
if (timeout != NULL)
err = __pthread_timedblock_intr (self, timeout, clock_id);
@@ -82,10 +144,24 @@ __sem_timedwait_internal (sem_t *restrict sem,
{
assert (err == ETIMEDOUT || err == EINTR);
errno = err;
- return -1;
+ ret = -1;
}
- return 0;
+ /* We're almost done. Remove the unblock hook, restore the previous
+ cancellation type, and check for a pending cancellation request. */
+ __pthread_mutex_lock (&self->cancel_lock);
+out_locked:
+ self->cancel_hook = NULL;
+ self->cancel_hook_arg = NULL;
+ self->cancel_type = oldtype;
+ cancelled = (self->cancel_state == PTHREAD_CANCEL_ENABLE)
+ && self->cancel_pending;
+ __pthread_mutex_unlock (&self->cancel_lock);
+
+ if (cancelled)
+ __pthread_exit (PTHREAD_CANCELED);
+
+ return ret;
}
int
diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
index 01602e6..ed31ae8 100644
--- a/sysdeps/pthread/Makefile
+++ b/sysdeps/pthread/Makefile
@@ -55,8 +55,8 @@ tests += tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \
tst-cancel-self-canceltype tst-cancel-self-testcancel \
tst-cancel1 tst-cancel2 tst-cancel3 \
tst-cancel6 tst-cancel8 tst-cancel9 tst-cancel10 tst-cancel11 \
- tst-cancel12 tst-cancel14 tst-cancel15 tst-cancel18 tst-cancel19 \
- tst-cancel21 \
+ tst-cancel12 tst-cancel13 tst-cancel14 tst-cancel15 tst-cancel18 \
+ tst-cancel19 tst-cancel21 \
tst-cancel22 tst-cancel23 tst-cancel26 tst-cancel27 tst-cancel28 \
tst-cleanup0 tst-cleanup1 tst-cleanup2 tst-cleanup3 \
tst-clock1 \
@@ -117,8 +117,8 @@ CFLAGS-tst-cleanup2.c += -fno-builtin
CFLAGS-tst-cleanupx2.c += -fno-builtin
tests += tst-cancelx2 tst-cancelx3 tst-cancelx6 tst-cancelx8 tst-cancelx9 \
- tst-cancelx10 tst-cancelx11 tst-cancelx12 tst-cancelx14 tst-cancelx15 \
- tst-cancelx18 tst-cancelx21 \
+ tst-cancelx10 tst-cancelx11 tst-cancelx12 tst-cancelx13 tst-cancelx14 \
+ tst-cancelx15 tst-cancelx18 tst-cancelx21 \
tst-cleanupx0 tst-cleanupx1 tst-cleanupx2 tst-cleanupx3
ifeq ($(build-shared),yes)
@@ -160,6 +160,7 @@ CFLAGS-tst-cancelx9.c += -fexceptions
CFLAGS-tst-cancelx10.c += -fexceptions
CFLAGS-tst-cancelx11.c += -fexceptions
CFLAGS-tst-cancelx12.c += -fexceptions
+CFLAGS-tst-cancelx13.c += -fexceptions
CFLAGS-tst-cancelx14.c += -fexceptions
CFLAGS-tst-cancelx15.c += -fexceptions
CFLAGS-tst-cancelx18.c += -fexceptions
diff --git a/sysdeps/pthread/tst-cancel13.c b/sysdeps/pthread/tst-cancel13.c
new file mode 100644
index 0000000..0b0086c
--- /dev/null
+++ b/sysdeps/pthread/tst-cancel13.c
@@ -0,0 +1,127 @@
+/* Test sem_wait cancellation for contended case.
+ Copyright (C) 2003-2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Ulrich Drepper <drepper@redhat.com>, 2003.
+
+ 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 <semaphore.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+static pthread_barrier_t bar;
+static sem_t sem;
+
+
+static void
+cleanup (void *arg)
+{
+ static int ncall;
+
+ if (++ncall != 1)
+ {
+ puts ("second call to cleanup");
+ exit (1);
+ }
+}
+
+
+static void *
+tf (void *arg)
+{
+ pthread_cleanup_push (cleanup, NULL);
+
+ int e = pthread_barrier_wait (&bar);
+ if (e != 0 && e != PTHREAD_BARRIER_SERIAL_THREAD)
+ {
+ puts ("error: tf: 1st barrier_wait failed");
+ exit (1);
+ }
+
+ /* This call should block and be cancelable. */
+ sem_wait (&sem);
+
+ pthread_cleanup_pop (0);
+
+ return NULL;
+}
+
+
+static int
+do_test (void)
+{
+ pthread_t th;
+
+ if (pthread_barrier_init (&bar, NULL, 2) != 0)
+ {
+ puts ("error: barrier_init failed");
+ exit (1);
+ }
+
+ /* A value equal to 0 will check for contended pthread cancellation,
+ where the sem_wait operation will block. */
+ if (sem_init (&sem, 0, 0) != 0)
+ {
+ puts ("error: sem_init failed");
+ exit (1);
+ }
+
+ if (pthread_create (&th, NULL, tf, NULL) != 0)
+ {
+ puts ("error: create failed");
+ exit (1);
+ }
+
+ int e = pthread_barrier_wait (&bar);
+ if (e != 0 && e != PTHREAD_BARRIER_SERIAL_THREAD)
+ {
+ puts ("error: 1st barrier_wait failed");
+ exit (1);
+ }
+
+ /* Give the child a chance to go to sleep in sem_wait. */
+ sleep (1);
+
+ /* Check whether cancellation is honored when waiting in sem_wait. */
+ if (pthread_cancel (th) != 0)
+ {
+ puts ("error: 1st cancel failed");
+ exit (1);
+ }
+
+ void *r;
+ if (pthread_join (th, &r) != 0)
+ {
+ puts ("error: join failed");
+ exit (1);
+ }
+
+ if (r != PTHREAD_CANCELED)
+ {
+ puts ("thread not canceled");
+ exit (1);
+ }
+
+ return 0;
+}
+
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/sysdeps/pthread/tst-cancelx13.c b/sysdeps/pthread/tst-cancelx13.c
new file mode 100644
index 0000000..37c4c39
--- /dev/null
+++ b/sysdeps/pthread/tst-cancelx13.c
@@ -0,0 +1 @@
+#include "tst-cancel13.c"