FreeBSD Bugzilla – Attachment 153485 Details for
Bug 198014
Signals can lead to an inconsistency in PI mutex ownership
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
Deterministic test case
umtx_pi_signal.c (text/x-c), 8.41 KB, created by
eric
on 2015-02-24 22:49:47 UTC
(
hide
)
Description:
Deterministic test case
Filename:
MIME Type:
Creator:
eric
Created:
2015-02-24 22:49:47 UTC
Size:
8.41 KB
patch
obsolete
>/*- > * Copyright (c) 2015 Dell Inc. > * All rights reserved. > * > * Author: Eric van Gyzen <eric_van_gyzen dell com> > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions > * are met: > * 1. Redistributions of source code must retain the above copyright > * notice, this list of conditions and the following disclaimer. > * 2. Redistributions in binary form must reproduce the above copyright > * notice, this list of conditions and the following disclaimer in the > * documentation and/or other materials provided with the distribution. > * > * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND > * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE > * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE > * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE > * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL > * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS > * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) > * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT > * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY > * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF > * SUCH DAMAGE. > */ > >/* > * This is a deterministic test case for > * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198014 > */ > >#include <sys/cdefs.h> >#include <sys/param.h> >#include <sys/cpuset.h> >#include <sys/sysctl.h> > >#include <machine/cpufunc.h> >#include <machine/cpu.h> > >#include <err.h> >#include <pthread.h> >#include <pthread_np.h> >#include <signal.h> >#include <stdio.h> >#include <stdlib.h> >#include <string.h> >#include <unistd.h> > >pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER; > >struct contender_arg { > const char *name; > int locked; >}; > >pthread_t contender1, contender2; >struct contender_arg carg1 = {"contender1", 0}; >struct contender_arg carg2 = {"contender2", 0}; > >volatile sig_atomic_t sigwinch_received = 0; > >static void >mutex_error(const char *tdname, const char *op, int error) >{ > fprintf(stderr, "%s: %s: %s\n", tdname, op, strerror(error)); > //for (;;) > //sleep(1); >} > >static void * >contender_func(void *arg) >{ > struct contender_arg *me = (struct contender_arg *)arg; > int error; > > for (;;) { > error = pthread_mutex_lock(&the_mutex); > if (error) mutex_error(me->name, "lock", error); > me->locked = 1; > usleep(10000); > error = pthread_mutex_unlock(&the_mutex); > if (error) mutex_error(me->name, "unlock", error); > me->locked = 0; > usleep(10000); > } > > return (NULL); >} > > >void >handle_sigwinch(int i __unused) >{ > sigwinch_received = 1; > // Do not return, since that would restart the > // umtx lock syscall and put this thread back on the umtxq. > while (sigwinch_received) > usleep(2000); >} > >int >main(int argc, char **argv) >{ > int error, hw_ncpu; > size_t hw_ncpu_size; > > hw_ncpu_size = sizeof(hw_ncpu); > error = sysctlbyname("hw.ncpu", &hw_ncpu, &hw_ncpu_size, NULL, 0); > if (error) > err(1, "sysctlbyname(hw.ncpu)"); > if (hw_ncpu < 2) > errx(1, "This program needs at least 2 CPUs."); > > // This sets SA_RESTART. > signal(SIGWINCH, handle_sigwinch); > > // Use SCHED_RR and different priorities just to make this test > // more deterministic. > const int SCHED_POLICY = SCHED_RR; > const int MAX_PRIO = sched_get_priority_max(SCHED_POLICY); > const int MIN_PRIO = sched_get_priority_min(SCHED_POLICY); > const int PRIO_INCR = (MAX_PRIO > MIN_PRIO) - (MIN_PRIO > MAX_PRIO); > > // The initial thread will be the owner. > // Raise its priority above the minimum. > struct sched_param schedp = { > .sched_priority = MIN_PRIO + PRIO_INCR > }; > pthread_set_name_np(pthread_self(), "owner"); > error = pthread_setschedparam(pthread_self(), SCHED_POLICY, &schedp); > if (error) errc(1, error, "pthread_setschedparam"); > > // Bind the owner to CPU 0. > cpuset_t cpuset; > CPU_ZERO(&cpuset); > CPU_SET(0, &cpuset); > error = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); > if (error) errc(1, error, "pthread_setaffinity_np"); > > // Initialize the mutex. > pthread_mutexattr_t mattr; > > error = pthread_mutexattr_init(&mattr); > if (error) errc(1, error, "pthread_mutexattr_init"); > > error = pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT); > if (error) errc(1, error, "pthread_mutexattr_setprotocol"); > > error = pthread_mutex_init(&the_mutex, &mattr); > if (error) errc(1, error, "pthread_mutex_init"); > > error = pthread_mutexattr_destroy(&mattr); > if (error) errc(1, error, "pthread_mutexattr_destroy"); > > // Lock the mutex. > error = pthread_mutex_lock(&the_mutex); > if (error) errc(1, error, "pthread_mutex_lock(%d)", __LINE__); > > pthread_attr_t attr; > > error = pthread_attr_init(&attr); > if (error) errc(1, error, "pthread_attr_init"); > > error = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); > if (error) errc(1, error, "pthread_attr_setinheritsched"); > > error = pthread_attr_setschedpolicy(&attr, SCHED_POLICY); > if (error) errc(1, error, "pthread_attr_setschedpolicy"); > > // Give contender1 minimum priority (worse than the owner). > schedp.sched_priority = MIN_PRIO; > error = pthread_attr_setschedparam(&attr, &schedp); > if (error) errc(1, error, "pthread_attr_setschedparam"); > > // Bind contender1 to CPU 0, the same as the owner. > error = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset); > if (error) errc(1, error, "pthread_attr_setaffinity_np"); > > error = pthread_create(&contender1, &attr, contender_func, &carg1); > if (error) errc(1, error, "pthread_create(contender1)"); > pthread_set_name_np(contender1, carg1.name); > > // Sleep to allow contender1 to enter the umtx lock syscall > // and sleep on the mutex. > usleep(100000); > > // Bind contender2 to CPU 1, different from the others. > // The priority is irrelevant. > CPU_CLR(0, &cpuset); > CPU_SET(1, &cpuset); > error = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset); > if (error) errc(1, error, "pthread_attr_setaffinity_np"); > > error = pthread_create(&contender2, &attr, contender_func, &carg2); > if (error) errc(1, error, "pthread_create(contender2)"); > pthread_set_name_np(contender2, carg2.name); > > error = pthread_attr_destroy(&attr); > if (error) errc(1, error, "pthread_attr_destroy"); > > // Sleep to allow contender2 to enter the umtx lock syscall > // and sleep on the mutex. It will be the second in the umtxq. > usleep(100000); > > // Unlock the mutex. This will remove contender1 from the umtxq. > error = pthread_mutex_unlock(&the_mutex); > if (error) errc(1, error, "pthread_mutex_unlock"); > > // Relock the mutex. Since contender1 is bound to the same CPU > // as this thread and has a lower RT RR priority, it won't have a > // chance to run and add itself back to the umtxq. > error = pthread_mutex_lock(&the_mutex); > if (error) errc(1, error, "pthread_mutex_lock(%d)", __LINE__); > > // Signal contender2. > pthread_kill(contender2, SIGWINCH); > > // contender2 now awakes from umtxq_sleep_pi(), removes himself > // from the umtxq, returns ERESTART, and calls handle_sigwinch. > // Spin to wait for all of this. Do not sleep, since that would > // allow contender1 to run. > while (!sigwinch_received) > cpu_spinwait(); > > // Unlock the mutex. The umtxq is empty, so do_unlock_pi::uq_first > // will be NULL, so do_unlock_pi will not release ownership of the umtx_pi. > error = pthread_mutex_unlock(&the_mutex); > if (error) errc(1, error, "pthread_mutex_unlock(%d)", __LINE__); > > // The mutex is currently UMUTEX_UNOWNED, with contender1 trying to run. > // Let contender2 return from handle_sigwinch and lock the mutex. > // Do not sleep, since that would let contender1 run, return from > // do_lock_pi(), and free the umtx_pi, therefore recovering from its > // incorrect pi_owner. > sigwinch_received = 0; > while (!carg2.locked) > cpu_spinwait(); > > // Contested operations on the mutex will now fail. > // Here's one way... > > // Let contender1 run. > usleep(200000); > > // Join in the contention. > struct contender_arg iarg = {"initial", 0}; > contender_func(&iarg); > > // For debugging... > for (;;) { > sleep(1); > } > > return (0); >}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Actions:
View
Attachments on
bug 198014
:
153475
|
153476
|
153477
| 153485 |
153486
|
153487
|
153524