From 729eedd152477ecb442cdd5abcc9a48181f853a5 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Mon, 19 Apr 2021 17:12:11 -0500 Subject: [PATCH] userspace-rcu-0.12.1-5 Replace arch-dependent /usr/include/urcu/config.h with arch-independent stub when needed. (bz# 1951223) Added CI gating tests Resolves: bz #1951223 --- tests/regression_tests/Makefile | 12 + tests/regression_tests/common/api.h | 308 +++++++++ tests/regression_tests/common/compat-rand.h | 3 + tests/regression_tests/common/cpuset.h | 43 ++ tests/regression_tests/common/debug-yield.h | 103 +++ tests/regression_tests/common/thread-id.h | 81 +++ tests/regression_tests/regression/Makefile | 31 + .../regression_tests/regression/rcutorture.h | 612 ++++++++++++++++++ .../regression/regression_tests | 61 ++ tests/regression_tests/regression/run.sh | 34 + .../regression/test_urcu_fork.c | 214 ++++++ .../regression_tests/regression/urcutorture.c | 29 + tests/regression_tests/utils/Makefile | 10 + tests/regression_tests/utils/tap.c | 473 ++++++++++++++ tests/regression_tests/utils/tap.h | 95 +++ tests/tests.yml | 20 + userspace-rcu.spec | 11 +- 17 files changed, 2139 insertions(+), 1 deletion(-) create mode 100644 tests/regression_tests/Makefile create mode 100644 tests/regression_tests/common/api.h create mode 100644 tests/regression_tests/common/compat-rand.h create mode 100644 tests/regression_tests/common/cpuset.h create mode 100644 tests/regression_tests/common/debug-yield.h create mode 100644 tests/regression_tests/common/thread-id.h create mode 100644 tests/regression_tests/regression/Makefile create mode 100644 tests/regression_tests/regression/rcutorture.h create mode 100644 tests/regression_tests/regression/regression_tests create mode 100755 tests/regression_tests/regression/run.sh create mode 100644 tests/regression_tests/regression/test_urcu_fork.c create mode 100644 tests/regression_tests/regression/urcutorture.c create mode 100644 tests/regression_tests/utils/Makefile create mode 100644 tests/regression_tests/utils/tap.c create mode 100644 tests/regression_tests/utils/tap.h create mode 100644 tests/tests.yml diff --git a/tests/regression_tests/Makefile b/tests/regression_tests/Makefile new file mode 100644 index 0000000..b5cad74 --- /dev/null +++ b/tests/regression_tests/Makefile @@ -0,0 +1,12 @@ +BUILDDIRS = utils regression + +all: recurse + $(MAKE) -C regression regtest + +clean: recurse_clean + +recurse: + @for dir in $(BUILDDIRS); do $(MAKE) -C $$dir || exit $?; done + +recurse_clean: + @for dir in $(BUILDDIRS); do $(MAKE) -C $$dir clean || exit $?; done diff --git a/tests/regression_tests/common/api.h b/tests/regression_tests/common/api.h new file mode 100644 index 0000000..2b72ec5 --- /dev/null +++ b/tests/regression_tests/common/api.h @@ -0,0 +1,308 @@ +#ifndef _INCLUDE_API_H +#define _INCLUDE_API_H + +/* + * common.h: Common Linux kernel-isms. + * + * 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; but version 2 of the License only due + * to code included from the Linux kernel. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (c) 2006 Paul E. McKenney, IBM. + * + * Much code taken from the Linux kernel. For such code, the option + * to redistribute under later versions of GPL might not be available. + */ + +#include +#include +#include "cpuset.h" + +/* + * Machine parameters. + */ + +#define ____cacheline_internodealigned_in_smp \ + __attribute__((__aligned__(CAA_CACHE_LINE_SIZE))) + +/* + * api_pthreads.h: API mapping to pthreads environment. + * + * 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 2 of the License, or + * (at your option) any later version. However, please note that much + * of the code in this file derives from the Linux kernel, and that such + * code may not be available except under GPLv2. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (c) 2006 Paul E. McKenney, IBM. + */ + +#include +#include +#include +#include +#include +#include +#include +/* #include "atomic.h" */ + +/* + * Exclusive locking primitives. + */ + +typedef pthread_mutex_t spinlock_t; + +#define DEFINE_SPINLOCK(lock) spinlock_t lock = PTHREAD_MUTEX_INITIALIZER; +#define __SPIN_LOCK_UNLOCKED(lockp) PTHREAD_MUTEX_INITIALIZER + +static void spin_lock_init(spinlock_t *sp) +{ + if (pthread_mutex_init(sp, NULL) != 0) { + perror("spin_lock_init:pthread_mutex_init"); + exit(-1); + } +} + +static void spin_lock(spinlock_t *sp) +{ + if (pthread_mutex_lock(sp) != 0) { + perror("spin_lock:pthread_mutex_lock"); + exit(-1); + } +} + +static void spin_unlock(spinlock_t *sp) +{ + if (pthread_mutex_unlock(sp) != 0) { + perror("spin_unlock:pthread_mutex_unlock"); + exit(-1); + } +} + +#define spin_lock_irqsave(l, f) do { f = 1; spin_lock(l); } while (0) +#define spin_unlock_irqrestore(l, f) do { f = 0; spin_unlock(l); } while (0) + +/* + * Thread creation/destruction primitives. + */ + +typedef pthread_t thread_id_t; + +#define NR_THREADS 128 + +#define __THREAD_ID_MAP_EMPTY ((thread_id_t) 0) +#define __THREAD_ID_MAP_WAITING ((thread_id_t) 1) +thread_id_t __thread_id_map[NR_THREADS]; +spinlock_t __thread_id_map_mutex; + +#define for_each_thread(t) \ + for (t = 0; t < NR_THREADS; t++) + +#define for_each_running_thread(t) \ + for (t = 0; t < NR_THREADS; t++) \ + if ((__thread_id_map[t] != __THREAD_ID_MAP_EMPTY) && \ + (__thread_id_map[t] != __THREAD_ID_MAP_WAITING)) + +#define for_each_tid(t, tid) \ + for (t = 0; t < NR_THREADS; t++) \ + if ((((tid) = __thread_id_map[t]) != __THREAD_ID_MAP_EMPTY) && \ + ((tid) != __THREAD_ID_MAP_WAITING)) + +pthread_key_t thread_id_key; + +static int __smp_thread_id(void) +{ + int i; + thread_id_t tid = pthread_self(); + + for (i = 0; i < NR_THREADS; i++) { + if (__thread_id_map[i] == tid) { + long v = i + 1; /* must be non-NULL. */ + + if (pthread_setspecific(thread_id_key, (void *)v) != 0) { + perror("pthread_setspecific"); + exit(-1); + } + return i; + } + } + spin_lock(&__thread_id_map_mutex); + for (i = 0; i < NR_THREADS; i++) { + if (__thread_id_map[i] == tid) { + spin_unlock(&__thread_id_map_mutex); + return i; + } + } + spin_unlock(&__thread_id_map_mutex); + fprintf(stderr, "smp_thread_id: Rogue thread, id: %lu(%#lx)\n", + (unsigned long) tid, (unsigned long) tid); + exit(-1); +} + +static int smp_thread_id(void) +{ + void *id; + + id = pthread_getspecific(thread_id_key); + if (id == NULL) + return __smp_thread_id(); + return (long)(id - 1); +} + +static thread_id_t create_thread(void *(*func)(void *), void *arg) +{ + thread_id_t tid; + int i; + + spin_lock(&__thread_id_map_mutex); + for (i = 0; i < NR_THREADS; i++) { + if (__thread_id_map[i] == __THREAD_ID_MAP_EMPTY) + break; + } + if (i >= NR_THREADS) { + spin_unlock(&__thread_id_map_mutex); + fprintf(stderr, "Thread limit of %d exceeded!\n", NR_THREADS); + exit(-1); + } + __thread_id_map[i] = __THREAD_ID_MAP_WAITING; + spin_unlock(&__thread_id_map_mutex); + if (pthread_create(&tid, NULL, func, arg) != 0) { + perror("create_thread:pthread_create"); + exit(-1); + } + __thread_id_map[i] = tid; + return tid; +} + +static void *wait_thread(thread_id_t tid) +{ + int i; + void *vp; + + for (i = 0; i < NR_THREADS; i++) { + if (__thread_id_map[i] == tid) + break; + } + if (i >= NR_THREADS){ + fprintf(stderr, "wait_thread: bad tid = %lu(%#lx)\n", + (unsigned long)tid, (unsigned long)tid); + exit(-1); + } + if (pthread_join(tid, &vp) != 0) { + perror("wait_thread:pthread_join"); + exit(-1); + } + __thread_id_map[i] = __THREAD_ID_MAP_EMPTY; + return vp; +} + +static void wait_all_threads(void) +{ + int i; + thread_id_t tid; + + for (i = 1; i < NR_THREADS; i++) { + tid = __thread_id_map[i]; + if (tid != __THREAD_ID_MAP_EMPTY && + tid != __THREAD_ID_MAP_WAITING) + (void)wait_thread(tid); + } +} + +static void run_on(int cpu) +{ +#if HAVE_SCHED_SETAFFINITY + cpu_set_t mask; + + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); +#if SCHED_SETAFFINITY_ARGS == 2 + sched_setaffinity(0, &mask); +#else + sched_setaffinity(0, sizeof(mask), &mask); +#endif +#endif /* HAVE_SCHED_SETAFFINITY */ +} + +/* + * timekeeping -- very crude -- should use MONOTONIC... + */ + +long long get_microseconds(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) != 0) + abort(); + return ((long long)tv.tv_sec) * 1000000LL + (long long)tv.tv_usec; +} + +/* + * Per-thread variables. + */ + +#define DEFINE_PER_THREAD(type, name) \ + struct { \ + __typeof__(type) v \ + __attribute__((__aligned__(CAA_CACHE_LINE_SIZE))); \ + } __per_thread_##name[NR_THREADS] +#define DECLARE_PER_THREAD(type, name) extern DEFINE_PER_THREAD(type, name) + +#define per_thread(name, thread) __per_thread_##name[thread].v +#define __get_thread_var(name) per_thread(name, smp_thread_id()) + +#define init_per_thread(name, v) \ + do { \ + int __i_p_t_i; \ + for (__i_p_t_i = 0; __i_p_t_i < NR_THREADS; __i_p_t_i++) \ + per_thread(name, __i_p_t_i) = v; \ + } while (0) + +DEFINE_PER_THREAD(int, smp_processor_id); + +/* + * Bug checks. + */ + +#define BUG_ON(c) do { if (!(c)) abort(); } while (0) + +/* + * Initialization -- Must be called before calling any primitives. + */ + +static void smp_init(void) +{ + int i; + + spin_lock_init(&__thread_id_map_mutex); + __thread_id_map[0] = pthread_self(); + for (i = 1; i < NR_THREADS; i++) + __thread_id_map[i] = __THREAD_ID_MAP_EMPTY; + init_per_thread(smp_processor_id, 0); + if (pthread_key_create(&thread_id_key, NULL) != 0) { + perror("pthread_key_create"); + exit(-1); + } +} + +#endif diff --git a/tests/regression_tests/common/compat-rand.h b/tests/regression_tests/common/compat-rand.h new file mode 100644 index 0000000..6c95abd --- /dev/null +++ b/tests/regression_tests/common/compat-rand.h @@ -0,0 +1,3 @@ +#ifndef _COMPAT_RAND_H +#define _COMPAT_RAND_H +#endif /* _COMPAT_RAND_H */ diff --git a/tests/regression_tests/common/cpuset.h b/tests/regression_tests/common/cpuset.h new file mode 100644 index 0000000..3c23d04 --- /dev/null +++ b/tests/regression_tests/common/cpuset.h @@ -0,0 +1,43 @@ +#ifndef _URCU_TESTS_CPUSET_H +#define _URCU_TESTS_CPUSET_H + +/* + * cpuset.h + * + * Userspace RCU library - test cpuset header + * + * Copyright 2009-2013 - Mathieu Desnoyers + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPU_SET_T) \ + || defined(HAVE_CPU_ZERO) || defined(HAVE_CPU_SET) +# include +#endif + +#ifndef HAVE_CPU_SET_T +typedef unsigned long cpu_set_t; +#endif + +#ifndef HAVE_CPU_ZERO +# define CPU_ZERO(cpuset) do { *(cpuset) = 0; } while(0) +#endif + +#ifndef HAVE_CPU_SET +# define CPU_SET(cpu, cpuset) do { *(cpuset) |= (1UL << (cpu)); } while(0) +#endif + +#endif /* _URCU_TESTS_CPUSET_H */ diff --git a/tests/regression_tests/common/debug-yield.h b/tests/regression_tests/common/debug-yield.h new file mode 100644 index 0000000..b48561c --- /dev/null +++ b/tests/regression_tests/common/debug-yield.h @@ -0,0 +1,103 @@ +#ifndef URCU_TESTS_DEBUG_YIELD_H +#define URCU_TESTS_DEBUG_YIELD_H + +/* + * debug-yield.h + * + * Userspace RCU library tests - Debugging header + * + * Copyright (c) 2009 Mathieu Desnoyers + * Copyright (c) 2009 Paul E. McKenney, IBM Corporation. + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * IBM's contributions to this file may be relicensed under LGPLv2 or later. + */ + +#include +#include +#include +#include + +#include + +#define RCU_YIELD_READ (1 << 0) +#define RCU_YIELD_WRITE (1 << 1) + +/* + * Updates with RCU_SIGNAL are much slower. Account this in the delay. + */ +#ifdef RCU_SIGNAL +/* maximum sleep delay, in us */ +#define MAX_SLEEP 30000 +#else +#define MAX_SLEEP 50 +#endif + +extern unsigned int rcu_yield_active; +extern DECLARE_URCU_TLS(unsigned int, rcu_rand_yield); + +#ifdef DEBUG_YIELD +static inline void rcu_debug_yield_read(void) +{ + if (rcu_yield_active & RCU_YIELD_READ) + if (rand_r(&URCU_TLS(rcu_rand_yield)) & 0x1) + usleep(rand_r(&URCU_TLS(rcu_rand_yield)) % MAX_SLEEP); +} + +static inline void rcu_debug_yield_write(void) +{ + if (rcu_yield_active & RCU_YIELD_WRITE) + if (rand_r(&URCU_TLS(rcu_rand_yield)) & 0x1) + usleep(rand_r(&URCU_TLS(rcu_rand_yield)) % MAX_SLEEP); +} + +static inline void rcu_debug_yield_enable(unsigned int flags) +{ + rcu_yield_active |= flags; +} + +static inline void rcu_debug_yield_disable(unsigned int flags) +{ + rcu_yield_active &= ~flags; +} + +static inline void rcu_debug_yield_init(void) +{ + URCU_TLS(rcu_rand_yield) = time(NULL) ^ (unsigned long) pthread_self(); +} +#else /* DEBUG_YIELD */ +static inline void rcu_debug_yield_read(void) +{ +} + +static inline void rcu_debug_yield_write(void) +{ +} + +static inline void rcu_debug_yield_enable(unsigned int flags) +{ +} + +static inline void rcu_debug_yield_disable(unsigned int flags) +{ +} + +static inline void rcu_debug_yield_init(void) +{ +} +#endif + +#endif /* URCU_TESTS_DEBUG_YIELD_H */ diff --git a/tests/regression_tests/common/thread-id.h b/tests/regression_tests/common/thread-id.h new file mode 100644 index 0000000..cb0d903 --- /dev/null +++ b/tests/regression_tests/common/thread-id.h @@ -0,0 +1,81 @@ +#ifndef _TEST_THREAD_ID_H +#define _TEST_THREAD_ID_H + +/* + * thread-id.h + * + * Userspace RCU library - thread ID + * + * Copyright 2013 - Mathieu Desnoyers + * + * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED + * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + * + * Permission is hereby granted to use or copy this program + * for any purpose, provided the above notices are retained on all copies. + * Permission to modify the code and to distribute modified code is granted, + * provided the above notices are retained, and a notice that the code was + * modified is included with the above copyright notice. + */ + +#ifdef __linux__ +# include + +# if defined(HAVE_GETTID) +/* + * Do not redefine gettid() as it is already included + * in bionic through . Some other libc + * may also already contain an implementation of gettid. + */ +# elif defined(_syscall0) +_syscall0(pid_t, gettid) +# elif defined(__NR_gettid) +static inline pid_t gettid(void) +{ + return syscall(__NR_gettid); +} +# endif + +static inline +unsigned long urcu_get_thread_id(void) +{ + return (unsigned long) gettid(); +} +#elif defined(__FreeBSD__) +# include + +static inline +unsigned long urcu_get_thread_id(void) +{ + return (unsigned long) pthread_getthreadid_np(); +} +#elif defined(__sun__) || defined(__APPLE__) +#include + +static inline +unsigned long urcu_get_thread_id(void) +{ + return (unsigned long) pthread_self(); +} +#elif defined(__CYGWIN__) +#include + +extern unsigned long pthread_getsequence_np(pthread_t *); + +static inline +unsigned long urcu_get_thread_id(void) +{ + pthread_t thr = pthread_self(); + return pthread_getsequence_np(&thr); +} + +#else +# warning "use pid as thread ID" +static inline +unsigned long urcu_get_thread_id(void) +{ + return (unsigned long) getpid(); +} +#endif + +#endif /* _TEST_THREAD_ID_H */ diff --git a/tests/regression_tests/regression/Makefile b/tests/regression_tests/regression/Makefile new file mode 100644 index 0000000..3646890 --- /dev/null +++ b/tests/regression_tests/regression/Makefile @@ -0,0 +1,31 @@ +CFLAGS = -O2 -g -Wall -I../utils -I../common -D_GNU_SOURCE -DHAVE_SCHED_SETAFFINITY -DHAVE_CPU_SET_T -DHAVE_CPU_ZERO -DHAVE_CPU_SET -pthread + +EXECS = test_urcu_fork rcutorture_urcu rcutorture_urcu_signal rcutorture_urcu_mb rcutorture_urcu_bp rcutorture_urcu_qsbr + +SRCS = urcutorture.c ../utils/libtap.a + +all: $(EXECS) + +test_urcu_fork: test_urcu_fork.c ../utils/libtap.a + $(CC) $(CFLAGS) $? -o $@ -lurcu + +rcutorture_urcu: $(SRCS) + $(CC) $(CFLAGS) -DRCU_MEMBARRIER $? -o $@ -lurcu + +rcutorture_urcu_signal: $(SRCS) + $(CC) $(CFLAGS) -DRCU_SIGNAL $? -o $@ -lurcu-signal + +rcutorture_urcu_mb: $(SRCS) + $(CC) $(CFLAGS) -DRCU_MB $? -o $@ -lurcu-mb + +rcutorture_urcu_bp: $(SRCS) + $(CC) $(CFLAGS) -DRCU_BP $? -o $@ -lurcu-bp + +rcutorture_urcu_qsbr: $(SRCS) + $(CC) $(CFLAGS) -DTORTURE_QSBR -DRCU_QSBR $? -o $@ -lurcu-qsbr + +regtest: + ./run.sh regression_tests + +clean: + rm -f $(EXECS) diff --git a/tests/regression_tests/regression/rcutorture.h b/tests/regression_tests/regression/rcutorture.h new file mode 100644 index 0000000..db3dfad --- /dev/null +++ b/tests/regression_tests/regression/rcutorture.h @@ -0,0 +1,612 @@ +/* + * rcutorture.h: simple user-level performance/stress test of RCU. + * + * Usage: + * ./rcu rperf [ ] + * Run a read-side performance test with the specified + * number of readers spaced by . + * Thus "./rcu 16 rperf 2" would run 16 readers on even-numbered + * CPUs from 0 to 30. + * ./rcu uperf [ ] + * Run an update-side performance test with the specified + * number of updaters and specified CPU spacing. + * ./rcu perf [ ] + * Run a combined read/update performance test with the specified + * number of readers and one updater and specified CPU spacing. + * The readers run on the low-numbered CPUs and the updater + * of the highest-numbered CPU. + * + * The above tests produce output as follows: + * + * n_reads: 46008000 n_updates: 146026 nreaders: 2 nupdaters: 1 duration: 1 + * ns/read: 43.4707 ns/update: 6848.1 + * + * The first line lists the total number of RCU reads and updates executed + * during the test, the number of reader threads, the number of updater + * threads, and the duration of the test in seconds. The second line + * lists the average duration of each type of operation in nanoseconds, + * or "nan" if the corresponding type of operation was not performed. + * + * ./rcu stress + * Run a stress test with the specified number of readers and + * one updater. None of the threads are affinitied to any + * particular CPU. + * + * This test produces output as follows: + * + * n_reads: 114633217 n_updates: 3903415 n_mberror: 0 + * rcu_stress_count: 114618391 14826 0 0 0 0 0 0 0 0 0 + * + * The first line lists the number of RCU read and update operations + * executed, followed by the number of memory-ordering violations + * (which will be zero in a correct RCU implementation). The second + * line lists the number of readers observing progressively more stale + * data. A correct RCU implementation will have all but the first two + * numbers non-zero. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright (c) 2008 Paul E. McKenney, IBM Corporation. + */ + +/* + * Test variables. + */ + +#include +#include "tap.h" + +#define NR_TESTS 1 + +DEFINE_PER_THREAD(long long, n_reads_pt); +DEFINE_PER_THREAD(long long, n_updates_pt); + +enum callrcu_type { + CALLRCU_GLOBAL, + CALLRCU_PERCPU, + CALLRCU_PERTHREAD, +}; + +static enum callrcu_type callrcu_type = CALLRCU_GLOBAL; + +long long n_reads = 0LL; +long n_updates = 0L; +int nthreadsrunning; +char argsbuf[64]; + +#define GOFLAG_INIT 0 +#define GOFLAG_RUN 1 +#define GOFLAG_STOP 2 + +volatile int goflag __attribute__((__aligned__(CAA_CACHE_LINE_SIZE))) + = GOFLAG_INIT; + +#define RCU_READ_RUN 1000 + +//MD +#define RCU_READ_NESTABLE + +#ifdef RCU_READ_NESTABLE +#define rcu_read_lock_nest() rcu_read_lock() +#define rcu_read_unlock_nest() rcu_read_unlock() +#else /* #ifdef RCU_READ_NESTABLE */ +#define rcu_read_lock_nest() +#define rcu_read_unlock_nest() +#endif /* #else #ifdef RCU_READ_NESTABLE */ + +#ifdef TORTURE_QSBR +#define mark_rcu_quiescent_state rcu_quiescent_state +#define put_thread_offline rcu_thread_offline +#define put_thread_online rcu_thread_online +#endif + +#ifndef mark_rcu_quiescent_state +#define mark_rcu_quiescent_state() do ; while (0) +#endif /* #ifdef mark_rcu_quiescent_state */ + +#ifndef put_thread_offline +#define put_thread_offline() do ; while (0) +#define put_thread_online() do ; while (0) +#define put_thread_online_delay() do ; while (0) +#else /* #ifndef put_thread_offline */ +#define put_thread_online_delay() synchronize_rcu() +#endif /* #else #ifndef put_thread_offline */ + +/* + * Performance test. + */ + +void *rcu_read_perf_test(void *arg) +{ + int i; + int me = (long)arg; + long long n_reads_local = 0; + + rcu_register_thread(); + run_on(me); + uatomic_inc(&nthreadsrunning); + put_thread_offline(); + while (goflag == GOFLAG_INIT) + (void) poll(NULL, 0, 1); + put_thread_online(); + while (goflag == GOFLAG_RUN) { + for (i = 0; i < RCU_READ_RUN; i++) { + rcu_read_lock(); + /* rcu_read_lock_nest(); */ + /* rcu_read_unlock_nest(); */ + rcu_read_unlock(); + } + n_reads_local += RCU_READ_RUN; + mark_rcu_quiescent_state(); + } + __get_thread_var(n_reads_pt) += n_reads_local; + put_thread_offline(); + rcu_unregister_thread(); + + return (NULL); +} + +void *rcu_update_perf_test(void *arg) +{ + long long n_updates_local = 0; + + if (callrcu_type == CALLRCU_PERTHREAD) { + struct call_rcu_data *crdp; + + crdp = create_call_rcu_data(0, -1); + if (crdp != NULL) { + diag("Successfully using per-thread call_rcu() worker."); + set_thread_call_rcu_data(crdp); + } + } + uatomic_inc(&nthreadsrunning); + while (goflag == GOFLAG_INIT) + (void) poll(NULL, 0, 1); + while (goflag == GOFLAG_RUN) { + synchronize_rcu(); + n_updates_local++; + } + __get_thread_var(n_updates_pt) += n_updates_local; + if (callrcu_type == CALLRCU_PERTHREAD) { + struct call_rcu_data *crdp; + + crdp = get_thread_call_rcu_data(); + set_thread_call_rcu_data(NULL); + call_rcu_data_free(crdp); + } + return NULL; +} + +void perftestinit(void) +{ + init_per_thread(n_reads_pt, 0LL); + init_per_thread(n_updates_pt, 0LL); + uatomic_set(&nthreadsrunning, 0); +} + +int perftestrun(int nthreads, int nreaders, int nupdaters) +{ + int t; + int duration = 1; + + cmm_smp_mb(); + while (uatomic_read(&nthreadsrunning) < nthreads) + (void) poll(NULL, 0, 1); + goflag = GOFLAG_RUN; + cmm_smp_mb(); + sleep(duration); + cmm_smp_mb(); + goflag = GOFLAG_STOP; + cmm_smp_mb(); + wait_all_threads(); + for_each_thread(t) { + n_reads += per_thread(n_reads_pt, t); + n_updates += per_thread(n_updates_pt, t); + } + diag("n_reads: %lld n_updates: %ld nreaders: %d nupdaters: %d duration: %d", + n_reads, n_updates, nreaders, nupdaters, duration); + diag("ns/read: %g ns/update: %g", + ((duration * 1000*1000*1000.*(double)nreaders) / + (double)n_reads), + ((duration * 1000*1000*1000.*(double)nupdaters) / + (double)n_updates)); + if (get_cpu_call_rcu_data(0)) { + diag("Deallocating per-CPU call_rcu threads.\n"); + free_all_cpu_call_rcu_data(); + } + return 0; +} + +int perftest(int nreaders, int cpustride) +{ + int i; + long arg; + + perftestinit(); + for (i = 0; i < nreaders; i++) { + arg = (long)(i * cpustride); + create_thread(rcu_read_perf_test, (void *)arg); + } + arg = (long)(i * cpustride); + create_thread(rcu_update_perf_test, (void *)arg); + return perftestrun(i + 1, nreaders, 1); +} + +int rperftest(int nreaders, int cpustride) +{ + int i; + long arg; + + perftestinit(); + init_per_thread(n_reads_pt, 0LL); + for (i = 0; i < nreaders; i++) { + arg = (long)(i * cpustride); + create_thread(rcu_read_perf_test, (void *)arg); + } + return perftestrun(i, nreaders, 0); +} + +int uperftest(int nupdaters, int cpustride) +{ + int i; + long arg; + + perftestinit(); + init_per_thread(n_reads_pt, 0LL); + for (i = 0; i < nupdaters; i++) { + arg = (long)(i * cpustride); + create_thread(rcu_update_perf_test, (void *)arg); + } + return perftestrun(i, 0, nupdaters); +} + +/* + * Stress test. + */ + +#define RCU_STRESS_PIPE_LEN 10 + +struct rcu_stress { + int pipe_count; + int mbtest; +}; + +struct rcu_stress rcu_stress_array[RCU_STRESS_PIPE_LEN] = { { 0 } }; +struct rcu_stress *rcu_stress_current; +int rcu_stress_idx = 0; + +int n_mberror = 0; +DEFINE_PER_THREAD(long long [RCU_STRESS_PIPE_LEN + 1], rcu_stress_count); + +int garbage = 0; + +void *rcu_read_stress_test(void *arg) +{ + int i; + int itercnt = 0; + struct rcu_stress *p; + int pc; + + rcu_register_thread(); + put_thread_offline(); + while (goflag == GOFLAG_INIT) + (void) poll(NULL, 0, 1); + put_thread_online(); + while (goflag == GOFLAG_RUN) { + rcu_read_lock(); + p = rcu_dereference(rcu_stress_current); + if (p->mbtest == 0) + n_mberror++; + rcu_read_lock_nest(); + for (i = 0; i < 100; i++) + garbage++; + rcu_read_unlock_nest(); + pc = p->pipe_count; + rcu_read_unlock(); + if ((pc > RCU_STRESS_PIPE_LEN) || (pc < 0)) + pc = RCU_STRESS_PIPE_LEN; + __get_thread_var(rcu_stress_count)[pc]++; + __get_thread_var(n_reads_pt)++; + mark_rcu_quiescent_state(); + if ((++itercnt % 0x1000) == 0) { + put_thread_offline(); + put_thread_online_delay(); + put_thread_online(); + } + } + put_thread_offline(); + rcu_unregister_thread(); + + return (NULL); +} + +static pthread_mutex_t call_rcu_test_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t call_rcu_test_cond = PTHREAD_COND_INITIALIZER; + +void rcu_update_stress_test_rcu(struct rcu_head *head) +{ + int ret; + + ret = pthread_mutex_lock(&call_rcu_test_mutex); + if (ret) { + errno = ret; + diag("pthread_mutex_lock: %s", + strerror(errno)); + abort(); + } + ret = pthread_cond_signal(&call_rcu_test_cond); + if (ret) { + errno = ret; + diag("pthread_cond_signal: %s", + strerror(errno)); + abort(); + } + ret = pthread_mutex_unlock(&call_rcu_test_mutex); + if (ret) { + errno = ret; + diag("pthread_mutex_unlock: %s", + strerror(errno)); + abort(); + } +} + +void *rcu_update_stress_test(void *arg) +{ + int i; + struct rcu_stress *p; + struct rcu_head rh; + + while (goflag == GOFLAG_INIT) + (void) poll(NULL, 0, 1); + while (goflag == GOFLAG_RUN) { + i = rcu_stress_idx + 1; + if (i >= RCU_STRESS_PIPE_LEN) + i = 0; + p = &rcu_stress_array[i]; + p->mbtest = 0; + cmm_smp_mb(); + p->pipe_count = 0; + p->mbtest = 1; + rcu_assign_pointer(rcu_stress_current, p); + rcu_stress_idx = i; + for (i = 0; i < RCU_STRESS_PIPE_LEN; i++) + if (i != rcu_stress_idx) + rcu_stress_array[i].pipe_count++; + if (n_updates & 0x1) + synchronize_rcu(); + else { + int ret; + + ret = pthread_mutex_lock(&call_rcu_test_mutex); + if (ret) { + errno = ret; + diag("pthread_mutex_lock: %s", + strerror(errno)); + abort(); + } + rcu_register_thread(); + call_rcu(&rh, rcu_update_stress_test_rcu); + rcu_unregister_thread(); + /* + * Our MacOS X test machine with the following + * config: + * 15.6.0 Darwin Kernel Version 15.6.0 + * root:xnu-3248.60.10~1/RELEASE_X86_64 + * appears to have issues with liburcu-signal + * signal being delivered on top of + * pthread_cond_wait. It seems to make the + * thread continue, and therefore corrupt the + * rcu_head. Work around this issue by + * unregistering the RCU read-side thread + * immediately after call_rcu (call_rcu needs + * us to be registered RCU readers). + */ + ret = pthread_cond_wait(&call_rcu_test_cond, + &call_rcu_test_mutex); + if (ret) { + errno = ret; + diag("pthread_cond_signal: %s", + strerror(errno)); + abort(); + } + ret = pthread_mutex_unlock(&call_rcu_test_mutex); + if (ret) { + errno = ret; + diag("pthread_mutex_unlock: %s", + strerror(errno)); + abort(); + } + } + n_updates++; + } + + return NULL; +} + +void *rcu_fake_update_stress_test(void *arg) +{ + if (callrcu_type == CALLRCU_PERTHREAD) { + struct call_rcu_data *crdp; + + crdp = create_call_rcu_data(0, -1); + if (crdp != NULL) { + diag("Successfully using per-thread call_rcu() worker."); + set_thread_call_rcu_data(crdp); + } + } + while (goflag == GOFLAG_INIT) + (void) poll(NULL, 0, 1); + while (goflag == GOFLAG_RUN) { + synchronize_rcu(); + (void) poll(NULL, 0, 1); + } + if (callrcu_type == CALLRCU_PERTHREAD) { + struct call_rcu_data *crdp; + + crdp = get_thread_call_rcu_data(); + set_thread_call_rcu_data(NULL); + call_rcu_data_free(crdp); + } + return NULL; +} + +int stresstest(int nreaders) +{ + int i; + int t; + long long *p; + long long sum; + + init_per_thread(n_reads_pt, 0LL); + for_each_thread(t) { + p = &per_thread(rcu_stress_count,t)[0]; + for (i = 0; i <= RCU_STRESS_PIPE_LEN; i++) + p[i] = 0LL; + } + rcu_stress_current = &rcu_stress_array[0]; + rcu_stress_current->pipe_count = 0; + rcu_stress_current->mbtest = 1; + for (i = 0; i < nreaders; i++) + create_thread(rcu_read_stress_test, NULL); + create_thread(rcu_update_stress_test, NULL); + for (i = 0; i < 5; i++) + create_thread(rcu_fake_update_stress_test, NULL); + cmm_smp_mb(); + goflag = GOFLAG_RUN; + cmm_smp_mb(); + sleep(10); + cmm_smp_mb(); + goflag = GOFLAG_STOP; + cmm_smp_mb(); + wait_all_threads(); + for_each_thread(t) + n_reads += per_thread(n_reads_pt, t); + diag("n_reads: %lld n_updates: %ld n_mberror: %d", + n_reads, n_updates, n_mberror); + rdiag_start(); + rdiag("rcu_stress_count:"); + for (i = 0; i <= RCU_STRESS_PIPE_LEN; i++) { + sum = 0LL; + for_each_thread(t) { + sum += per_thread(rcu_stress_count, t)[i]; + } + rdiag(" %lld", sum); + } + rdiag_end(); + if (get_cpu_call_rcu_data(0)) { + diag("Deallocating per-CPU call_rcu threads."); + free_all_cpu_call_rcu_data(); + } + if (!n_mberror) + return 0; + else + return -1; +} + +/* + * Mainprogram. + */ + +void usage(int argc, char *argv[]) +{ + diag("Usage: %s nreaders [ perf | rperf | uperf | stress ] [ stride ] [ callrcu_global | callrcu_percpu | callrcu_perthread ]\n", argv[0]); + exit(-1); +} + +int main(int argc, char *argv[]) +{ + int nreaders = 1; + int cpustride = 1; + + plan_tests(NR_TESTS); + + smp_init(); + //rcu_init(); + if (argc > 4) { + const char *callrcu_str = argv[4];; + + if (strcmp(callrcu_str, "callrcu_global") == 0) { + callrcu_type = CALLRCU_GLOBAL; + } else if (strcmp(callrcu_str, "callrcu_percpu") == 0) { + callrcu_type = CALLRCU_PERCPU; + } else if (strcmp(callrcu_str, "callrcu_perthread") == 0) { + callrcu_type = CALLRCU_PERTHREAD; + } else { + usage(argc, argv); + goto end; + } + } + + switch (callrcu_type) { + case CALLRCU_GLOBAL: + diag("Using global per-process call_rcu thread."); + break; + case CALLRCU_PERCPU: + diag("Using per-CPU call_rcu threads."); + if (create_all_cpu_call_rcu_data(0)) + diag("create_all_cpu_call_rcu_data: %s", + strerror(errno)); + break; + case CALLRCU_PERTHREAD: + diag("Using per-thread call_rcu() worker."); + break; + default: + abort(); + } + +#ifdef DEBUG_YIELD + yield_active |= YIELD_READ; + yield_active |= YIELD_WRITE; +#endif + + if (argc > 1) { + if (strcmp(argv[1], "-h") == 0 + || strcmp(argv[1], "--help") == 0) { + usage(argc, argv); + goto end; + } + nreaders = strtoul(argv[1], NULL, 0); + if (argc == 2) { + ok(!perftest(nreaders, cpustride), + "perftest readers: %d, stride: %d", + nreaders, cpustride); + goto end; + } + if (argc > 3) + cpustride = strtoul(argv[3], NULL, 0); + if (strcmp(argv[2], "perf") == 0) + ok(!perftest(nreaders, cpustride), + "perftest readers: %d, stride: %d", + nreaders, cpustride); + else if (strcmp(argv[2], "rperf") == 0) + ok(!rperftest(nreaders, cpustride), + "rperftest readers: %d, stride: %d", + nreaders, cpustride); + else if (strcmp(argv[2], "uperf") == 0) + ok(!uperftest(nreaders, cpustride), + "uperftest readers: %d, stride: %d", + nreaders, cpustride); + else if (strcmp(argv[2], "stress") == 0) + ok(!stresstest(nreaders), + "stresstest readers: %d, stride: %d", + nreaders, cpustride); + else + usage(argc, argv); + } else { + usage(argc, argv); + } +end: + return exit_status(); +} diff --git a/tests/regression_tests/regression/regression_tests b/tests/regression_tests/regression/regression_tests new file mode 100644 index 0000000..628308c --- /dev/null +++ b/tests/regression_tests/regression/regression_tests @@ -0,0 +1,61 @@ +./test_urcu_fork +./rcutorture_urcu `nproc` perf 1 callrcu_global +./rcutorture_urcu_signal `nproc` perf 1 callrcu_global +./rcutorture_urcu_mb `nproc` perf 1 callrcu_global +./rcutorture_urcu_bp `nproc` perf 1 callrcu_global +./rcutorture_urcu_qsbr `nproc` perf 1 callrcu_global +./rcutorture_urcu `nproc` rperf 1 callrcu_global +./rcutorture_urcu_signal `nproc` rperf 1 callrcu_global +./rcutorture_urcu_mb `nproc` rperf 1 callrcu_global +./rcutorture_urcu_bp `nproc` rperf 1 callrcu_global +./rcutorture_urcu_qsbr `nproc` rperf 1 callrcu_global +./rcutorture_urcu `nproc` uperf 1 callrcu_global +./rcutorture_urcu_signal `nproc` uperf 1 callrcu_global +./rcutorture_urcu_mb `nproc` uperf 1 callrcu_global +./rcutorture_urcu_bp `nproc` uperf 1 callrcu_global +./rcutorture_urcu_qsbr `nproc` uperf 1 callrcu_global +./rcutorture_urcu `nproc` stress 1 callrcu_global +./rcutorture_urcu_signal `nproc` stress 1 callrcu_global +./rcutorture_urcu_mb `nproc` stress 1 callrcu_global +./rcutorture_urcu_bp `nproc` stress 1 callrcu_global +./rcutorture_urcu_qsbr `nproc` stress 1 callrcu_global +./rcutorture_urcu `nproc` perf 1 callrcu_percpu +./rcutorture_urcu_signal `nproc` perf 1 callrcu_percpu +./rcutorture_urcu_mb `nproc` perf 1 callrcu_percpu +./rcutorture_urcu_bp `nproc` perf 1 callrcu_percpu +./rcutorture_urcu_qsbr `nproc` perf 1 callrcu_percpu +./rcutorture_urcu `nproc` rperf 1 callrcu_percpu +./rcutorture_urcu_signal `nproc` rperf 1 callrcu_percpu +./rcutorture_urcu_mb `nproc` rperf 1 callrcu_percpu +./rcutorture_urcu_bp `nproc` rperf 1 callrcu_percpu +./rcutorture_urcu_qsbr `nproc` rperf 1 callrcu_percpu +./rcutorture_urcu `nproc` uperf 1 callrcu_percpu +./rcutorture_urcu_signal `nproc` uperf 1 callrcu_percpu +./rcutorture_urcu_mb `nproc` uperf 1 callrcu_percpu +./rcutorture_urcu_bp `nproc` uperf 1 callrcu_percpu +./rcutorture_urcu_qsbr `nproc` uperf 1 callrcu_percpu +./rcutorture_urcu `nproc` stress 1 callrcu_percpu +./rcutorture_urcu_signal `nproc` stress 1 callrcu_percpu +./rcutorture_urcu_mb `nproc` stress 1 callrcu_percpu +./rcutorture_urcu_bp `nproc` stress 1 callrcu_percpu +./rcutorture_urcu_qsbr `nproc` stress 1 callrcu_percpu +./rcutorture_urcu `nproc` perf 1 callrcu_perthread +./rcutorture_urcu_signal `nproc` perf 1 callrcu_perthread +./rcutorture_urcu_mb `nproc` perf 1 callrcu_perthread +./rcutorture_urcu_bp `nproc` perf 1 callrcu_perthread +./rcutorture_urcu_qsbr `nproc` perf 1 callrcu_perthread +./rcutorture_urcu `nproc` rperf 1 callrcu_perthread +./rcutorture_urcu_signal `nproc` rperf 1 callrcu_perthread +./rcutorture_urcu_mb `nproc` rperf 1 callrcu_perthread +./rcutorture_urcu_bp `nproc` rperf 1 callrcu_perthread +./rcutorture_urcu_qsbr `nproc` rperf 1 callrcu_perthread +./rcutorture_urcu `nproc` uperf 1 callrcu_perthread +./rcutorture_urcu_signal `nproc` uperf 1 callrcu_perthread +./rcutorture_urcu_mb `nproc` uperf 1 callrcu_perthread +./rcutorture_urcu_bp `nproc` uperf 1 callrcu_perthread +./rcutorture_urcu_qsbr `nproc` uperf 1 callrcu_perthread +./rcutorture_urcu `nproc` stress 1 callrcu_perthread +./rcutorture_urcu_signal `nproc` stress 1 callrcu_perthread +./rcutorture_urcu_mb `nproc` stress 1 callrcu_perthread +./rcutorture_urcu_bp `nproc` stress 1 callrcu_perthread +./rcutorture_urcu_qsbr `nproc` stress 1 callrcu_perthread diff --git a/tests/regression_tests/regression/run.sh b/tests/regression_tests/regression/run.sh new file mode 100755 index 0000000..1ec7e5e --- /dev/null +++ b/tests/regression_tests/regression/run.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Copyright (C) 2013 - Christian Babeux +# 2016 - Michael Jeanson +# +# 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; only version 2 +# of the License. +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +INPUT=$1 +ARGS=() +shift 1 + +if [ -z "${INPUT}" ]; then + echo "Error: No testlist. Please specify a testlist to run." + exit 1 +fi + +if [ "x$V" == "x1" ]; then + ARGS+=('-v') +fi + +prove "${@}" "${ARGS[@]}" --merge --exec '' - < "${INPUT}" diff --git a/tests/regression_tests/regression/test_urcu_fork.c b/tests/regression_tests/regression/test_urcu_fork.c new file mode 100644 index 0000000..8118be7 --- /dev/null +++ b/tests/regression_tests/regression/test_urcu_fork.c @@ -0,0 +1,214 @@ +/* + * test_urcu_fork.c + * + * Userspace RCU library - test program (fork) + * + * Copyright February 2012 - Mathieu Desnoyers + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef DYNAMIC_LINK_TEST +#define _LGPL_SOURCE +#else +#define rcu_debug_yield_read() +#endif +#include + +#include "tap.h" + +/* We generate children 3 levels deep */ +#define FORK_DEPTH 3 +/* Each generation spawns 10 children */ +#define NR_FORK 10 + +#define NR_TESTS NR_FORK + +static int fork_generation; + +/* + * Only print diagnostic for top level parent process, else the console + * has trouble formatting the tap output. + */ +#define diag_gen0(...) \ + do { \ + if (!fork_generation) \ + diag(__VA_ARGS__); \ + } while (0) + +struct test_node { + int somedata; + struct rcu_head head; +}; + +static void cb(struct rcu_head *head) +{ + struct test_node *node; + + diag_gen0("rcu callback invoked in pid: %d", (int) getpid()); + node = caa_container_of(head, struct test_node, head); + free(node); +} + +static void test_rcu(void) +{ + struct test_node *node; + + rcu_register_thread(); + + synchronize_rcu(); + + rcu_read_lock(); + rcu_read_unlock(); + + node = malloc(sizeof(*node)); + assert(node); + + call_rcu(&node->head, cb); + + synchronize_rcu(); + + rcu_unregister_thread(); +} + +/* + * Return 0 if child, > 0 if parent, < 0 on error. + */ +static int do_fork(const char *execname) +{ + pid_t pid; + + diag_gen0("%s parent pid: %d, before fork", + execname, (int) getpid()); + + call_rcu_before_fork(); + pid = fork(); + if (pid == 0) { + /* child */ + fork_generation++; + tap_disable(); + + call_rcu_after_fork_child(); + diag_gen0("%s child pid: %d, after fork", + execname, (int) getpid()); + test_rcu(); + diag_gen0("%s child pid: %d, after rcu test", + execname, (int) getpid()); + if (fork_generation >= FORK_DEPTH) + exit(EXIT_SUCCESS); + return 0; + } else if (pid > 0) { + int status; + + /* parent */ + call_rcu_after_fork_parent(); + diag_gen0("%s parent pid: %d, after fork", + execname, (int) getpid()); + test_rcu(); + diag_gen0("%s parent pid: %d, after rcu test", + execname, (int) getpid()); + for (;;) { + pid = wait(&status); + if (pid < 0) { + if (!fork_generation) + perror("wait"); + return -1; + } + if (WIFEXITED(status)) { + diag_gen0("child %u exited normally with status %u", + pid, WEXITSTATUS(status)); + if (WEXITSTATUS(status)) + return -1; + break; + } else if (WIFSIGNALED(status)) { + diag_gen0("child %u was terminated by signal %u", + pid, WTERMSIG(status)); + return -1; + } else { + continue; + } + } + return 1; + } else { + if (!fork_generation) + perror("fork"); + return -1; + } +} + +int main(int argc, char **argv) +{ + unsigned int i; + + plan_tests(NR_TESTS); + +#if 0 + /* pthread_atfork does not work with malloc/free in callbacks */ + ret = pthread_atfork(call_rcu_before_fork, + call_rcu_after_fork_parent, + call_rcu_after_fork_child); + if (ret) { + errno = ret; + perror("pthread_atfork"); + exit(EXIT_FAILURE); + } +#endif + +restart: + for (i = 0; i < NR_FORK; i++) { + int ret; + + test_rcu(); + synchronize_rcu(); + ret = do_fork(argv[0]); + if (!fork_generation) { + ok(ret >= 0, "child status %d", ret); + } + if (ret == 0) { /* child */ + goto restart; + } else if (ret < 0) { + goto error; + } else { + /* else parent, continue. */ + } + } + if (!fork_generation) { + return exit_status(); + } else { + exit(EXIT_SUCCESS); + } + +error: + if (!fork_generation) { + return exit_status(); + } else { + exit(EXIT_FAILURE); + } +} diff --git a/tests/regression_tests/regression/urcutorture.c b/tests/regression_tests/regression/urcutorture.c new file mode 100644 index 0000000..5e9b059 --- /dev/null +++ b/tests/regression_tests/regression/urcutorture.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include +#include +#include "api.h" +#define _LGPL_SOURCE + +#ifdef RCU_MEMBARRIER +#include +#endif +#ifdef RCU_SIGNAL +#include +#endif +#ifdef RCU_MB +#include +#endif +#ifdef RCU_QSBR +#include +#endif +#ifdef RCU_BP +#include +#endif + +#include +#include +#include "rcutorture.h" diff --git a/tests/regression_tests/utils/Makefile b/tests/regression_tests/utils/Makefile new file mode 100644 index 0000000..a54f996 --- /dev/null +++ b/tests/regression_tests/utils/Makefile @@ -0,0 +1,10 @@ +CFLAGS = -O2 -g -Wall -D_GNU_SOURCE -DHAVE_LIBPTHREAD -pthread + +LIB = libtap.a +OBJS = tap.o + +$(LIB): $(OBJS) + ar rcs $@ $? + +clean: + rm -f $(LIB) $(OBJS) diff --git a/tests/regression_tests/utils/tap.c b/tests/regression_tests/utils/tap.c new file mode 100644 index 0000000..573ae33 --- /dev/null +++ b/tests/regression_tests/utils/tap.c @@ -0,0 +1,473 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * 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. + */ + +#include +#include +#include +#include + +#include "tap.h" + +static int no_plan = 0; +static int skip_all = 0; +static int have_plan = 0; +static unsigned int test_count = 0; /* Number of tests that have been run */ +static unsigned int e_tests = 0; /* Expected number of tests to run */ +static unsigned int failures = 0; /* Number of tests that failed */ +static char *todo_msg = NULL; +static char *todo_msg_fixed = "libtap malloc issue"; +static int todo = 0; +static int test_died = 0; +static int tap_is_disabled = 0; + +/* Encapsulate the pthread code in a conditional. In the absence of + libpthread the code does nothing */ +#ifdef HAVE_LIBPTHREAD +#include +static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER; +# define LOCK pthread_mutex_lock(&M); +# define UNLOCK pthread_mutex_unlock(&M); +#else +# define LOCK +# define UNLOCK +#endif + +static void _expected_tests(unsigned int); +static void _tap_init(void); +static void _cleanup(void); + +/* + * Generate a test result. + * + * ok -- boolean, indicates whether or not the test passed. + * test_name -- the name of the test, may be NULL + * test_comment -- a comment to print afterwards, may be NULL + */ +unsigned int +_gen_result(int ok, const char *func, char *file, unsigned int line, + char *test_name, ...) +{ + va_list ap; + char *local_test_name = NULL; + char *c; + int name_is_digits; + + LOCK; + + test_count++; + + /* Start by taking the test name and performing any printf() + expansions on it */ + if(test_name != NULL) { + va_start(ap, test_name); + if (vasprintf(&local_test_name, test_name, ap) == -1) { + local_test_name = NULL; + } + va_end(ap); + + /* Make sure the test name contains more than digits + and spaces. Emit an error message and exit if it + does */ + if(local_test_name) { + name_is_digits = 1; + for(c = local_test_name; *c != '\0'; c++) { + if(!isdigit(*c) && !isspace(*c)) { + name_is_digits = 0; + break; + } + } + + if(name_is_digits) { + diag(" You named your test '%s'. You shouldn't use numbers for your test names.", local_test_name); + diag(" Very confusing."); + } + } + } + + if(!ok) { + printf("not "); + failures++; + } + + printf("ok %d", test_count); + + if(test_name != NULL) { + printf(" - "); + + /* Print the test name, escaping any '#' characters it + might contain */ + if(local_test_name != NULL) { + flockfile(stdout); + for(c = local_test_name; *c != '\0'; c++) { + if(*c == '#') + fputc('\\', stdout); + fputc((int)*c, stdout); + } + funlockfile(stdout); + } else { /* vasprintf() failed, use a fixed message */ + printf("%s", todo_msg_fixed); + } + } + + /* If we're in a todo_start() block then flag the test as being + TODO. todo_msg should contain the message to print at this + point. If it's NULL then asprintf() failed, and we should + use the fixed message. + + This is not counted as a failure, so decrement the counter if + the test failed. */ + if(todo) { + printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed); + if(!ok) + failures--; + } + + printf("\n"); + + if(!ok) { + if(getenv("HARNESS_ACTIVE") != NULL) + fputs("\n", stderr); + + diag(" Failed %stest (%s:%s() at line %d)", + todo ? "(TODO) " : "", file, func, line); + } + free(local_test_name); + + UNLOCK; + + /* We only care (when testing) that ok is positive, but here we + specifically only want to return 1 or 0 */ + return ok ? 1 : 0; +} + +/* + * Initialise the TAP library. Will only do so once, however many times it's + * called. + */ +void +_tap_init(void) +{ + static int run_once = 0; + + if(!run_once) { + atexit(_cleanup); + + /* stdout needs to be unbuffered so that the output appears + in the same place relative to stderr output as it does + with Test::Harness */ + setbuf(stdout, 0); + run_once = 1; + } +} + +/* + * Note that there's no plan. + */ +int +plan_no_plan(void) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + have_plan = 1; + no_plan = 1; + + UNLOCK; + + return 1; +} + +/* + * Note that the plan is to skip all tests + */ +int +plan_skip_all(char *reason) +{ + + LOCK; + + _tap_init(); + + skip_all = 1; + + printf("1..0"); + + if(reason != NULL) + printf(" # Skip %s", reason); + + printf("\n"); + + UNLOCK; + + exit(0); +} + +/* + * Note the number of tests that will be run. + */ +int +plan_tests(unsigned int tests) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + if(tests == 0) { + fprintf(stderr, "You said to run 0 tests! You've got to run something.\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + have_plan = 1; + + _expected_tests(tests); + + UNLOCK; + + return e_tests; +} + +unsigned int +diag(char *fmt, ...) +{ + va_list ap; + + fputs("# ", stderr); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputs("\n", stderr); + + return 0; +} + +unsigned int +rdiag_start(void) +{ + fputs("# ", stderr); + return 0; +} + +unsigned int +rdiag(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + return 0; +} + +unsigned int +rdiag_end(void) +{ + fputs("\n", stderr); + return 0; +} + +void +_expected_tests(unsigned int tests) +{ + + printf("1..%d\n", tests); + e_tests = tests; +} + +int +skip(unsigned int n, char *fmt, ...) +{ + va_list ap; + char *skip_msg = NULL; + + LOCK; + + va_start(ap, fmt); + if (asprintf(&skip_msg, fmt, ap) == -1) { + skip_msg = NULL; + } + va_end(ap); + + while(n-- > 0) { + test_count++; + printf("ok %d # skip %s\n", test_count, + skip_msg != NULL ? + skip_msg : "libtap():malloc() failed"); + } + + free(skip_msg); + + UNLOCK; + + return 1; +} + +void +todo_start(char *fmt, ...) +{ + va_list ap; + + LOCK; + + va_start(ap, fmt); + if (vasprintf(&todo_msg, fmt, ap) == -1) { + todo_msg = NULL; + } + va_end(ap); + + todo = 1; + + UNLOCK; +} + +void +todo_end(void) +{ + + LOCK; + + todo = 0; + free(todo_msg); + + UNLOCK; +} + +int +exit_status(void) +{ + int r; + + LOCK; + + /* If there's no plan, just return the number of failures */ + if(no_plan || !have_plan) { + UNLOCK; + return failures; + } + + /* Ran too many tests? Return the number of tests that were run + that shouldn't have been */ + if(e_tests < test_count) { + r = test_count - e_tests; + UNLOCK; + return r; + } + + /* Return the number of tests that failed + the number of tests + that weren't run */ + r = failures + e_tests - test_count; + UNLOCK; + + return r; +} + +/* + * Cleanup at the end of the run, produce any final output that might be + * required. + */ +void +_cleanup(void) +{ + + LOCK; + + if (tap_is_disabled) { + UNLOCK; + return; + } + + /* If plan_no_plan() wasn't called, and we don't have a plan, + and we're not skipping everything, then something happened + before we could produce any output */ + if(!no_plan && !have_plan && !skip_all) { + diag("Looks like your test died before it could output anything."); + UNLOCK; + return; + } + + if(test_died) { + diag("Looks like your test died just after %d.", test_count); + UNLOCK; + return; + } + + + /* No plan provided, but now we know how many tests were run, and can + print the header at the end */ + if(!skip_all && (no_plan || !have_plan)) { + printf("1..%d\n", test_count); + } + + if((have_plan && !no_plan) && e_tests < test_count) { + diag("Looks like you planned %d %s but ran %d extra.", + e_tests, e_tests == 1 ? "test" : "tests", test_count - e_tests); + UNLOCK; + return; + } + + if((have_plan || !no_plan) && e_tests > test_count) { + diag("Looks like you planned %d %s but only ran %d.", + e_tests, e_tests == 1 ? "test" : "tests", test_count); + UNLOCK; + return; + } + + if(failures) + diag("Looks like you failed %d %s of %d.", + failures, failures == 1 ? "test" : "tests", test_count); + + UNLOCK; +} + +/* Disable tap for this process. */ +void +tap_disable(void) +{ + LOCK; + tap_is_disabled = 1; + UNLOCK; +} diff --git a/tests/regression_tests/utils/tap.h b/tests/regression_tests/utils/tap.h new file mode 100644 index 0000000..9118bf5 --- /dev/null +++ b/tests/regression_tests/utils/tap.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * 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. + */ + +/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting + and requires the caller to add the final comma if they've ommitted + the optional arguments */ +#ifdef __GNUC__ +# define ok(e, test, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + test, ## __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + test, ## __VA_ARGS__)) + +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +# define pass(test, ...) ok(1, test, ## __VA_ARGS__); +# define fail(test, ...) ok(0, test, ## __VA_ARGS__); + +# define skip_start(test, n, fmt, ...) \ + do { \ + if((test)) { \ + skip(n, fmt, ## __VA_ARGS__); \ + continue; \ + } +#elif __STDC_VERSION__ >= 199901L /* __GNUC__ */ +# define ok(e, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + __VA_ARGS__)) + +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +# define pass(...) ok(1, __VA_ARGS__); +# define fail(...) ok(0, __VA_ARGS__); + +# define skip_start(test, n, ...) \ + do { \ + if((test)) { \ + skip(n, __VA_ARGS__); \ + continue; \ + } +#else /* __STDC_VERSION__ */ +# error "Needs gcc or C99 compiler for variadic macros." +#endif /* __STDC_VERSION__ */ + +#define skip_end() } while(0); + +unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...); + +int plan_no_plan(void); +int plan_skip_all(char *); +int plan_tests(unsigned int); + +unsigned int diag(char *, ...); + +int skip(unsigned int, char *, ...); + +void todo_start(char *, ...); +void todo_end(void); + +int exit_status(void); + +void tap_disable(void); + +unsigned int rdiag_start(void); +unsigned int rdiag(char *fmt, ...); +unsigned int rdiag_end(void); diff --git a/tests/tests.yml b/tests/tests.yml new file mode 100644 index 0000000..90e5536 --- /dev/null +++ b/tests/tests.yml @@ -0,0 +1,20 @@ +--- +# No tests suitable for atomic environment +# No tests suitable for container environment + +# Tests suitable for classic environment +- hosts: localhost + roles: + - role: standard-test-basic + tags: + - classic + tests: + - regression_tests: + dir: regression_tests + run: make + required_packages: + - userspace-rcu + - userspace-rcu-devel + - perl-Test-Harness + - make + - gcc diff --git a/userspace-rcu.spec b/userspace-rcu.spec index b8480b9..9056c75 100644 --- a/userspace-rcu.spec +++ b/userspace-rcu.spec @@ -1,6 +1,6 @@ Name: userspace-rcu Version: 0.12.1 -Release: 4%{?dist} +Release: 5%{?dist} Summary: RCU (read-copy-update) implementation in user-space License: LGPLv2+ URL: http://liburcu.org @@ -14,6 +14,7 @@ BuildRequires: make BuildRequires: pkgconfig BuildRequires: perl-Test-Harness BuildRequires: autoconf automake libtool +BuildRequires: multilib-rpm-config %description This data synchronization library provides read-side access which scales @@ -46,6 +47,8 @@ V=1 make %{?_smp_mflags} make install DESTDIR=$RPM_BUILD_ROOT find %{buildroot} -type f -name "*.la" -delete rm %{buildroot}/%{_docdir}/%{name}/LICENSE +# Replace arch-dependent header file with arch-independent stub (when needed). +%multilib_fix_c_header --file %{_includedir}/urcu/config.h %check make check @@ -85,6 +88,12 @@ make regtest %changelog +* Mon Apr 19 2021 Benjamin Marzinski - 0.12.1-5 +- Replace arch-dependent /usr/include/urcu/config.h with arch-independent + stub when needed. (bz# 1951223) +- Added CI gating tests +- Resolves: bz #1951223 + * Fri Apr 16 2021 Mohan Boddu - 0.12.1-4 - Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937