mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-02 23:18:44 +00:00
Add *NSYNC unit test suite
This change also fixes the clock_nanosleep() api and polyfills futexes on Windows, Mac, and NetBSD using exponential backoff.
This commit is contained in:
parent
3421b9a580
commit
9849b4c7ba
51 changed files with 5505 additions and 1060 deletions
1
Makefile
1
Makefile
|
@ -160,6 +160,7 @@ include net/https/https.mk # │
|
|||
include third_party/regex/regex.mk #─┘
|
||||
include third_party/tidy/tidy.mk
|
||||
include third_party/third_party.mk
|
||||
include third_party/nsync/testing/testing.mk
|
||||
include libc/testlib/testlib.mk
|
||||
include tool/viz/lib/vizlib.mk
|
||||
include tool/args/args.mk
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/asan.internal.h"
|
||||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/calls/struct/timespec.internal.h"
|
||||
#include "libc/calls/struct/timeval.h"
|
||||
#include "libc/calls/struct/timeval.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
|
@ -69,7 +71,7 @@
|
|||
* (1) it's non-null; (2) `flags` is 0; and (3) -1 w/ `EINTR` is
|
||||
* returned; if this function returns 0 then `rem` is undefined;
|
||||
* if flags is `TIMER_ABSTIME` then `rem` is ignored
|
||||
* @return 0 on success, or -1 w/ errno
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise EINTR when a signal got delivered while we were waiting
|
||||
* @raise ENOTSUP if `clock` is known but we can't use it here
|
||||
* @raise EINVAL if `clock` is unknown to current platform
|
||||
|
@ -77,11 +79,12 @@
|
|||
* @raise EINVAL if `req->tv_nsec ∉ [0,1000000000)`
|
||||
* @raise EFAULT if bad memory was passed
|
||||
* @raise ENOSYS on bare metal
|
||||
* @returnserrno
|
||||
* @norestart
|
||||
*/
|
||||
int clock_nanosleep(int clock, int flags, const struct timespec *req,
|
||||
struct timespec *rem) {
|
||||
int rc;
|
||||
errno_t clock_nanosleep(int clock, int flags, const struct timespec *req,
|
||||
struct timespec *rem) {
|
||||
int rc, e = errno;
|
||||
|
||||
if (!req || (IsAsan() && (!__asan_is_valid_timespec(req) ||
|
||||
(rem && !__asan_is_valid_timespec(rem))))) {
|
||||
|
@ -103,9 +106,18 @@ int clock_nanosleep(int clock, int flags, const struct timespec *req,
|
|||
rc = sys_clock_nanosleep_nt(clock, flags, req, rem);
|
||||
}
|
||||
|
||||
STRACE("clock_nanosleep(%s, %s, %s, [%s]) → %d% m", DescribeClockName(clock),
|
||||
DescribeSleepFlags(flags), flags, DescribeTimespec(0, req),
|
||||
DescribeTimespec(rc, rem), rc);
|
||||
if (rc == -1) {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
}
|
||||
|
||||
#if SYSDEBUG
|
||||
if (!__time_critical) {
|
||||
STRACE("clock_nanosleep(%s, %s, %s, [%s]) → %s", DescribeClockName(clock),
|
||||
DescribeSleepFlags(flags), DescribeTimespec(0, req),
|
||||
DescribeTimespec(rc, rem), DescribeErrnoResult(rc));
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -21,11 +21,14 @@
|
|||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/calls/struct/timespec.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
// TODO(jart): Just delegate to clock_nanosleep()
|
||||
|
||||
/**
|
||||
* Sleeps for relative amount of time.
|
||||
*
|
||||
|
@ -49,9 +52,10 @@ int nanosleep(const struct timespec *req, struct timespec *rem) {
|
|||
} else if (req->tv_sec < 0 ||
|
||||
!(0 <= req->tv_nsec && req->tv_nsec <= 999999999)) {
|
||||
rc = einval();
|
||||
} else if (IsLinux()) {
|
||||
} else if (IsLinux() || IsFreebsd() || IsNetbsd()) {
|
||||
rc = sys_clock_nanosleep(CLOCK_REALTIME, 0, req, rem);
|
||||
} else if (IsOpenbsd() || IsFreebsd() || IsNetbsd()) {
|
||||
if (rc > 0) errno = rc, rc = -1;
|
||||
} else if (IsOpenbsd()) {
|
||||
rc = sys_nanosleep(req, rem);
|
||||
} else if (IsXnu()) {
|
||||
rc = sys_nanosleep_xnu(req, rem);
|
||||
|
|
|
@ -41,6 +41,8 @@ int sys_fadvise_netbsd(int, int, int64_t, int64_t, int) asm("sys_fadvise");
|
|||
* @raise EINVAL if `advice` is invalid or `len` is huge
|
||||
* @raise ESPIPE if `fd` refers to a pipe
|
||||
* @raise ENOSYS on XNU and OpenBSD
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
errno_t posix_fadvise(int fd, uint64_t offset, uint64_t len, int advice) {
|
||||
int rc, e = errno;
|
||||
|
|
|
@ -17,7 +17,21 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/errno.h"
|
||||
|
||||
int posix_madvise(void *addr, uint64_t len, int advice) {
|
||||
return madvise(addr, len, advice);
|
||||
/**
|
||||
* Advises kernel about memory intentions, the POSIX way.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
errno_t posix_madvise(void *addr, uint64_t len, int advice) {
|
||||
int rc, e = errno;
|
||||
rc = madvise(addr, len, advice);
|
||||
if (rc == -1) {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -74,7 +74,6 @@ kErrnoDocs:
|
|||
.e ELOOP,"Too many symbolic links encountered"
|
||||
.e ENOMSG,"No message of desired type"
|
||||
.e EIDRM,"Identifier removed"
|
||||
.e ETIME,"Timer expired"
|
||||
.e EPROTO,"Protocol error"
|
||||
.e EOVERFLOW,"Value too large for defined data type"
|
||||
.e EILSEQ,"Illegal byte sequence"
|
||||
|
@ -103,6 +102,7 @@ kErrnoDocs:
|
|||
.e ESHUTDOWN,"Cannot send after transport endpoint shutdown"
|
||||
.e ETOOMANYREFS,"Too many references: cannot splice"
|
||||
.e ETIMEDOUT,"Connection timed out"
|
||||
.e ETIME,"Timer expired"
|
||||
.e ECONNREFUSED,"Connection refused"
|
||||
.e EHOSTDOWN,"Host is down"
|
||||
.e EHOSTUNREACH,"No route to host"
|
||||
|
|
|
@ -74,7 +74,6 @@ kErrnoNames:
|
|||
.e ELOOP
|
||||
.e ENOMSG
|
||||
.e EIDRM
|
||||
.e ETIME
|
||||
.e EPROTO
|
||||
.e EOVERFLOW
|
||||
.e EILSEQ
|
||||
|
@ -103,6 +102,7 @@ kErrnoNames:
|
|||
.e ESHUTDOWN
|
||||
.e ETOOMANYREFS
|
||||
.e ETIMEDOUT
|
||||
.e ETIME
|
||||
.e ECONNREFUSED
|
||||
.e EHOSTDOWN
|
||||
.e EHOSTUNREACH
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
// Normalizes return value to Linux ABI -errno convention.
|
||||
sys_umtx_op:
|
||||
mov $0x1c6,%eax
|
||||
mov %rcx,%r10
|
||||
syscall
|
||||
jc 1f
|
||||
ret
|
||||
|
|
43
libc/intrin/sys_umtx_timedwait_uint.c
Normal file
43
libc/intrin/sys_umtx_timedwait_uint.c
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
#include "libc/thread/freebsd.internal.h"
|
||||
|
||||
int sys_umtx_timedwait_uint(int *p, int expect, bool pshare,
|
||||
const struct timespec *abstime) {
|
||||
int op;
|
||||
size_t size;
|
||||
struct _umtx_time *tm_p, timo;
|
||||
if (!abstime) {
|
||||
tm_p = 0;
|
||||
size = 0;
|
||||
} else {
|
||||
timo._clockid = CLOCK_REALTIME;
|
||||
timo._flags = UMTX_ABSTIME;
|
||||
timo._timeout = *abstime;
|
||||
tm_p = &timo;
|
||||
size = sizeof(timo);
|
||||
}
|
||||
if (pshare) {
|
||||
op = UMTX_OP_WAIT_UINT;
|
||||
} else {
|
||||
op = UMTX_OP_WAIT_UINT_PRIVATE;
|
||||
}
|
||||
return sys_umtx_op(p, op, expect, (void *)size, tm_p);
|
||||
}
|
|
@ -66,7 +66,6 @@ syscon errno ENOTEMPTY 39 66 66 66 66 145 # directory not empty;
|
|||
syscon errno ELOOP 40 62 62 62 62 1921 # too many levels of symbolic links; bsd consensus; kNtErrorCantResolveFilename; raised by access(2), acct(2), bind(2), chdir(2), chmod(2), chown(2), chroot(2), epoll_ctl(2), execve(2), execveat(2), keyctl(2), link(2), mkdir(2), mknod(2), mount(2), open(2), open_by_handle_at(2), openat2(2), readlink(2), rename(2), rmdir(2), spu_create(2), stat(2), statfs(2), statx(2), symlink(2), truncate(2), unlink(2), utimensat(2)
|
||||
syscon errno ENOMSG 42 91 83 90 83 0 # raised by msgop(2)
|
||||
syscon errno EIDRM 43 90 82 89 82 0 # identifier removed; raised by msgctl(2), msgget(2), msgop(2), semctl(2), semop(2), shmctl(2), shmget(2), shmop(2)
|
||||
syscon errno ETIME 62 101 60 60 92 0 # timer expired; timer expired; raised by connect(2), futex(2), keyctl(2), mq_receive(2), mq_send(2), rtime(2), sem_wait(2)
|
||||
syscon errno EPROTO 71 100 92 95 96 0 # raised by accept(2), connect(2), socket(2), socketpair(2)
|
||||
syscon errno EOVERFLOW 75 84 84 87 84 0 # raised by aio_read(2), copy_file_range(2), ctime(2), fanotify_init(2), lseek(2), mmap(2), open(2), open_by_handle_at(2), sem_post(2), sendfile(2), shmctl(2), stat(2), statfs(2), statvfs(2), time(2), timegm(2)
|
||||
syscon errno EILSEQ 84 92 86 84 85 0 # returned by fgetwc(3), fputwc(3), getwchar(3), putwchar(3), scanf(3), ungetwc(3)
|
||||
|
@ -95,6 +94,7 @@ syscon errno ENOTCONN 107 57 57 57 57 10057 # socket is not conne
|
|||
syscon errno ESHUTDOWN 108 58 58 58 58 10058 # cannot send after transport endpoint shutdown; note that shutdown write is an EPIPE; bsd consensus; WSAESHUTDOWN
|
||||
syscon errno ETOOMANYREFS 109 59 59 59 59 10059 # too many references: cannot splice; bsd consensus; WSAETOOMANYREFS; raised by sendmsg(2), unix(7)
|
||||
syscon errno ETIMEDOUT 110 60 60 60 60 1460 # connection timed out; kNtErrorTimeout; bsd consensus; WSAETIMEDOUT; raised by connect(2), futex(2), keyctl(2), tcp(7)
|
||||
syscon errno ETIME 62 101 60 60 92 0 # timer expired (POSIX.1 XSI STREAMS)
|
||||
syscon errno ECONNREFUSED 111 61 61 61 61 10061 # bsd consensus; WSAECONNREFUSED; raised by connect(2), listen(2), recv(2), unix(7), udp(7)system-imposed limit on the number of threads was encountered.
|
||||
syscon errno EHOSTDOWN 112 64 64 64 64 10064 # bsd consensus; WSAEHOSTDOWN; raised by accept(2)
|
||||
syscon errno EHOSTUNREACH 113 65 65 65 65 10065 # bsd consensus; WSAEHOSTUNREACH; raised by accept(2), ip(7)
|
||||
|
|
|
@ -46,6 +46,7 @@ struct _umtx_time {
|
|||
};
|
||||
|
||||
int sys_umtx_op(void *, int, unsigned long, void *, void *);
|
||||
int sys_umtx_timedwait_uint(int *, int, bool, const struct timespec *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
/**
|
||||
* Destroys pthread attributes.
|
||||
|
|
|
@ -182,6 +182,7 @@ static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) {
|
|||
* @raise EINVAL if `attr` was supplied and had unnaceptable data
|
||||
* @raise EPERM if scheduling policy was requested and user account
|
||||
* isn't authorized to use it
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
*
|
||||
* @return 0 on success, or errno with error
|
||||
* @raise EINVAL if thread is null or already detached
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
int pthread_detach(pthread_t thread) {
|
||||
struct PosixThread *pt;
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
*
|
||||
* @return 0 on success, or errno with error
|
||||
* @raise EDEADLK if thread is detached
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
int pthread_join(pthread_t thread, void **value_ptr) {
|
||||
struct PosixThread *pt;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/synchronization.h"
|
||||
|
@ -66,7 +67,7 @@ static void _wait0_futex(const atomic_int *a, int e) {
|
|||
rc = -GetLastError();
|
||||
}
|
||||
} else if (IsFreebsd()) {
|
||||
rc = sys_umtx_op(a, UMTX_OP_WAIT_UINT, 0, 0, 0);
|
||||
rc = sys_umtx_op(a, UMTX_OP_WAIT_UINT, e, 0, 0);
|
||||
} else {
|
||||
rc = _futex(a, op, e, 0);
|
||||
if (IsOpenbsd() && rc > 0) {
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/stdio/rand.h"
|
||||
#include "libc/testlib/testlib.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
|
||||
TEST(_timespec_gte, test) {
|
||||
EXPECT_FALSE(_timespec_gte((struct timespec){1}, (struct timespec){2}));
|
||||
|
@ -72,6 +74,8 @@ TEST(_timespec_tomillis, test) {
|
|||
EXPECT_EQ(2123, _timespec_tomillis((struct timespec){2, 123000000}));
|
||||
EXPECT_EQ(INT64_MAX, _timespec_tomillis((struct timespec){INT64_MAX, 0}));
|
||||
EXPECT_EQ(INT64_MIN, _timespec_tomillis((struct timespec){INT64_MIN, 0}));
|
||||
EXPECT_EQ(INT64_MAX, _timespec_tomillis(
|
||||
(struct timespec){0x7fffffffffffffff, 999999999}));
|
||||
}
|
||||
|
||||
TEST(_timespec_tomicros, test) {
|
||||
|
|
|
@ -48,12 +48,6 @@ TEST(nanosleep, testNoSignalIsDelivered) {
|
|||
ASSERT_SYS(0, 0, nanosleep(&ts, 0));
|
||||
}
|
||||
|
||||
TEST(clock_nanosleep, testNoSignalIsDelivered) {
|
||||
struct timespec ts = {0, 1};
|
||||
ASSERT_SYS(0, 0, clock_nanosleep(CLOCK_REALTIME, 0, &ts, &ts));
|
||||
ASSERT_SYS(0, 0, clock_nanosleep(CLOCK_REALTIME, 0, &ts, 0));
|
||||
}
|
||||
|
||||
TEST(nanosleep, testInterrupt_remIsUpdated) {
|
||||
struct sigaction sa = {
|
||||
.sa_handler = OnAlrm,
|
||||
|
@ -68,3 +62,24 @@ TEST(nanosleep, testInterrupt_remIsUpdated) {
|
|||
ASSERT_GT(ts.tv_sec, 400);
|
||||
ASSERT_NE(0, ts.tv_nsec);
|
||||
}
|
||||
|
||||
TEST(clock_nanosleep, testNoSignalIsDelivered) {
|
||||
struct timespec ts = {0, 1};
|
||||
ASSERT_EQ(0, clock_nanosleep(CLOCK_REALTIME, 0, &ts, &ts));
|
||||
ASSERT_EQ(0, clock_nanosleep(CLOCK_REALTIME, 0, &ts, 0));
|
||||
}
|
||||
|
||||
TEST(clock_nanosleep, testInterrupt_remIsUpdated) {
|
||||
struct sigaction sa = {
|
||||
.sa_handler = OnAlrm,
|
||||
.sa_flags = SA_RESETHAND,
|
||||
};
|
||||
ASSERT_SYS(0, 0, sigaction(SIGALRM, &sa, 0));
|
||||
struct itimerval it = {{0, 0}, {0, 10000}}; // 10ms singleshot
|
||||
ASSERT_SYS(0, 0, setitimer(ITIMER_REAL, &it, 0));
|
||||
struct timespec ts = {500, 0};
|
||||
ASSERT_EQ(EINTR, clock_nanosleep(CLOCK_REALTIME, 0, &ts, &ts));
|
||||
ASSERT_LT(ts.tv_sec, 500);
|
||||
ASSERT_GT(ts.tv_sec, 400);
|
||||
ASSERT_NE(0, ts.tv_nsec);
|
||||
}
|
||||
|
|
2
third_party/nsync/common.internal.h
vendored
2
third_party/nsync/common.internal.h
vendored
|
@ -202,6 +202,7 @@ struct wait_condition_s {
|
|||
nsync_mu_semaphore_v (&w.sem); */
|
||||
typedef struct {
|
||||
uint32_t tag; /* debug DLL_NSYNC_WAITER, DLL_WAITER, DLL_WAITER_SAMECOND */
|
||||
int flags; /* see WAITER_* bits below */
|
||||
nsync_semaphore sem; /* Thread waits on this semaphore. */
|
||||
struct nsync_waiter_s nw; /* An embedded nsync_waiter_s. */
|
||||
struct nsync_mu_s_ *cv_mu; /* pointer to nsync_mu associated with a cv wait */
|
||||
|
@ -211,7 +212,6 @@ typedef struct {
|
|||
struct wait_condition_s cond; /* A condition on which to acquire a mu. */
|
||||
nsync_dll_element_ same_condition; /* Links neighbours in nw.q with same
|
||||
non-nil condition. */
|
||||
int flags; /* see WAITER_* bits below */
|
||||
} waiter;
|
||||
static const uint32_t WAITER_TAG = 0x0590239f;
|
||||
static const uint32_t NSYNC_WAITER_TAG = 0x726d2ba9;
|
||||
|
|
107
third_party/nsync/futex.c
vendored
107
third_party/nsync/futex.c
vendored
|
@ -16,10 +16,13 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/atomic.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/calls/struct/timespec.internal.h"
|
||||
#include "libc/calls/syscall_support-nt.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/limits.h"
|
||||
|
@ -27,10 +30,10 @@
|
|||
#include "libc/nt/synchronization.h"
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
#include "libc/sysv/consts/futex.h"
|
||||
#include "libc/sysv/consts/timer.h"
|
||||
#include "libc/thread/freebsd.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "third_party/nsync/common.internal.h"
|
||||
#include "third_party/nsync/futex.internal.h"
|
||||
#include "third_party/nsync/mu_semaphore.internal.h"
|
||||
// clang-format off
|
||||
|
||||
|
@ -57,8 +60,8 @@ __attribute__((__constructor__)) static void nsync_futex_init_ (void) {
|
|||
if (IsFreebsd ()) {
|
||||
FUTEX_IS_SUPPORTED = true;
|
||||
FUTEX_WAIT_ = FUTEX_WAIT;
|
||||
FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG;
|
||||
FUTEX_TIMEOUT_IS_ABSOLUTE = true;
|
||||
FUTEX_PRIVATE_FLAG_ = FUTEX_PRIVATE_FLAG;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -101,18 +104,58 @@ __attribute__((__constructor__)) static void nsync_futex_init_ (void) {
|
|||
}
|
||||
}
|
||||
|
||||
static int nsync_futex_polyfill_ (int *p, int expect, struct timespec *timeout) {
|
||||
int rc;
|
||||
atomic_int *w;
|
||||
int64_t max, nanos;
|
||||
struct timespec ts, deadline;
|
||||
|
||||
w = (atomic_int *)p;
|
||||
if (atomic_load_explicit (p, memory_order_relaxed) != expect) {
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
ts = _timespec_real ();
|
||||
if (!timeout) {
|
||||
deadline = nsync_time_no_deadline;
|
||||
} else if (FUTEX_TIMEOUT_IS_ABSOLUTE) {
|
||||
deadline = *timeout;
|
||||
} else {
|
||||
deadline = _timespec_add (ts, *timeout);
|
||||
}
|
||||
|
||||
nanos = 100;
|
||||
max = __SIG_POLLING_INTERVAL_MS * 1000L * 1000;
|
||||
while (_timespec_gt (deadline, ts)) {
|
||||
if (atomic_load_explicit (p, memory_order_relaxed) != expect) {
|
||||
return 0;
|
||||
}
|
||||
ts = _timespec_add (ts, _timespec_fromnanos (nanos));
|
||||
if (_timespec_gt (ts, deadline)) {
|
||||
ts = deadline;
|
||||
}
|
||||
rc = clock_nanosleep (CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0);
|
||||
if (rc) {
|
||||
ASSERT (rc == EINTR);
|
||||
return -EINTR;
|
||||
}
|
||||
if (nanos < max) {
|
||||
nanos <<= 1;
|
||||
if (nanos > max) {
|
||||
nanos = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
int nsync_futex_wait_ (int *p, int expect, char pshare, struct timespec *timeout) {
|
||||
size_t size;
|
||||
uint32_t ms;
|
||||
int rc, op, fop;
|
||||
|
||||
if (!FUTEX_IS_SUPPORTED || (IsWindows() && pshare)) {
|
||||
nsync_yield_ ();
|
||||
if (timeout) {
|
||||
return -EINTR;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if (!FUTEX_IS_SUPPORTED) {
|
||||
return nsync_futex_polyfill_ (p, expect, timeout);
|
||||
}
|
||||
|
||||
op = FUTEX_WAIT_;
|
||||
|
@ -121,6 +164,10 @@ int nsync_futex_wait_ (int *p, int expect, char pshare, struct timespec *timeout
|
|||
}
|
||||
|
||||
if (NSYNC_FUTEX_WIN32 && IsWindows ()) {
|
||||
// Windows 8 futexes don't support multiple processes :(
|
||||
if (pshare) {
|
||||
return nsync_futex_polyfill_ (p, expect, timeout);
|
||||
}
|
||||
if (timeout) {
|
||||
ms = _timespec_tomillis (*timeout);
|
||||
} else {
|
||||
|
@ -132,23 +179,7 @@ int nsync_futex_wait_ (int *p, int expect, char pshare, struct timespec *timeout
|
|||
rc = -GetLastError ();
|
||||
}
|
||||
} else if (IsFreebsd ()) {
|
||||
struct _umtx_time *put, ut;
|
||||
if (!timeout) {
|
||||
put = 0;
|
||||
size = 0;
|
||||
} else {
|
||||
ut._flags = UMTX_ABSTIME;
|
||||
ut._clockid = CLOCK_REALTIME;
|
||||
ut._timeout = *timeout;
|
||||
put = &ut;
|
||||
size = sizeof(ut);
|
||||
}
|
||||
if (pshare) {
|
||||
fop = UMTX_OP_WAIT_UINT;
|
||||
} else {
|
||||
fop = UMTX_OP_WAIT_UINT_PRIVATE;
|
||||
}
|
||||
rc = sys_umtx_op (p, fop, 0, &size, put);
|
||||
rc = sys_umtx_timedwait_uint (p, expect, pshare, timeout);
|
||||
} else {
|
||||
rc = _futex (p, op, expect, timeout, 0, FUTEX_WAIT_BITS_);
|
||||
if (IsOpenbsd() && rc > 0) {
|
||||
|
@ -157,10 +188,10 @@ int nsync_futex_wait_ (int *p, int expect, char pshare, struct timespec *timeout
|
|||
}
|
||||
}
|
||||
|
||||
STRACE("futex(%t, %s, %d, %s) → %s",
|
||||
p, DescribeFutexOp (op), expect,
|
||||
DescribeTimespec (0, timeout),
|
||||
DescribeErrnoResult (rc));
|
||||
STRACE ("futex(%t, %s, %d, %s) → %s",
|
||||
p, DescribeFutexOp (op), expect,
|
||||
DescribeTimespec (0, timeout),
|
||||
DescribeErrnoResult (rc));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
@ -171,7 +202,7 @@ int nsync_futex_wake_ (int *p, int count, char pshare) {
|
|||
|
||||
ASSERT (count == 1 || count == INT_MAX);
|
||||
|
||||
if (!FUTEX_IS_SUPPORTED || (IsWindows() && pshare)) {
|
||||
if (!FUTEX_IS_SUPPORTED) {
|
||||
nsync_yield_ ();
|
||||
return 0;
|
||||
}
|
||||
|
@ -182,6 +213,10 @@ int nsync_futex_wake_ (int *p, int count, char pshare) {
|
|||
}
|
||||
|
||||
if (NSYNC_FUTEX_WIN32 && IsWindows ()) {
|
||||
if (pshare) {
|
||||
nsync_yield_ ();
|
||||
return 0;
|
||||
}
|
||||
if (count == 1) {
|
||||
WakeByAddressSingle (p);
|
||||
} else {
|
||||
|
@ -199,9 +234,9 @@ int nsync_futex_wake_ (int *p, int count, char pshare) {
|
|||
rc = wake (p, op, count);
|
||||
}
|
||||
|
||||
STRACE("futex(%t, %s, %d) → %s", p,
|
||||
DescribeFutexOp(op),
|
||||
count, DescribeErrnoResult(rc));
|
||||
STRACE ("futex(%t, %s, %d) → %s", p,
|
||||
DescribeFutexOp(op),
|
||||
count, DescribeErrnoResult(rc));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
46
third_party/nsync/testing/array.c
vendored
Normal file
46
third_party/nsync/testing/array.c
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/testing/array.h"
|
||||
// clang-format off
|
||||
|
||||
void a_ensure_ (void *v, int delta, int sz) {
|
||||
typedef A_TYPE (void *) a_void_ptr;
|
||||
a_void_ptr *a = (a_void_ptr *) v;
|
||||
int omax = a->h_.max_;
|
||||
if (omax < 0) {
|
||||
omax = -omax;
|
||||
}
|
||||
if (a->h_.len_ + delta > omax) {
|
||||
int nmax = a->h_.len_ + delta;
|
||||
void *na;
|
||||
if (nmax < omax * 2) {
|
||||
nmax = omax * 2;
|
||||
}
|
||||
if (a->h_.max_ <= 0) {
|
||||
na = malloc (nmax * sz);
|
||||
memcpy (na, a->a_, omax*sz);
|
||||
} else {
|
||||
na = realloc (a->a_, nmax*sz);
|
||||
}
|
||||
memset (omax *sz + (char *)na, 0, (nmax - omax) * sz);
|
||||
a->a_ = (void **) na;
|
||||
a->h_.max_ = nmax;
|
||||
}
|
||||
}
|
61
third_party/nsync/testing/array.h
vendored
Normal file
61
third_party/nsync/testing/array.h
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef NSYNC_TESTING_ARRAY_H_
|
||||
#define NSYNC_TESTING_ARRAY_H_
|
||||
/* clang-format off */
|
||||
|
||||
/* Return the number of elements in a C array a.
|
||||
A constant expression if a is a constant expression. */
|
||||
#define NELEM(a) ((int) (sizeof (a) / sizeof (a[0])))
|
||||
|
||||
|
||||
/* A dynamic array */
|
||||
|
||||
/* internal routines */
|
||||
void a_ensure_ (void *v, int delta, int sz);
|
||||
|
||||
/* The following struct prefixes all array values. */
|
||||
struct a_hdr_ {
|
||||
int len_; /* The number of elements considererf to be part of the array. */
|
||||
int max_; /* abs(max_) is the number of elements that have been set aside
|
||||
to store elements of the array. If max_ < 0, the space
|
||||
has been allocated statically. If max_ > 0, the space
|
||||
has been allocated dynamically. */
|
||||
};
|
||||
|
||||
/* A type declaration for an array of "type". Example: typedef A_TYPE (int) array_of_int; */
|
||||
#define A_TYPE(type) struct { struct a_hdr_ h_; type *a_; }
|
||||
|
||||
/* The empty array initializer. Empty arrays can also be initialized by zeroing. */
|
||||
#define A_EMPTY { { 0, 0 }, NULL }
|
||||
|
||||
/* A static initializer using the contents of C array "data".
|
||||
Example:
|
||||
static int some_ints[] = { 7, 11, 13 };
|
||||
static array_of_int x = A_INIT (some_ints); */
|
||||
#define A_INIT(data) { { NELEM (data), -NELEM (data) }, data }
|
||||
|
||||
/* Element i of the array *a (l-value or r-value) */
|
||||
#define A(a,i) ((a)->a_[i])
|
||||
|
||||
/* Append an entry to array *a, and yield it as an l-value. */
|
||||
#define A_PUSH(a) (*(a_ensure_ ((a), 1, sizeof ((a)->a_[0])), &(a)->a_[(a)->h_.len_++]))
|
||||
|
||||
/* Return the length of array *a. */
|
||||
#define A_LEN(a) ((a)->h_.len_)
|
||||
|
||||
/* Set the length of array *a to l. Requires that 0 <= l <= A_LEN (a). */
|
||||
#define A_SET_LEN(a, l) do { if (0 <= (l) && (l) <= (a)->h_.len_) { \
|
||||
(a)->h_.len_ = (l); } else { \
|
||||
*(volatile int *)0 = 0; } } while (0)
|
||||
|
||||
/* Reduce the length of array *a by n. Requires that 0 <= n <= A_LEN (a). */
|
||||
#define A_DISCARD(a, n) do { if (0 <= (n) && (n) <= (a)->h_.len_) { \
|
||||
(a)->h_.len_ -= (n); } else { \
|
||||
*(volatile int *)0 = 0; } } while (0)
|
||||
|
||||
/* Deallocate and disassociate any storage associated with *a, and make *a
|
||||
empty. */
|
||||
#define A_FREE(a) do { if ((a)->h_.max_ > 0) { free ((void *) (a)->a_); } \
|
||||
(a)->a_ = NULL; (a)->h_.max_ = 0; (a)->h_.len_ = 0; \
|
||||
} while (0)
|
||||
|
||||
#endif /*NSYNC_TESTING_ARRAY_H_*/
|
108
third_party/nsync/testing/atm_log.c
vendored
Normal file
108
third_party/nsync/testing/atm_log.c
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/thread/thread.h"
|
||||
// clang-format off
|
||||
|
||||
#ifndef NSYNC_ATM_LOG
|
||||
#define NSYNC_ATM_LOG 0
|
||||
#endif
|
||||
|
||||
struct atm_log {
|
||||
uintptr_t i;
|
||||
uintptr_t thd_id;
|
||||
uintptr_t c;
|
||||
void *p;
|
||||
uintptr_t o;
|
||||
uintptr_t n;
|
||||
const char *file;
|
||||
uintptr_t line;
|
||||
};
|
||||
|
||||
#define LOG_N 14
|
||||
|
||||
static struct atm_log log_entries[1 << LOG_N];
|
||||
static uint32_t log_i;
|
||||
|
||||
static pthread_mutex_t log_mu;
|
||||
|
||||
static pthread_key_t key;
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
static void do_once (void) {
|
||||
pthread_mutex_init (&log_mu, NULL);
|
||||
pthread_key_create (&key, NULL);
|
||||
}
|
||||
static int thread_id;
|
||||
|
||||
void nsync_atm_log_ (int c, void *p, uint32_t o, uint32_t n, const char *file, int line) {
|
||||
if (NSYNC_ATM_LOG) {
|
||||
struct atm_log *e;
|
||||
uint32_t i;
|
||||
int *pthd_id;
|
||||
int thd_id;
|
||||
|
||||
pthread_once (&once, &do_once);
|
||||
pthd_id = (int *) pthread_getspecific (key);
|
||||
|
||||
pthread_mutex_lock (&log_mu);
|
||||
i = log_i++;
|
||||
if (pthd_id == NULL) {
|
||||
thd_id = thread_id++;
|
||||
pthd_id = (int *) malloc (sizeof (*pthd_id));
|
||||
pthread_setspecific (key, pthd_id);
|
||||
*pthd_id = thd_id;
|
||||
} else {
|
||||
thd_id = *pthd_id;
|
||||
}
|
||||
pthread_mutex_unlock (&log_mu);
|
||||
|
||||
e = &log_entries[i & ((1 << LOG_N) - 1)];
|
||||
e->i = i;
|
||||
e->thd_id = thd_id;
|
||||
e->c = c;
|
||||
e->p = p;
|
||||
e->o = o;
|
||||
e->n = n;
|
||||
e->file = file;
|
||||
e->line = line;
|
||||
}
|
||||
}
|
||||
|
||||
void nsync_atm_log_print_ (void) {
|
||||
if (NSYNC_ATM_LOG) {
|
||||
uint32_t i;
|
||||
pthread_once (&once, &do_once);
|
||||
pthread_mutex_lock (&log_mu);
|
||||
for (i = 0; i != (1 << LOG_N); i++) {
|
||||
struct atm_log *e = &log_entries[i];
|
||||
if (e->file != 0) {
|
||||
fprintf (stderr, "%6lx %3d %c p %16p o %8x n %8x %10s:%d\n",
|
||||
(unsigned long) e->i,
|
||||
(int) e->thd_id,
|
||||
e->c <= ' '? '?' : (char)e->c,
|
||||
e->p,
|
||||
(uint32_t) e->o,
|
||||
(uint32_t) e->n,
|
||||
e->file,
|
||||
(int) e->line);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock (&log_mu);
|
||||
}
|
||||
}
|
7
third_party/nsync/testing/atm_log.h
vendored
Normal file
7
third_party/nsync/testing/atm_log.h
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef NSYNC_TESTING_ATM_LOG_H_
|
||||
#define NSYNC_TESTING_ATM_LOG_H_
|
||||
|
||||
void nsync_atm_log_(int, void *, uint32_t, uint32_t, const char *, int);
|
||||
void nsync_atm_log_print_(void);
|
||||
|
||||
#endif /*NSYNC_TESTING_ATM_LOG_H_*/
|
35
third_party/nsync/testing/closure.c
vendored
Normal file
35
third_party/nsync/testing/closure.c
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
// clang-format off
|
||||
|
||||
void nsync_start_thread_ (void (*f) (void *), void *arg);
|
||||
|
||||
/* Run the closure *cl. */
|
||||
void closure_run (closure *cl) {
|
||||
(*cl->f0) (cl);
|
||||
}
|
||||
|
||||
/* Run the closure (closure *), but wrapped to fix the type. */
|
||||
static void closure_run_body (void *v) {
|
||||
closure_run ((closure *)v);
|
||||
}
|
||||
|
||||
void closure_fork (closure *cl) {
|
||||
nsync_start_thread_ (&closure_run_body, cl);
|
||||
}
|
185
third_party/nsync/testing/closure.h
vendored
Normal file
185
third_party/nsync/testing/closure.h
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
#ifndef NSYNC_TESTING_CLOSURE_H_
|
||||
#define NSYNC_TESTING_CLOSURE_H_
|
||||
/* clang-format off */
|
||||
#include "libc/mem/mem.h"
|
||||
|
||||
/* A run-once, self-freeing closure. */
|
||||
typedef struct closure_s {
|
||||
void (*f0) (void *);
|
||||
} closure;
|
||||
|
||||
/* Run the closure *cl, and free it. */
|
||||
void closure_run (closure *cl);
|
||||
|
||||
/* Fork a new thread running the closure *cl, which will be freed when the
|
||||
thread exits. */
|
||||
void closure_fork (closure *cl);
|
||||
|
||||
/* To create a closure, declare a closure constructor with the right function arguments.
|
||||
|
||||
For functions taking no arguments, use
|
||||
CLOSURE_DECL_BODY0 (foo)
|
||||
to generate the static routine:
|
||||
static closure *closure_foo (void (*f) (void));
|
||||
that will return a closure for any function *f that takes no argument.
|
||||
|
||||
For an 1-argument function, use
|
||||
CLOSURE_DECL_BODY1 (foo, type)
|
||||
to generate the static routine:
|
||||
static closure *closure_foo (void (*f) (type), type x);
|
||||
that will return a closure for any function taking a single argument of the
|
||||
specified type.
|
||||
|
||||
For an 2-argument function, use
|
||||
CLOSURE_DECL_BODY2 (foo, type0, type1)
|
||||
to generate the static routine:
|
||||
static closure *closure_foo (void (*f) (type0, type1), type0 x0, type1 x1);
|
||||
that will return a closure for any function taking a "type0" argument, and
|
||||
a "type1" argument.
|
||||
|
||||
And so on, up to 9 arguments.
|
||||
|
||||
For example, to make closures out of qsort():
|
||||
|
||||
// First, just once (per module) define:
|
||||
// static closure *closure_qsort_args (
|
||||
// void (*f) (void *, size_t, size_t, int (*)(const void *, const void *))
|
||||
// void *x0, size_t x1, size_t x2, int (*x3)(const void *, const void *));
|
||||
// by writing:
|
||||
CLOSURE_DECL_BODY4 (qsort_args, void *, size_t, size_t, int (*)(const void *, const void *))
|
||||
|
||||
// Second, for each closure to be created, write something like this:
|
||||
closure *cl = closure_qsort_args (array, n_elements, sizeof (array[0]), &elem_cmp);
|
||||
|
||||
// Then to run (and free) each closure:
|
||||
closure_run (cl);
|
||||
// This is like calling
|
||||
// qsort (array, n_elements, sizeof (array[0]), &elem_cmp);
|
||||
// free (cl);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal macro details follow. */
|
||||
#define CLOSURE_S0(x,e) e
|
||||
#define CLOSURE_S1(x,e) x##0
|
||||
#define CLOSURE_S2(x,e) x##0, x##1
|
||||
#define CLOSURE_S3(x,e) x##0, x##1, x##2
|
||||
#define CLOSURE_S4(x,e) x##0, x##1, x##2, x##3
|
||||
#define CLOSURE_S5(x,e) x##0, x##1, x##2, x##3, x##4
|
||||
#define CLOSURE_S6(x,e) x##0, x##1, x##2, x##3, x##4, x##5
|
||||
#define CLOSURE_S7(x,e) x##0, x##1, x##2, x##3, x##4, x##5, x##6
|
||||
#define CLOSURE_S8(x,e) x##0, x##1, x##2, x##3, x##4, x##5, x##6, x##7
|
||||
#define CLOSURE_S9(x,e) x##0, x##1, x##2, x##3, x##4, x##5, x##6, x##7, x##8
|
||||
|
||||
#define CLOSURE_P0(x,y,p,s,t)
|
||||
#define CLOSURE_P1(x,y,p,s,t) p x##0 y##0 t
|
||||
#define CLOSURE_P2(x,y,p,s,t) p x##0 y##0 s x##1 y##1 t
|
||||
#define CLOSURE_P3(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 t
|
||||
#define CLOSURE_P4(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 t
|
||||
#define CLOSURE_P5(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 t
|
||||
#define CLOSURE_P6(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 t
|
||||
#define CLOSURE_P7(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 s x##6 y##6 t
|
||||
#define CLOSURE_P8(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 s x##6 y##6 s x##7 y##7 t
|
||||
#define CLOSURE_P9(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 s x##6 y##6 s x##7 y##7 s x##8 y##8 t
|
||||
|
||||
#define CLOSURE_COMMA_ ,
|
||||
#define CLOSURE_SEMI_ ;
|
||||
#define CLOSURE_BLANK_
|
||||
|
||||
#define CLOSURE_DECL_BODY_N_(name, n) \
|
||||
struct closure_s_##name { \
|
||||
void (*f0) (void *); /* must be first; matches closure. */ \
|
||||
void (*f) (CLOSURE_S##n (closure_t_##name##_,void)); \
|
||||
CLOSURE_P##n (closure_t_##name##_, a, CLOSURE_BLANK_, \
|
||||
CLOSURE_SEMI_, CLOSURE_SEMI_) \
|
||||
}; \
|
||||
static void closure_f0_##name (void *v) { \
|
||||
struct closure_s_##name *a = (struct closure_s_##name *) v; \
|
||||
(*a->f) (CLOSURE_S##n (a->a,CLOSURE_BLANK_)); \
|
||||
free (a); \
|
||||
} \
|
||||
static closure *closure_##name (void (*f) (CLOSURE_S##n (closure_t_##name##_,void)) \
|
||||
CLOSURE_P##n (closure_t_##name##_, a, CLOSURE_COMMA_, \
|
||||
CLOSURE_COMMA_, CLOSURE_BLANK_)) { \
|
||||
struct closure_s_##name *cl = (struct closure_s_##name *) malloc (sizeof (*cl)); \
|
||||
cl->f0 = &closure_f0_##name; \
|
||||
cl->f = f; \
|
||||
CLOSURE_P##n (cl->a, = a, CLOSURE_BLANK_, CLOSURE_SEMI_, CLOSURE_SEMI_) \
|
||||
return ((closure *) cl); \
|
||||
}
|
||||
|
||||
|
||||
#define CLOSURE_DECL_BODY0(name) \
|
||||
CLOSURE_DECL_BODY_N_ (name, 0)
|
||||
#define CLOSURE_DECL_BODY1(name, t0) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 1)
|
||||
#define CLOSURE_DECL_BODY2(name, t0, t1) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 2)
|
||||
#define CLOSURE_DECL_BODY3(name, t0, t1, t2) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 3)
|
||||
#define CLOSURE_DECL_BODY4(name, t0, t1, t2, t3) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 4)
|
||||
#define CLOSURE_DECL_BODY5(name, t0, t1, t2, t3, t4) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 5)
|
||||
#define CLOSURE_DECL_BODY6(name, t0, t1, t2, t3, t4, t5) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 6)
|
||||
#define CLOSURE_DECL_BODY7(name, t0, t1, t2, t3, t4, t5, t6) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
typedef t6 closure_t_##name##_6; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 7)
|
||||
#define CLOSURE_DECL_BODY8(name, t0, t1, t2, t3, t4, t5, t6, t7) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
typedef t6 closure_t_##name##_6; \
|
||||
typedef t7 closure_t_##name##_7; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 8)
|
||||
#define CLOSURE_DECL_BODY9(name, t0, t1, t2, t3, t4, t5, t6, t7, t8) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
typedef t6 closure_t_##name##_6; \
|
||||
typedef t7 closure_t_##name##_7; \
|
||||
typedef t8 closure_t_##name##_8; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 9)
|
||||
|
||||
#endif /*NSYNC_TESTING_CLOSURE_H_*/
|
156
third_party/nsync/testing/counter_test.c
vendored
Normal file
156
third_party/nsync/testing/counter_test.c
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "third_party/nsync/counter.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
// clang-format off
|
||||
|
||||
/* Verify the properties of a zero counter. */
|
||||
static void test_counter_zero (testing t) {
|
||||
int i;
|
||||
nsync_counter c = nsync_counter_new (0);
|
||||
for (i = 0; i != 2; i++) {
|
||||
if (nsync_counter_value (c) != 0) {
|
||||
TEST_ERROR (t, ("zero counter is not zero (test, %d)", i));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_zero) != 0) {
|
||||
TEST_ERROR (t, ("zero counter is not zero (poll, %d)", i));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("zero counter is not zero (infinite wait, %d)", i));
|
||||
}
|
||||
nsync_counter_add (c, 0);
|
||||
}
|
||||
nsync_counter_free (c);
|
||||
}
|
||||
|
||||
|
||||
/* Verify the properties of a non-zero counter. */
|
||||
static void test_counter_non_zero (testing t) {
|
||||
nsync_time start;
|
||||
nsync_time waited;
|
||||
nsync_time abs_deadline;
|
||||
nsync_counter c = nsync_counter_new (1);
|
||||
if (nsync_counter_value (c) != 1) {
|
||||
TEST_ERROR (t, ("counter is not 1 (test)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_zero) != 1) {
|
||||
TEST_ERROR (t, ("counter is not 1 (poll)"));
|
||||
}
|
||||
start = nsync_time_now ();
|
||||
abs_deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (1000));
|
||||
if (nsync_counter_wait (c, abs_deadline) != 1) {
|
||||
TEST_ERROR (t, ("counter is not 1 (1s wait)"));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("timed wait on non-zero counter returned too quickly (1s wait took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("timed wait on non-zero counter returned too slowly (1s wait took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
|
||||
if (nsync_counter_add (c, -1) != 0) {
|
||||
TEST_ERROR (t, ("zero counter note is not 0 (add)"));
|
||||
}
|
||||
|
||||
if (nsync_counter_value (c) != 0) {
|
||||
TEST_ERROR (t, ("zero counter note is not 0 (test)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_zero) != 0) {
|
||||
TEST_ERROR (t, ("zero counter note is not 0 (poll)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("zero counter note is not 0 (infinite wait)"));
|
||||
}
|
||||
nsync_counter_free (c);
|
||||
}
|
||||
|
||||
static void decrement_at (nsync_counter c, nsync_time abs_deadline) {
|
||||
nsync_time_sleep_until (abs_deadline);
|
||||
nsync_counter_add (c, -1);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY2 (decrement, nsync_counter, nsync_time)
|
||||
|
||||
/* Test decrement of a counter. */
|
||||
static void test_counter_decrement (testing t) {
|
||||
nsync_time start;
|
||||
nsync_time waited;
|
||||
nsync_counter c = nsync_counter_new (1);
|
||||
closure_fork (closure_decrement (&decrement_at, c,
|
||||
nsync_time_add (nsync_time_now (), nsync_time_ms (1000))));
|
||||
start = nsync_time_now ();
|
||||
if (nsync_counter_wait (c, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0"));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("counter wait too fast (1s delay took %s)", nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("counter wait too slow (1s delay took %s)", nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_counter_value (c) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0 (test)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_zero) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0 (poll)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0 (infinite wait)"));
|
||||
}
|
||||
nsync_counter_free (c);
|
||||
|
||||
c = nsync_counter_new (1);
|
||||
closure_fork (closure_decrement (&decrement_at, c,
|
||||
nsync_time_add (nsync_time_now (), nsync_time_ms (1000))));
|
||||
start = nsync_time_now ();
|
||||
while (nsync_counter_value (c) != 0) {
|
||||
nsync_time_sleep (nsync_time_ms (10));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("counter wait too fast (1s delay took %s)", nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("counter wait too slow (1s delay took %s)", nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_counter_value (c) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0 (test)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_zero) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0 (poll)"));
|
||||
}
|
||||
if (nsync_counter_wait (c, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("counter is not 0 (infinite wait)"));
|
||||
}
|
||||
nsync_counter_free (c);
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_counter_zero);
|
||||
TEST_RUN (tb, test_counter_non_zero);
|
||||
TEST_RUN (tb, test_counter_decrement);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
559
third_party/nsync/testing/cv_mu_timeout_stress_test.c
vendored
Normal file
559
third_party/nsync/testing/cv_mu_timeout_stress_test.c
vendored
Normal file
|
@ -0,0 +1,559 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/stdio/rand.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/cv.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
// clang-format off
|
||||
|
||||
/* A cv_stress_data represents the data used by the threads of the tests below. */
|
||||
typedef struct cv_stress_data_s {
|
||||
nsync_mu mu; /* protects fields below */
|
||||
uintmax_t count; /* incremented by the various threads */
|
||||
uintmax_t timeouts; /* incremented on each timeout */
|
||||
uintmax_t reader_loops; /* the number of loops executed by the reader threads, if any */
|
||||
|
||||
unsigned refs; /* ref count: one per normal test thread, decremented on its exit */
|
||||
unsigned reader_refs; /* ref count: one per reader test thread, decremented on its exit */
|
||||
|
||||
int use_cv; /* threads are using CVs; under mu */
|
||||
nsync_cv count_is_imod4[4]; /* element i signalled when count==i mod 4 if use_cv non-0. */
|
||||
nsync_cv refs_is_zero; /* signalled when refs==0 */
|
||||
nsync_cv reader_refs_is_zero; /* signalled when reader_refs==0 */
|
||||
|
||||
|
||||
/* iterations per writer thread; under mu--increased until deadline exceeded */
|
||||
uintmax_t loop_count;
|
||||
|
||||
/* number of various types of thread to create -- r/o after init */
|
||||
uintmax_t cv_threads_per_value;
|
||||
uintmax_t cv_reader_threads_per_value;
|
||||
uintmax_t mu_threads_per_value;
|
||||
uintmax_t mu_reader_threads_per_value;
|
||||
|
||||
/* end times */
|
||||
nsync_time deadline; /* r/o after init */
|
||||
} cv_stress_data;
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
/* The delays in cv_stress_inc_loop(), cv_stress_reader_loop(), mu_stress_inc_loop(),
|
||||
and mu_stress_reader_loop() are uniformly distributed from 0 to
|
||||
STRESS_MAX_DELAY_MICROS-1 microseconds. */
|
||||
#define STRESS_MAX_DELAY_MICROS (4000) /* maximum delay */
|
||||
#define STRESS_MEAN_DELAY_MICROS (STRESS_MAX_DELAY_MICROS / 2) /* mean delay */
|
||||
#define STRESS_EXPECT_TIMEOUTS_PER_SEC (1000000 / STRESS_MEAN_DELAY_MICROS) /* expect timeouts/s*/
|
||||
|
||||
/* Acquire s.mu, then increment s.count n times, each time
|
||||
waiting until condition is true. Use a random delay between 0us and 999us
|
||||
for each wait; if the timeout expires, increment s.timeouts, and
|
||||
retry the wait. Decrement s.refs before the returning. */
|
||||
static void cv_stress_inc_loop (cv_stress_data *s, uintmax_t count_imod4) {
|
||||
uintmax_t i;
|
||||
nsync_mu_lock (&s->mu);
|
||||
s->use_cv = 1;
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
for (i = 0; i != s->loop_count; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
while ((s->count & 3) != count_imod4) {
|
||||
nsync_time abs_deadline;
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
while (nsync_cv_wait_with_deadline (
|
||||
&s->count_is_imod4[count_imod4],
|
||||
&s->mu, abs_deadline, NULL) != 0 &&
|
||||
(s->count&3) != count_imod4) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->timeouts++;
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
}
|
||||
}
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->count++;
|
||||
nsync_cv_signal (&s->count_is_imod4[s->count&3]);
|
||||
}
|
||||
s->refs--;
|
||||
if (s->refs == 0) {
|
||||
if (s->reader_refs != 0) { /* wake any readers so they will exit */
|
||||
for (i = 0; i != 4; i++) {
|
||||
nsync_cv_broadcast (&s->count_is_imod4[i]);
|
||||
}
|
||||
}
|
||||
nsync_cv_signal (&s->refs_is_zero);
|
||||
}
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY2 (cv_stress_inc_loop, cv_stress_data *, uintmax_t)
|
||||
|
||||
/* Acquires s.u in reader mode, and wait until a
|
||||
condition is true or a timeout occurs on a random wait between 0us and 999us, repeatedly.
|
||||
Every 16 times, release the reader lock, but immediately reacquire it.
|
||||
Once the count of threads running cv_stress_inc_loop() reaches zero (s.refs == 0),
|
||||
sum the number of loops complete into s.reader_loops, and
|
||||
the number of timeouts experience into s.timeouts.
|
||||
Then decrement s.reader_refs before returning. */
|
||||
static void cv_stress_reader_loop (cv_stress_data *s, uintmax_t count_imod4) {
|
||||
uintmax_t loops;
|
||||
uintmax_t timeouts = 0;
|
||||
nsync_mu_lock (&s->mu);
|
||||
s->use_cv = 1;
|
||||
nsync_mu_unlock (&s->mu);
|
||||
nsync_mu_rlock (&s->mu);
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
loops = 0;
|
||||
while (s->refs != 0) {
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
while ((s->count&3) != count_imod4 && s->refs != 0) {
|
||||
nsync_time abs_deadline;
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
while (nsync_cv_wait_with_deadline (&s->count_is_imod4[count_imod4],
|
||||
&s->mu, abs_deadline, NULL) != 0 &&
|
||||
(s->count&3) != count_imod4 && s->refs != 0) {
|
||||
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
timeouts++;
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
}
|
||||
}
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
loops++;
|
||||
if ((loops & 0xf) == 0) {
|
||||
nsync_mu_runlock (&s->mu);
|
||||
if ((loops & 0xfff) == 0) {
|
||||
nsync_time_sleep (nsync_time_ms (1));
|
||||
}
|
||||
nsync_mu_rlock (&s->mu);
|
||||
}
|
||||
}
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
nsync_mu_runlock (&s->mu);
|
||||
|
||||
nsync_mu_lock (&s->mu);
|
||||
s->reader_loops += loops;
|
||||
s->timeouts += timeouts;
|
||||
s->reader_refs--;
|
||||
if (s->reader_refs == 0) {
|
||||
nsync_cv_signal (&s->reader_refs_is_zero);
|
||||
}
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY2 (cv_stress_reader_loop, cv_stress_data *, uintmax_t)
|
||||
|
||||
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
/* These tests use the data structure cv_stress_data defined above.
|
||||
One test uses nsync_mu and nsync_cv, one nsync_mu and its conditional critical sections, while
|
||||
a third mixes conditional critical sections and condition variables; they
|
||||
all the routines above */
|
||||
|
||||
/* ---------------------------
|
||||
The various conditions that threads wait for on cv_stress_data. */
|
||||
static int count_is0mod4 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count & 3) == 0);
|
||||
}
|
||||
static int count_is1mod4 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count & 3) == 1);
|
||||
}
|
||||
static int count_is2mod4 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count & 3) == 2);
|
||||
}
|
||||
static int count_is3mod4 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count & 3) == 3);
|
||||
}
|
||||
static int count_is0mod4or_refs_is0 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count&3) == 0 || s->refs == 0);
|
||||
}
|
||||
static int count_is1mod4or_refs_is0 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count&3) == 1 || s->refs == 0);
|
||||
}
|
||||
static int count_is2mod4or_refs_is0 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count&3) == 2 || s->refs == 0);
|
||||
}
|
||||
static int count_is3mod4or_refs_is0 (const void *v) {
|
||||
const cv_stress_data *s = (const cv_stress_data *) v;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
return ((s->count&3) == 3 || s->refs == 0);
|
||||
}
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
typedef int (*condition_func) (const void *);
|
||||
|
||||
/* Acquire s.mu, then increment s.count n times, each time
|
||||
waiting until condition is true. Use a random delay between 0us and 999us
|
||||
for each wait; if the timeout expires, increment s.timeouts, and
|
||||
the retry the wait. Decrement s.refs before returning. */
|
||||
static void mu_stress_inc_loop (cv_stress_data *s, condition_func condition,
|
||||
const void *condition_arg) {
|
||||
uintmax_t i;
|
||||
nsync_mu_lock (&s->mu);
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
for (i = 0; i != s->loop_count; i++) {
|
||||
nsync_time abs_deadline;
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
while (nsync_mu_wait_with_deadline (&s->mu, condition, condition_arg, NULL,
|
||||
abs_deadline, NULL) != 0) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->timeouts++;
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
}
|
||||
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->count++;
|
||||
if (s->use_cv) {
|
||||
nsync_cv_signal (&s->count_is_imod4[s->count&3]);
|
||||
}
|
||||
}
|
||||
s->refs--;
|
||||
if (s->refs == 0) {
|
||||
if (s->use_cv && s->reader_refs != 0) { /* wake any readers so they will exit */
|
||||
for (i = 0; i != 4; i++) {
|
||||
nsync_cv_broadcast (&s->count_is_imod4[i]);
|
||||
}
|
||||
}
|
||||
nsync_cv_signal (&s->refs_is_zero);
|
||||
}
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY3 (mu_stress_inc_loop, cv_stress_data *, condition_func, const void *)
|
||||
|
||||
|
||||
/* Acquire s.u in reader mode, and wait until a
|
||||
condition is true or a timeout occurs on a random wait between 0us and 999us, repeatedly.
|
||||
Every 16 times, release the reader lock, but immediately reacquire it.
|
||||
Once the count of threads running mu_stress_inc_loop() reaches zero (s.refs == 0),
|
||||
sum the number of loops completed into s.reader_loops, and
|
||||
the number of timeouts it experienced into s.timeouts.
|
||||
Then decrement s.reader_refs before returning. */
|
||||
static void mu_stress_reader_loop (cv_stress_data *s, condition_func condition,
|
||||
const void *condition_arg) {
|
||||
uintmax_t loops;
|
||||
uintmax_t timeouts = 0;
|
||||
nsync_mu_rlock (&s->mu);
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
loops = 0;
|
||||
while (s->refs != 0) {
|
||||
nsync_time abs_deadline;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
while (nsync_mu_wait_with_deadline (&s->mu, condition, condition_arg, NULL,
|
||||
abs_deadline, NULL) != 0) {
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
s->timeouts++;
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
abs_deadline = nsync_time_add (nsync_time_now (),
|
||||
nsync_time_us (rand () % STRESS_MAX_DELAY_MICROS));
|
||||
}
|
||||
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
loops++;
|
||||
if ((loops & 0xf) == 0) {
|
||||
nsync_mu_runlock (&s->mu);
|
||||
if ((loops & 0xfff) == 0) {
|
||||
nsync_time_sleep (nsync_time_ms (1));
|
||||
}
|
||||
nsync_mu_rlock (&s->mu);
|
||||
}
|
||||
}
|
||||
nsync_mu_rassert_held (&s->mu);
|
||||
nsync_mu_runlock (&s->mu);
|
||||
|
||||
nsync_mu_lock (&s->mu);
|
||||
s->reader_loops += loops;
|
||||
s->timeouts += timeouts;
|
||||
s->reader_refs--;
|
||||
if (s->reader_refs == 0) {
|
||||
nsync_cv_signal (&s->reader_refs_is_zero);
|
||||
}
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY3 (mu_stress_reader_loop, cv_stress_data *, condition_func, const void *)
|
||||
|
||||
static const condition_func is_n_mod_4[] = {
|
||||
&count_is0mod4,
|
||||
&count_is1mod4,
|
||||
&count_is2mod4,
|
||||
&count_is3mod4
|
||||
};
|
||||
static const condition_func is_n_mod_4_or_refs0[] = {
|
||||
&count_is0mod4or_refs_is0,
|
||||
&count_is1mod4or_refs_is0,
|
||||
&count_is2mod4or_refs_is0,
|
||||
&count_is3mod4or_refs_is0
|
||||
};
|
||||
|
||||
/* Create cv_threads_per_value threads using cv_stress_inc_loop(),
|
||||
and mu_threads_per_value threads using mu_stress_inc_loop(), all trying to
|
||||
increment s.count from 1 to 2 mod 4, plus the same from 2 to 3 mod 4, and
|
||||
again from 3 to 0 mod 4, using random delays in their waits. Sleep a few seconds,
|
||||
ensuring many random timeouts by these threads, because there is no thread
|
||||
incrementing s.count from 0 (which is 0 mod 4). Then create
|
||||
cv_threads_per_value threads using cv_stress_inc_loop(), and mu_threads_per_value
|
||||
threads using mu_stress_inc_loop(), all trying to increment s.count from 0 to 1
|
||||
mod 4. This allows all the threads to run to completion, since there are
|
||||
equal numbers for each condition.
|
||||
|
||||
At the same time, create cv_reader_threads_per_value threads using cv_stress_reader_loop
|
||||
and mu_reader_threads_per_value using mu_stress_reader_loop, for each of the transitions
|
||||
0 to 1, 1, to 2, 2 to 3, and 3 to 0 mod 4.
|
||||
|
||||
All these loops count how many timeouts they encounter. The reader loops
|
||||
count how many times they manage to run.
|
||||
|
||||
These counts are tested against expected values.
|
||||
|
||||
Finally, it waits for all threads to exit.
|
||||
|
||||
It returns whether the deadlie has expired. */
|
||||
static int run_stress_test (cv_stress_data *s, testing t,
|
||||
const char *test_name) {
|
||||
int done = 0;
|
||||
uintmax_t expected_timeouts;
|
||||
uintmax_t timeouts_seen;
|
||||
uintmax_t expected_count;
|
||||
uintmax_t i;
|
||||
static const int sleep_seconds = 1;
|
||||
|
||||
nsync_mu_lock (&s->mu);
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
/* Create threads trying to increment from 1, 2, and 3 mod 4.
|
||||
They will continually hit their timeouts because s.count==0 */
|
||||
for (i = 0; i != s->cv_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->refs++;
|
||||
closure_fork (closure_cv_stress_inc_loop (&cv_stress_inc_loop, s, 1));
|
||||
s->refs++;
|
||||
closure_fork (closure_cv_stress_inc_loop (&cv_stress_inc_loop, s, 2));
|
||||
s->refs++;
|
||||
closure_fork (closure_cv_stress_inc_loop (&cv_stress_inc_loop, s, 3));
|
||||
}
|
||||
for (i = 0; i != s->cv_reader_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_cv_stress_reader_loop (&cv_stress_reader_loop, s, 1));
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_cv_stress_reader_loop (&cv_stress_reader_loop, s, 2));
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_cv_stress_reader_loop (&cv_stress_reader_loop, s, 3));
|
||||
}
|
||||
for (i = 0; i != s->mu_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->refs++;
|
||||
closure_fork (closure_mu_stress_inc_loop (&mu_stress_inc_loop,
|
||||
s, is_n_mod_4[1], s));
|
||||
s->refs++;
|
||||
closure_fork (closure_mu_stress_inc_loop (&mu_stress_inc_loop,
|
||||
s, is_n_mod_4[2], s));
|
||||
s->refs++;
|
||||
closure_fork (closure_mu_stress_inc_loop (&mu_stress_inc_loop,
|
||||
s, is_n_mod_4[3], s));
|
||||
}
|
||||
for (i = 0; i != s->mu_reader_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_mu_stress_reader_loop (&mu_stress_reader_loop,
|
||||
s, is_n_mod_4_or_refs0[1], s));
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_mu_stress_reader_loop (&mu_stress_reader_loop,
|
||||
s, is_n_mod_4_or_refs0[2], s));
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_mu_stress_reader_loop (&mu_stress_reader_loop,
|
||||
s, is_n_mod_4_or_refs0[3], s));
|
||||
}
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
|
||||
/* Sleep a while to cause many timeouts. */
|
||||
nsync_time_sleep (nsync_time_ms (sleep_seconds * 1000));
|
||||
|
||||
nsync_mu_lock (&s->mu);
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
|
||||
/* Check that approximately the right number of timeouts have occurred.
|
||||
The 3 below is the three classes of thread produced before the Sleep().
|
||||
The factor of 1/8 is to allow for randomness and slow test machines. */
|
||||
expected_timeouts = (s->cv_threads_per_value + s->mu_threads_per_value) * 3 *
|
||||
sleep_seconds * STRESS_EXPECT_TIMEOUTS_PER_SEC / 8;
|
||||
timeouts_seen = s->timeouts;
|
||||
/* actual check is below. */
|
||||
|
||||
/* Create the threads that increment from 0 mod 4. s.count will then be incremented. */
|
||||
for (i = 0; i != s->cv_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->refs++;
|
||||
closure_fork (closure_cv_stress_inc_loop (&cv_stress_inc_loop, s, 0));
|
||||
}
|
||||
for (i = 0; i != s->cv_reader_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_cv_stress_reader_loop (&cv_stress_reader_loop, s, 0));
|
||||
}
|
||||
for (i = 0; i != s->mu_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->refs++;
|
||||
closure_fork (closure_mu_stress_inc_loop (&mu_stress_inc_loop,
|
||||
s, is_n_mod_4[0], s));
|
||||
}
|
||||
for (i = 0; i != s->mu_reader_threads_per_value; i++) {
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
s->reader_refs++;
|
||||
closure_fork (closure_mu_stress_reader_loop (&mu_stress_reader_loop,
|
||||
s, is_n_mod_4_or_refs0[0], s));
|
||||
}
|
||||
|
||||
/* wait for threads to exit. */
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
while (s->refs != 0) {
|
||||
nsync_cv_wait (&s->refs_is_zero, &s->mu);
|
||||
}
|
||||
while (s->reader_refs != 0) {
|
||||
nsync_cv_wait (&s->reader_refs_is_zero, &s->mu);
|
||||
}
|
||||
|
||||
nsync_mu_assert_held (&s->mu);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
|
||||
if (nsync_time_cmp (s->deadline, nsync_time_now ()) < 0) {
|
||||
if (timeouts_seen < expected_timeouts && !testing_is_uniprocessor (t)) {
|
||||
TEST_ERROR (t, ("%s: expected more than %d timeouts, got %d",
|
||||
test_name, expected_timeouts, timeouts_seen));
|
||||
}
|
||||
|
||||
/* Check that s.count has the right value. */
|
||||
expected_count = s->loop_count * (s->cv_threads_per_value +
|
||||
s->mu_threads_per_value) * 4;
|
||||
if (s->count != expected_count) {
|
||||
TEST_ERROR (t, ("%s: expected to increment s->count to %d, got %d",
|
||||
test_name, expected_count, s->count));
|
||||
}
|
||||
|
||||
/* Some timeouts should have happened while the counts were being incremented. */
|
||||
expected_timeouts = timeouts_seen + 1;
|
||||
if (s->timeouts < expected_timeouts) {
|
||||
TEST_ERROR (t, ("%s: expected more than %d timeouts, got %d",
|
||||
test_name, expected_timeouts, s->timeouts));
|
||||
}
|
||||
done = 1;
|
||||
}
|
||||
return (done);
|
||||
}
|
||||
|
||||
/* Test many threads using a single lock, using
|
||||
condition variables and timeouts. See run_stress_test() for details. */
|
||||
static void test_cv_timeout_stress (testing t) {
|
||||
uintmax_t loop_count = 3;
|
||||
cv_stress_data s;
|
||||
nsync_time deadline;
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (5000));
|
||||
do {
|
||||
memset ((void *) &s, 0, sizeof (s));
|
||||
s.loop_count = loop_count;
|
||||
s.cv_threads_per_value = 4;
|
||||
s.cv_reader_threads_per_value = 2;
|
||||
s.mu_threads_per_value = 0;
|
||||
s.mu_reader_threads_per_value = 0;
|
||||
s.deadline = deadline;
|
||||
loop_count *= 2;
|
||||
} while (!run_stress_test (&s, t, "test_cv_timeout_stress"));
|
||||
}
|
||||
|
||||
|
||||
/* Test many threads using a single lock, using
|
||||
conditional critical sections and timeouts. See run_stress_test() for details. */
|
||||
static void test_mu_timeout_stress (testing t) {
|
||||
uintmax_t loop_count = 3;
|
||||
cv_stress_data s;
|
||||
nsync_time deadline;
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (5000));
|
||||
do {
|
||||
memset ((void *) &s, 0, sizeof (s));
|
||||
s.loop_count = loop_count;
|
||||
s.cv_threads_per_value = 0;
|
||||
s.cv_reader_threads_per_value = 0;
|
||||
s.mu_threads_per_value = 4;
|
||||
s.mu_reader_threads_per_value = 2;
|
||||
s.deadline = deadline;
|
||||
loop_count *= 2;
|
||||
} while (!run_stress_test (&s, t, "test_mu_timeout_stress"));
|
||||
}
|
||||
|
||||
/* Like test_mu_timeout_stress() but has both threads that use conditional
|
||||
critical sections and threads that use condition variables, to ensure that
|
||||
they work together. */
|
||||
static void test_mu_cv_timeout_stress (testing t) {
|
||||
uintmax_t loop_count = 3;
|
||||
cv_stress_data s;
|
||||
nsync_time deadline;
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (5000));
|
||||
do {
|
||||
memset ((void *) &s, 0, sizeof (s));
|
||||
s.loop_count = loop_count;
|
||||
s.cv_threads_per_value = 4;
|
||||
s.cv_reader_threads_per_value = 1;
|
||||
s.mu_threads_per_value = 4;
|
||||
s.mu_reader_threads_per_value = 1;
|
||||
s.deadline = deadline;
|
||||
loop_count *= 2;
|
||||
} while (!run_stress_test (&s, t, "test_mu_cv_timeout_stress"));
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_cv_timeout_stress);
|
||||
TEST_RUN (tb, test_mu_timeout_stress);
|
||||
TEST_RUN (tb, test_mu_cv_timeout_stress);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
794
third_party/nsync/testing/cv_test.c
vendored
Normal file
794
third_party/nsync/testing/cv_test.c
vendored
Normal file
|
@ -0,0 +1,794 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/fmt.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/cv.h"
|
||||
#include "third_party/nsync/debug.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/note.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
// clang-format off
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
/* A cv_queue represents a FIFO queue with up to limit elements.
|
||||
The storage for the queue expands as necessary up to limit. */
|
||||
typedef struct cv_queue_s {
|
||||
int limit; /* max value of count---should not be changed after initialization */
|
||||
nsync_cv non_empty; /* signalled when count transitions from zero to non-zero */
|
||||
nsync_cv non_full; /* signalled when count transitions from limit to less than limit */
|
||||
nsync_mu mu; /* protects fields below */
|
||||
int pos; /* index of first in-use element */
|
||||
int count; /* number of elements in use */
|
||||
void *data[1]; /* in use elements are data[pos, ..., (pos+count-1)%limit] */
|
||||
} cv_queue;
|
||||
|
||||
/* Return a pointer to new cv_queue. */
|
||||
static cv_queue *cv_queue_new (int limit) {
|
||||
cv_queue *q;
|
||||
int size = offsetof (struct cv_queue_s, data) + sizeof (q->data[0]) * limit;
|
||||
q = (cv_queue *) malloc (size);
|
||||
memset ((void *) q, 0, size);
|
||||
q->limit = limit;
|
||||
return (q);
|
||||
}
|
||||
|
||||
/* Add v to the end of the FIFO *q and return non-zero, or if the FIFO already
|
||||
has limit elements and continues to do so until abs_deadline, do nothing and
|
||||
return 0. */
|
||||
static int cv_queue_put (cv_queue *q, void *v, nsync_time abs_deadline) {
|
||||
int added = 0;
|
||||
int wake = 0;
|
||||
nsync_mu_lock (&q->mu);
|
||||
while (q->count == q->limit &&
|
||||
nsync_cv_wait_with_deadline (&q->non_full, &q->mu, abs_deadline, NULL) == 0) {
|
||||
}
|
||||
if (q->count != q->limit) {
|
||||
int i = q->pos + q->count;
|
||||
if (q->limit <= i) {
|
||||
i -= q->limit;
|
||||
}
|
||||
q->data[i] = v;
|
||||
if (q->count == 0) {
|
||||
wake = 1;
|
||||
}
|
||||
q->count++;
|
||||
added = 1;
|
||||
}
|
||||
nsync_mu_unlock (&q->mu);
|
||||
if (wake) {
|
||||
nsync_cv_broadcast (&q->non_empty);
|
||||
}
|
||||
return (added);
|
||||
}
|
||||
|
||||
/* Remove the first value from the front of the FIFO *q and return it,
|
||||
or if the FIFO is empty and continues to be so until abs_deadline,
|
||||
do nothing and return NULL. */
|
||||
static void *cv_queue_get (cv_queue *q, nsync_time abs_deadline) {
|
||||
void *v = NULL;
|
||||
nsync_mu_lock (&q->mu);
|
||||
while (q->count == 0 &&
|
||||
nsync_cv_wait_with_deadline (&q->non_empty, &q->mu, abs_deadline, NULL) == 0) {
|
||||
}
|
||||
if (q->count != 0) {
|
||||
v = q->data[q->pos];
|
||||
q->data[q->pos] = NULL;
|
||||
if (q->count == q->limit) {
|
||||
nsync_cv_broadcast (&q->non_full);
|
||||
}
|
||||
q->pos++;
|
||||
q->count--;
|
||||
if (q->pos == q->limit) {
|
||||
q->pos = 0;
|
||||
}
|
||||
}
|
||||
nsync_mu_unlock (&q->mu);
|
||||
return (v);
|
||||
}
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
static char ptr_to_int_c;
|
||||
#define INT_TO_PTR(x) ((x) + &ptr_to_int_c)
|
||||
#define PTR_TO_INT(p) (((char *) (p)) - &ptr_to_int_c)
|
||||
|
||||
/* Put count integers on *q, in the sequence start*3, (start+1)*3, (start+2)*3, .... */
|
||||
static void producer_cv_n (testing t, cv_queue *q, int start, int count) {
|
||||
int i;
|
||||
for (i = 0; i != count; i++) {
|
||||
if (!cv_queue_put (q, INT_TO_PTR ((start+i)*3), nsync_time_no_deadline)) {
|
||||
TEST_FATAL (t, ("cv_queue_put() returned 0 with no deadline"));
|
||||
}
|
||||
}
|
||||
}
|
||||
CLOSURE_DECL_BODY4 (producer_cv_n, testing, cv_queue *, int, int)
|
||||
|
||||
/* Get count integers from *q, and check that they are in the
|
||||
sequence start*3, (start+1)*3, (start+2)*3, .... */
|
||||
static void consumer_cv_n (testing t, cv_queue *q, int start, int count) {
|
||||
int i;
|
||||
for (i = 0; i != count; i++) {
|
||||
void *v = cv_queue_get (q, nsync_time_no_deadline);
|
||||
int x;
|
||||
if (v == NULL) {
|
||||
TEST_FATAL (t, ("cv_queue_get() returned NULL with no deadline"));
|
||||
}
|
||||
x = PTR_TO_INT (v);
|
||||
if (x != (start+i)*3) {
|
||||
TEST_FATAL (t, ("cv_queue_get() returned bad value; want %d, got %d",
|
||||
(start+i)*3, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* CV_PRODUCER_CONSUMER_N is the number of elements passed from producer to consumer in the
|
||||
test_cv_producer_consumer*() tests below. */
|
||||
#define CV_PRODUCER_CONSUMER_N 100000
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**0. */
|
||||
static void test_cv_producer_consumer0 (testing t) {
|
||||
cv_queue *q = cv_queue_new (1);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**1. */
|
||||
static void test_cv_producer_consumer1 (testing t) {
|
||||
cv_queue *q = cv_queue_new (10);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**2. */
|
||||
static void test_cv_producer_consumer2 (testing t) {
|
||||
cv_queue *q = cv_queue_new (100);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**3. */
|
||||
static void test_cv_producer_consumer3 (testing t) {
|
||||
cv_queue *q = cv_queue_new (1000);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**4. */
|
||||
static void test_cv_producer_consumer4 (testing t) {
|
||||
cv_queue *q = cv_queue_new (10 * 1000);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**5. */
|
||||
static void test_cv_producer_consumer5 (testing t) {
|
||||
cv_queue *q = cv_queue_new (100 * 1000);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**6. */
|
||||
static void test_cv_producer_consumer6 (testing t) {
|
||||
cv_queue *q = cv_queue_new (1000 * 1000);
|
||||
closure_fork (closure_producer_cv_n (&producer_cv_n, t, q, 0, CV_PRODUCER_CONSUMER_N));
|
||||
consumer_cv_n (t, q, 0, CV_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* The following values control how aggressively we police the timeout. */
|
||||
#define TOO_EARLY_MS 1
|
||||
#define TOO_LATE_MS 100 /* longer, to accommodate scheduling delays */
|
||||
#define TOO_LATE_ALLOWED 25 /* number of iterations permitted to violate too_late */
|
||||
|
||||
/* Check timeouts on a CV wait_with_deadline(). */
|
||||
static void test_cv_deadline (testing t) {
|
||||
int too_late_violations;
|
||||
nsync_mu mu;
|
||||
nsync_cv cv;
|
||||
int i;
|
||||
nsync_time too_early;
|
||||
nsync_time too_late;
|
||||
|
||||
nsync_mu_init (&mu);
|
||||
nsync_cv_init (&cv);
|
||||
too_early = nsync_time_ms (TOO_EARLY_MS);
|
||||
too_late = nsync_time_ms (TOO_LATE_MS);
|
||||
too_late_violations = 0;
|
||||
nsync_mu_lock (&mu);
|
||||
for (i = 0; i != 50; i++) {
|
||||
nsync_time end_time;
|
||||
nsync_time start_time;
|
||||
nsync_time expected_end_time;
|
||||
start_time = nsync_time_now ();
|
||||
expected_end_time = nsync_time_add (start_time, nsync_time_ms (87));
|
||||
if (nsync_cv_wait_with_deadline (&cv, &mu, expected_end_time,
|
||||
NULL) != ETIMEDOUT) {
|
||||
TEST_FATAL (t, ("nsync_cv_wait() returned non-expired for a timeout"));
|
||||
}
|
||||
end_time = nsync_time_now ();
|
||||
if (nsync_time_cmp (end_time, nsync_time_sub (expected_end_time, too_early)) < 0) {
|
||||
char *elapsed_str = nsync_time_str (nsync_time_sub (expected_end_time, end_time), 2);
|
||||
TEST_ERROR (t, ("nsync_cv_wait() returned %s too early", elapsed_str));
|
||||
free (elapsed_str);
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_add (expected_end_time, too_late), end_time) < 0) {
|
||||
too_late_violations++;
|
||||
}
|
||||
}
|
||||
nsync_mu_unlock (&mu);
|
||||
if (too_late_violations > TOO_LATE_ALLOWED) {
|
||||
TEST_ERROR (t, ("nsync_cv_wait() returned too late %d times", too_late_violations));
|
||||
}
|
||||
}
|
||||
|
||||
/* Check cancellations with nsync_cv_wait_with_deadline(). */
|
||||
static void test_cv_cancel (testing t) {
|
||||
nsync_time future_time;
|
||||
int too_late_violations;
|
||||
nsync_mu mu;
|
||||
nsync_cv cv;
|
||||
int i;
|
||||
nsync_time too_early;
|
||||
nsync_time too_late;
|
||||
|
||||
nsync_mu_init (&mu);
|
||||
nsync_cv_init (&cv);
|
||||
too_early = nsync_time_ms (TOO_EARLY_MS);
|
||||
too_late = nsync_time_ms (TOO_LATE_MS);
|
||||
|
||||
/* The loops below cancel after 87 milliseconds, like the timeout tests above. */
|
||||
|
||||
future_time = nsync_time_add (nsync_time_now (), nsync_time_ms (3600000)); /* test cancels with timeout */
|
||||
|
||||
too_late_violations = 0;
|
||||
nsync_mu_lock (&mu);
|
||||
for (i = 0; i != 50; i++) {
|
||||
int x;
|
||||
nsync_note cancel;
|
||||
nsync_time end_time;
|
||||
nsync_time start_time;
|
||||
nsync_time expected_end_time;
|
||||
start_time = nsync_time_now ();
|
||||
expected_end_time = nsync_time_add (start_time, nsync_time_ms (87));
|
||||
|
||||
cancel = nsync_note_new (NULL, expected_end_time);
|
||||
|
||||
x = nsync_cv_wait_with_deadline (&cv, &mu, future_time, cancel);
|
||||
if (x != ECANCELED) {
|
||||
TEST_FATAL (t, ("nsync_cv_wait() returned non-cancelled (%d) for "
|
||||
"a cancellation; expected %d",
|
||||
x, ECANCELED));
|
||||
}
|
||||
end_time = nsync_time_now ();
|
||||
if (nsync_time_cmp (end_time, nsync_time_sub (expected_end_time, too_early)) < 0) {
|
||||
char *elapsed_str = nsync_time_str (nsync_time_sub (expected_end_time, end_time), 2);
|
||||
TEST_ERROR (t, ("nsync_cv_wait() returned %s too early", elapsed_str));
|
||||
free (elapsed_str);
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_add (expected_end_time, too_late), end_time) < 0) {
|
||||
too_late_violations++;
|
||||
}
|
||||
|
||||
/* Check that an already cancelled wait returns immediately. */
|
||||
start_time = nsync_time_now ();
|
||||
|
||||
x = nsync_cv_wait_with_deadline (&cv, &mu, nsync_time_no_deadline, cancel);
|
||||
if (x != ECANCELED) {
|
||||
TEST_FATAL (t, ("nsync_cv_wait() returned non-cancelled (%d) for "
|
||||
"a cancellation; expected %d",
|
||||
x, ECANCELED));
|
||||
}
|
||||
end_time = nsync_time_now ();
|
||||
if (nsync_time_cmp (end_time, start_time) < 0) {
|
||||
char *elapsed_str = nsync_time_str (nsync_time_sub (expected_end_time, end_time), 2);
|
||||
TEST_ERROR (t, ("nsync_cv_wait() returned %s too early", elapsed_str));
|
||||
free (elapsed_str);
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_add (start_time, too_late), end_time) < 0) {
|
||||
too_late_violations++;
|
||||
}
|
||||
nsync_note_notify (cancel);
|
||||
|
||||
nsync_note_free (cancel);
|
||||
}
|
||||
nsync_mu_unlock (&mu);
|
||||
if (too_late_violations > TOO_LATE_ALLOWED) {
|
||||
TEST_ERROR (t, ("nsync_cv_wait() returned too late %d times", too_late_violations));
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
/* Names of debug results for test_cv_debug. */
|
||||
static const char *result_name[] = {
|
||||
"init_mu0",
|
||||
"init_cv0",
|
||||
"init_mu1",
|
||||
"init_cv1",
|
||||
"init_mu2",
|
||||
"init_cv2",
|
||||
"held_mu",
|
||||
"wait0_mu",
|
||||
"wait0_cv",
|
||||
"wait1_mu",
|
||||
"wait1_cv",
|
||||
"wait2_mu",
|
||||
"wait2_cv",
|
||||
"wait3_mu",
|
||||
"wait3_cv",
|
||||
"rheld1_mu",
|
||||
"rheld2_mu",
|
||||
"rheld1again_mu",
|
||||
NULL /* sentinel */
|
||||
};
|
||||
|
||||
/* state for test_cv_debug() */
|
||||
struct debug_state {
|
||||
nsync_mu mu; /* protects flag field */
|
||||
nsync_cv cv; /* signalled when flag becomes zero */
|
||||
int flag; /* 0 => threads proceed; non-zero => threads block */
|
||||
|
||||
/* result[] is an array of nul-terminated string values, accessed via
|
||||
name (in result_name[]) via slot(). Entries accessed from multiple
|
||||
threads are protected by result_mu. */
|
||||
char *result[sizeof (result_name) / sizeof (result_name[0])];
|
||||
nsync_mu result_mu;
|
||||
};
|
||||
|
||||
/* Return a pointer to the slot in s->result[] associated with the
|
||||
nul-terminated name[] */
|
||||
static char **slot (struct debug_state *s, const char *name) {
|
||||
int i = 0;
|
||||
while (result_name[i] != NULL && strcmp (result_name[i], name) != 0) {
|
||||
i++;
|
||||
}
|
||||
if (result_name[i] == NULL) { /* caller gave non-existent name */
|
||||
abort ();
|
||||
}
|
||||
return (&s->result[i]);
|
||||
}
|
||||
|
||||
/* Check that the strings associated with nul-terminated strings name0[] and
|
||||
name1[] have the same values in s->result[]. */
|
||||
static void check_same (testing t, struct debug_state *s,
|
||||
const char *name0, const char *name1) {
|
||||
if (strcmp (*slot (s, name0), *slot (s, name1)) != 0) {
|
||||
TEST_ERROR (t, ("nsync_mu_debug_state() %s state != %s state (%s vs. %s)",
|
||||
name0, name1, *slot (s, name0), *slot (s, name1)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that the strings associated with nul-terminated strings name0[] and
|
||||
name1[] have different values in s->result[]. */
|
||||
static void check_different (testing t, struct debug_state *s,
|
||||
const char *name0, const char *name1) {
|
||||
if (strcmp (*slot (s, name0), *slot (s, name1)) == 0) {
|
||||
TEST_ERROR (t, ("nsync_mu_debug_state() %s state == %s state",
|
||||
name0, name1));
|
||||
}
|
||||
}
|
||||
|
||||
/* Return whether the integer at address v is zero. */
|
||||
static int int_is_zero (const void *v) {
|
||||
return (*(int *)v == 0);
|
||||
}
|
||||
|
||||
/* Acquire and release s->mu in write mode, waiting for s->flag==0
|
||||
using nsync_mu_wait(). */
|
||||
static void debug_thread_writer (struct debug_state *s) {
|
||||
nsync_mu_lock (&s->mu);
|
||||
nsync_mu_wait (&s->mu, &int_is_zero, &s->flag, NULL);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
}
|
||||
|
||||
/* Acquire and release s->mu in write mode, waiting for s->flag==0
|
||||
using nsync_cv_wait(). */
|
||||
static void debug_thread_writer_cv (struct debug_state *s) {
|
||||
nsync_mu_lock (&s->mu);
|
||||
while (s->flag != 0) {
|
||||
nsync_cv_wait (&s->cv, &s->mu);
|
||||
}
|
||||
nsync_mu_unlock (&s->mu);
|
||||
}
|
||||
|
||||
/* Acquire and release s->mu in read mode, waiting for s->flag==0
|
||||
using nsync_mu_wait().
|
||||
If name!=NULL, record state of s->mu while held using name[]. */
|
||||
static void debug_thread_reader (struct debug_state *s,
|
||||
const char *name) {
|
||||
nsync_mu_rlock (&s->mu);
|
||||
nsync_mu_wait (&s->mu, &int_is_zero, &s->flag, NULL);
|
||||
if (name != NULL) {
|
||||
int len = 1024;
|
||||
nsync_mu_lock (&s->result_mu);
|
||||
*slot (s, name) = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
nsync_mu_unlock (&s->result_mu);
|
||||
}
|
||||
nsync_mu_runlock (&s->mu);
|
||||
}
|
||||
|
||||
/* Acquire and release s->mu in read mode, waiting for s->flag==0
|
||||
using nsync_cv_wait().
|
||||
If name!=NULL, record state of s->mu while held using name[]. */
|
||||
static void debug_thread_reader_cv (struct debug_state *s,
|
||||
const char *name) {
|
||||
nsync_mu_rlock (&s->mu);
|
||||
while (s->flag != 0) {
|
||||
nsync_cv_wait (&s->cv, &s->mu);
|
||||
}
|
||||
if (name != NULL) {
|
||||
int len = 1024;
|
||||
nsync_mu_lock (&s->result_mu);
|
||||
*slot (s, name) = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
nsync_mu_unlock (&s->result_mu);
|
||||
}
|
||||
nsync_mu_runlock (&s->mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY1 (debug_thread, struct debug_state *)
|
||||
CLOSURE_DECL_BODY2 (debug_thread_reader, struct debug_state *, const char *)
|
||||
|
||||
/* Check that nsync_mu_debug_state() and nsync_cv_debug_state()
|
||||
and their variants yield reasonable results.
|
||||
|
||||
The specification of those routines is intentionally loose,
|
||||
so this do not check much, but the various possibilities can be
|
||||
examined using the verbose testing flag (-v). */
|
||||
static void test_cv_debug (testing t) {
|
||||
int i;
|
||||
int len = 1024;
|
||||
char *tmp;
|
||||
char *buf;
|
||||
int buflen;
|
||||
struct debug_state xs;
|
||||
struct debug_state *s = &xs;
|
||||
memset ((void *) s, 0, sizeof (*s));
|
||||
|
||||
/* Use nsync_*_debugger to check that they work. */
|
||||
tmp = nsync_mu_debugger (&s->mu);
|
||||
buflen = strlen (tmp)+1;
|
||||
buf = (char *) malloc (buflen);
|
||||
snprintf (buf, buflen, "%s", tmp);
|
||||
*slot (s, "init_mu0") = buf;
|
||||
|
||||
tmp = nsync_cv_debugger (&s->cv);
|
||||
buflen = strlen (tmp)+1;
|
||||
buf = (char *) malloc (buflen);
|
||||
snprintf (buf, buflen, "%s", tmp);
|
||||
*slot (s, "init_cv0") = buf;
|
||||
|
||||
/* Get the same information via the other routines */
|
||||
*slot (s, "init_mu1") = nsync_mu_debug_state (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
*slot (s, "init_cv1") = nsync_cv_debug_state (
|
||||
&s->cv, (char *) malloc (len), len);
|
||||
*slot (s, "init_mu2") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
*slot (s, "init_cv2") = nsync_cv_debug_state_and_waiters (
|
||||
&s->cv, (char *) malloc (len), len);
|
||||
|
||||
nsync_mu_lock (&s->mu);
|
||||
*slot (s, "held_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
nsync_mu_unlock (&s->mu);
|
||||
|
||||
/* set up several threads waiting on the mutex */
|
||||
nsync_mu_lock (&s->mu);
|
||||
s->flag = 1; /* so thread will block on conditions */
|
||||
closure_fork (closure_debug_thread (&debug_thread_writer, s));
|
||||
closure_fork (closure_debug_thread (&debug_thread_writer, s));
|
||||
closure_fork (closure_debug_thread (&debug_thread_writer, s));
|
||||
closure_fork (closure_debug_thread_reader (&debug_thread_reader, s, NULL));
|
||||
closure_fork (closure_debug_thread (&debug_thread_writer_cv, s));
|
||||
closure_fork (closure_debug_thread (&debug_thread_writer_cv, s));
|
||||
closure_fork (closure_debug_thread (&debug_thread_writer_cv, s));
|
||||
closure_fork (closure_debug_thread_reader (&debug_thread_reader_cv, s, NULL));
|
||||
nsync_time_sleep (nsync_time_ms (500));
|
||||
*slot (s, "wait0_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
*slot (s, "wait0_cv") = nsync_cv_debug_state_and_waiters (
|
||||
&s->cv, (char *) malloc (len), len);
|
||||
|
||||
/* allow the threads to proceed to their conditional waits */
|
||||
nsync_mu_unlock (&s->mu);
|
||||
nsync_time_sleep (nsync_time_ms (500));
|
||||
*slot (s, "wait1_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
*slot (s, "wait1_cv") = nsync_cv_debug_state_and_waiters (
|
||||
&s->cv, (char *) malloc (len), len);
|
||||
|
||||
nsync_mu_lock (&s->mu);
|
||||
/* move cv waiters to mutex queue */
|
||||
nsync_cv_broadcast (&s->cv);
|
||||
*slot (s, "wait2_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
*slot (s, "wait2_cv") = nsync_cv_debug_state_and_waiters (
|
||||
&s->cv, (char *) malloc (len), len);
|
||||
|
||||
/* allow all threads to proceed and exit */
|
||||
s->flag = 0;
|
||||
nsync_mu_unlock (&s->mu);
|
||||
nsync_time_sleep (nsync_time_ms (500));
|
||||
*slot (s, "wait3_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
*slot (s, "wait3_cv") = nsync_cv_debug_state_and_waiters (
|
||||
&s->cv, (char *) malloc (len), len);
|
||||
|
||||
/* Test with more than one reader */
|
||||
nsync_mu_rlock (&s->mu);
|
||||
*slot (s, "rheld1_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
closure_fork (closure_debug_thread_reader (
|
||||
&debug_thread_reader, s, "rheld2_mu"));
|
||||
nsync_time_sleep (nsync_time_ms (500));
|
||||
*slot (s, "rheld1again_mu") = nsync_mu_debug_state_and_waiters (
|
||||
&s->mu, (char *) malloc (len), len);
|
||||
nsync_mu_runlock (&s->mu);
|
||||
|
||||
check_same (t, s, "init_mu0", "init_mu1");
|
||||
check_same (t, s, "init_mu0", "init_mu2");
|
||||
check_same (t, s, "init_cv0", "init_cv1");
|
||||
check_same (t, s, "init_cv0", "init_cv2");
|
||||
check_different (t, s, "init_mu0", "held_mu");
|
||||
check_different (t, s, "rheld1_mu", "held_mu");
|
||||
/* Must acquire result_mu, because the "rheld2_mu" slot is accessed
|
||||
from the debug_thread_reader() thread created above. */
|
||||
nsync_mu_lock (&s->result_mu);
|
||||
check_different (t, s, "rheld1_mu", "rheld2_mu");
|
||||
nsync_mu_unlock (&s->result_mu);
|
||||
check_different (t, s, "init_mu0", "init_cv0");
|
||||
|
||||
for (i = 0; result_name[i] != NULL; i++) {
|
||||
if (testing_verbose (t)) {
|
||||
const char *str = *slot (s, result_name[i]);
|
||||
TEST_LOG (t, ("%-16s %s\n", result_name[i], str));
|
||||
}
|
||||
if (strlen (s->result[i]) == 0) {
|
||||
TEST_ERROR (t, ("nsync_mu_debug_state() %s empty",
|
||||
result_name[i]));
|
||||
}
|
||||
free (s->result[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
/* Max number of waiter threads used in transfer test.
|
||||
The last uses a conditional critical section, and others
|
||||
use a condition variable. */
|
||||
#define TRANSFER_MAX_WAITERS 8
|
||||
|
||||
/* A struct cv_transfer is used to test cv-to-mu thread transfer.
|
||||
There are up to TRANSFER_MAX_WAITERS waiter threads, and a wakeup thread.
|
||||
Some threads wait using conditional critical sections,
|
||||
and others using a condition variable. */
|
||||
struct cv_transfer {
|
||||
nsync_mu mu;
|
||||
|
||||
nsync_cv cv; /* signalled each time a cond[] element becomes non-zero */
|
||||
/* Thread i waits for cond[i] to be non-zero; under mu. */
|
||||
int cond[TRANSFER_MAX_WAITERS];
|
||||
|
||||
nsync_mu control_mu; /* protects fields below */
|
||||
nsync_cv done_cv; /* signalled each time an element of done[] becomes non-zero */
|
||||
int ready[TRANSFER_MAX_WAITERS]; /* set by waiters as they wait */
|
||||
int done[TRANSFER_MAX_WAITERS]; /* set by completed waiters: to 1 by readers, and to 2 by writers */
|
||||
};
|
||||
|
||||
/* Return whether *(int *)v != 0. Used as a condition for nsync_mu_wait(). */
|
||||
static int int_is_non_zero (const void *v) {
|
||||
return (0 != *(const int *)v);
|
||||
}
|
||||
|
||||
/* Return when *pi becomes non-zero, where *pi is protected by *mu.
|
||||
Acquires and releases *mu. */
|
||||
static void transfer_await_nonzero (nsync_mu *mu, int *pi) {
|
||||
nsync_mu_lock (mu);
|
||||
nsync_mu_wait (mu, &int_is_non_zero, pi, NULL);
|
||||
nsync_mu_unlock (mu);
|
||||
}
|
||||
|
||||
/* Set *pi to x value, where *pi is protected by *mu.
|
||||
Acquires and releases *mu. */
|
||||
static void transfer_set (nsync_mu *mu, int *pi, int x) {
|
||||
nsync_mu_lock (mu);
|
||||
*pi = x;
|
||||
nsync_mu_unlock (mu);
|
||||
}
|
||||
|
||||
/* Lock and unlock routines for writers (index 0), and readers (index 1). */
|
||||
static const struct {
|
||||
void (*lock) (nsync_mu *);
|
||||
void (*unlock) (nsync_mu *);
|
||||
} lock_type[2] = {
|
||||
{ &nsync_mu_lock, &nsync_mu_unlock },
|
||||
{ &nsync_mu_rlock, &nsync_mu_runlock },
|
||||
};
|
||||
|
||||
/* Signal and broadcast routines */
|
||||
typedef void (*wakeup_func_type) (nsync_cv *);
|
||||
static wakeup_func_type wakeup_func[2] = { &nsync_cv_broadcast, &nsync_cv_signal };
|
||||
|
||||
/* Acquire cvt->mu in write or read mode (depending on "reader"),
|
||||
set cvt->ready[i], wait for cvt->cond[i] to become non-zero (using
|
||||
a condition variable if use_cv!=0), then release cvt->mu, and
|
||||
set cvt->done[i].
|
||||
Used as the body of waiter threads created by test_cv_transfer(). */
|
||||
static void transfer_waiter_thread (struct cv_transfer *cvt, int i, int reader, int use_cv) {
|
||||
(*lock_type[reader].lock) (&cvt->mu);
|
||||
transfer_set (&cvt->control_mu, &cvt->ready[i], 1);
|
||||
if (use_cv) {
|
||||
while (!cvt->cond[i]) {
|
||||
nsync_cv_wait (&cvt->cv, &cvt->mu);
|
||||
}
|
||||
} else {
|
||||
nsync_mu_wait (&cvt->mu, &int_is_non_zero, &cvt->cond[i], NULL);
|
||||
}
|
||||
(*lock_type[reader].unlock) (&cvt->mu);
|
||||
|
||||
transfer_set (&cvt->control_mu, &cvt->done[i], reader? 1 : 2);
|
||||
nsync_cv_broadcast (&cvt->done_cv);
|
||||
}
|
||||
|
||||
/* Return whether all the elements a[0..n-1] are less than x. */
|
||||
static int are_all_below (int a[], int n, int x) {
|
||||
int i;
|
||||
for (i = 0; i != n && a[i] < x; i++) {
|
||||
}
|
||||
return (i == n);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY4 (transfer_thread, struct cv_transfer *, int, int, int)
|
||||
|
||||
/* Test cv-to-mutex queue transfer. (See the code in cv.c, wake_waiters().)
|
||||
|
||||
The queue transfer needs to work regardless of:
|
||||
- whether the mutex is also being used with conditional critical sections,
|
||||
- whether reader locks are used,
|
||||
- whether the waker signals from within the critical section (as it would in
|
||||
a traditional monitor), or after that critical section, and
|
||||
- the number of threads that might be awoken. */
|
||||
static void test_cv_transfer (testing t) {
|
||||
int waiters; /* number of waiters (in [2, TRANSFER_MAX_WAITERS]). */
|
||||
int cv_writers; /* number of cv_writers: -1 means all */
|
||||
int ccs_reader; /* ccs waiter is a reader */
|
||||
int wakeup_type; /* bits: use_signal and after_region */
|
||||
enum { use_signal = 0x1 }; /* use signal rather than broadcast */
|
||||
enum { after_region = 0x2 }; /* perform wakeup after region, rather than within */
|
||||
struct cv_transfer Xcvt;
|
||||
struct cv_transfer *cvt = &Xcvt; /* So all accesses are of form cvt-> */
|
||||
int i;
|
||||
|
||||
/* for all settings of all of wakeup_type, ccs_reader, cv_writers,
|
||||
and various different numbers of waiters */
|
||||
for (waiters = 2; waiters <= TRANSFER_MAX_WAITERS; waiters <<= 1) {
|
||||
for (wakeup_type = 0; wakeup_type != 4; wakeup_type++) {
|
||||
for (cv_writers = -1; cv_writers != 3; cv_writers++) {
|
||||
for (ccs_reader = 0; ccs_reader != 2; ccs_reader++) {
|
||||
if (testing_verbose (t)) {
|
||||
TEST_LOG (t, ("transfer waiters %d wakeup_type %d cv_writers %d ccs_reader %d\n",
|
||||
waiters, wakeup_type, cv_writers, ccs_reader));
|
||||
}
|
||||
memset ((void *) cvt, 0, sizeof (*cvt));
|
||||
|
||||
/* Start the waiter threads that use condition variables. */
|
||||
for (i = 0; i < waiters-1; i++) {
|
||||
int is_reader = (cv_writers != -1 && i < waiters-1-cv_writers);
|
||||
closure_fork (closure_transfer_thread (&transfer_waiter_thread, cvt, i,
|
||||
is_reader, 1/*use_cv*/));
|
||||
transfer_await_nonzero (&cvt->control_mu, &cvt->ready[i]);
|
||||
}
|
||||
/* Start the waiter thread that uses conditional critical sections. */
|
||||
closure_fork (closure_transfer_thread (&transfer_waiter_thread, cvt, i,
|
||||
ccs_reader, 0/*use_cv*/));
|
||||
/* Wait for all waiters to enter their regions. */
|
||||
for (i = 0; i != waiters; i++) {
|
||||
transfer_await_nonzero (&cvt->control_mu, &cvt->ready[i]);
|
||||
}
|
||||
|
||||
nsync_mu_lock (&cvt->mu);
|
||||
/* At this point, all the waiter threads are in waiting:
|
||||
they have set their ready[] flags, and have released cvt->mu. */
|
||||
|
||||
/* Mark all the condition-variable as runnable,
|
||||
and signal at least one of them.
|
||||
This may wake more than one, depending on
|
||||
the presence of readers, and the use of
|
||||
signal vs broadcast. */
|
||||
for (i = 0; i != waiters-1; i++) {
|
||||
cvt->cond[i] = 1;
|
||||
}
|
||||
if ((wakeup_type & after_region) == 0) {
|
||||
(*wakeup_func[wakeup_type & use_signal]) (&cvt->cv);
|
||||
}
|
||||
nsync_mu_unlock (&cvt->mu);
|
||||
if ((wakeup_type & after_region) != 0) {
|
||||
for (i = 0; i != waiters-1; i++) {
|
||||
(*wakeup_func[wakeup_type & use_signal]) (&cvt->cv);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for at least one woken waiter to proceed,
|
||||
and at least one writer if there is one. */
|
||||
nsync_mu_lock (&cvt->control_mu);
|
||||
while (are_all_below (&cvt->done[0], waiters-1, cv_writers!=0? 2 : 1)) {
|
||||
nsync_cv_wait (&cvt->done_cv, &cvt->control_mu);
|
||||
}
|
||||
nsync_mu_unlock (&cvt->control_mu);
|
||||
|
||||
/* Wake all remaining threads. */
|
||||
nsync_cv_broadcast (&cvt->cv);
|
||||
transfer_set (&cvt->mu, &cvt->cond[waiters-1], 1);
|
||||
|
||||
/* And wait for all to finish. */
|
||||
for (i = 0; i != waiters; i++) {
|
||||
transfer_await_nonzero (&cvt->control_mu, &cvt->done[i]);
|
||||
}
|
||||
|
||||
if (testing_verbose (t)) {
|
||||
TEST_LOG (t, ("transfer waiters %d wakeup_type %d cv_writers %d ccs_reader %d complete\n",
|
||||
waiters, wakeup_type, cv_writers, ccs_reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_cv_producer_consumer0);
|
||||
TEST_RUN (tb, test_cv_producer_consumer1);
|
||||
TEST_RUN (tb, test_cv_producer_consumer2);
|
||||
TEST_RUN (tb, test_cv_producer_consumer3);
|
||||
TEST_RUN (tb, test_cv_producer_consumer4);
|
||||
TEST_RUN (tb, test_cv_producer_consumer5);
|
||||
TEST_RUN (tb, test_cv_producer_consumer6);
|
||||
TEST_RUN (tb, test_cv_deadline);
|
||||
TEST_RUN (tb, test_cv_cancel);
|
||||
TEST_RUN (tb, test_cv_debug);
|
||||
TEST_RUN (tb, test_cv_transfer);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
182
third_party/nsync/testing/cv_wait_example_test.c
vendored
Normal file
182
third_party/nsync/testing/cv_wait_example_test.c
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/fmt/fmt.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/cv.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/testing/array.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/heap.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
// clang-format off
|
||||
|
||||
/* Example use of CV.wait(): A priority queue of strings whose
|
||||
"remove_with_deadline" operation has a deadline. */
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
typedef A_TYPE (const char *) a_string; /* An array used as a heap of strings. */
|
||||
|
||||
/* heap comparison function */
|
||||
static int str_lt (const char *e0, const char *e1) {
|
||||
return (strcmp (e0, e1) < 0);
|
||||
}
|
||||
|
||||
static void no_set (const char *a, int b) {
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* A string_priority_queue_cv is a priority queue of strings, which emits the
|
||||
lexicographically least string available. */
|
||||
typedef struct string_priority_queue_cv_s {
|
||||
nsync_cv non_empty; /* signalled when heap becomes non-empty */
|
||||
nsync_mu mu; /* protects heap */
|
||||
a_string heap;
|
||||
} string_priority_queue_cv;
|
||||
|
||||
|
||||
/* Add "s" to the queue *q. */
|
||||
static void string_priority_queue_cv_add (string_priority_queue_cv *q, const char *s) {
|
||||
int alen;
|
||||
nsync_mu_lock (&q->mu);
|
||||
alen = A_LEN (&q->heap);
|
||||
if (alen == 0) {
|
||||
nsync_cv_broadcast (&q->non_empty);
|
||||
}
|
||||
A_PUSH (&q->heap) = s;
|
||||
heap_add (&A (&q->heap, 0), alen, str_lt, no_set, s);
|
||||
nsync_mu_unlock (&q->mu);
|
||||
}
|
||||
|
||||
/* Wait until queue *q is non-empty, then remove a string from its
|
||||
beginning, and return it; or if abs_deadline is reached before the
|
||||
queue becomes non-empty, return NULL. */
|
||||
static const char *string_priority_queue_cv_remove_with_deadline (string_priority_queue_cv *q,
|
||||
nsync_time abs_deadline) {
|
||||
int alen;
|
||||
const char *s = NULL;
|
||||
nsync_mu_lock (&q->mu);
|
||||
while (A_LEN (&q->heap) == 0 &&
|
||||
nsync_cv_wait_with_deadline (&q->non_empty, &q->mu, abs_deadline, NULL) == 0) {
|
||||
}
|
||||
alen = A_LEN (&q->heap);
|
||||
if (alen != 0) {
|
||||
s = A (&q->heap, 0);
|
||||
heap_remove (&A (&q->heap, 0), alen, str_lt, no_set, 0);
|
||||
A_DISCARD (&q->heap, 1);
|
||||
}
|
||||
nsync_mu_unlock (&q->mu);
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Free resources associates with *q */
|
||||
static void string_priority_queue_cv_destroy (string_priority_queue_cv *q) {
|
||||
A_FREE (&q->heap);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Add strings s[0, ..., n-1] to *q, with the specified delay between additions. */
|
||||
static void add_and_wait_cv (string_priority_queue_cv *q, nsync_time delay,
|
||||
int n, const char *s[]) {
|
||||
int i;
|
||||
for (i = 0; i != n; i++) {
|
||||
string_priority_queue_cv_add (q, s[i]);
|
||||
nsync_time_sleep (delay);
|
||||
}
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY4 (add_and_wait_cv, string_priority_queue_cv *,
|
||||
nsync_time, int, const char **)
|
||||
|
||||
typedef A_TYPE (char) a_char; /* an array or characters */
|
||||
|
||||
/* Append the nul-terminated string str[] to *a. */
|
||||
static void a_char_append (a_char *a, const char *str) {
|
||||
while (*str != 0) {
|
||||
A_PUSH (a) = *str;
|
||||
str++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the first item from *q and output it on stdout,
|
||||
or output "timeout: <delay>" if no value can be found before "delay" elapses. */
|
||||
static void remove_and_print_cv (string_priority_queue_cv *q, nsync_time delay, a_char *output) {
|
||||
const char *s;
|
||||
if ((s = string_priority_queue_cv_remove_with_deadline (
|
||||
q, nsync_time_add (nsync_time_now(), delay))) != NULL) {
|
||||
a_char_append (output, s);
|
||||
a_char_append (output, "\n");
|
||||
} else {
|
||||
char buf[64];
|
||||
snprintf (buf, sizeof (buf), "timeout %gs\n",
|
||||
nsync_time_to_dbl (delay));
|
||||
a_char_append (output, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Demonstrates the use of nsync_mu_wait() via a priority queue of strings.
|
||||
See the routine string_priority_queue_cv_remove_with_deadline(), above. */
|
||||
static void example_cv_wait (testing t) {
|
||||
static const char *input[] = { "one", "two", "three", "four", "five" };
|
||||
string_priority_queue_cv q;
|
||||
a_char output;
|
||||
static const char *expected =
|
||||
"one\n"
|
||||
"three\n"
|
||||
"two\n"
|
||||
"timeout 0.1s\n"
|
||||
"four\n"
|
||||
"timeout 0.1s\n"
|
||||
"five\n"
|
||||
"timeout 1s\n";
|
||||
|
||||
memset ((void *) &q, 0, sizeof (q));
|
||||
memset (&output, 0, sizeof (output));
|
||||
|
||||
closure_fork (closure_add_and_wait_cv (&add_and_wait_cv, &q,
|
||||
nsync_time_ms (500), NELEM (input), input));
|
||||
|
||||
/* delay: "one", "two", "three" are queued; not "four" */
|
||||
nsync_time_sleep (nsync_time_ms (1200));
|
||||
|
||||
remove_and_print_cv (&q, nsync_time_ms (1000), &output); /* "one" */
|
||||
remove_and_print_cv (&q, nsync_time_ms (1000), &output); /* "three" (less than "two") */
|
||||
remove_and_print_cv (&q, nsync_time_ms (1000), &output); /* "two" */
|
||||
remove_and_print_cv (&q, nsync_time_ms (100), &output); /* time out because 1.3 < 0.5*3 */
|
||||
remove_and_print_cv (&q, nsync_time_ms (1000), &output); /* "four" */
|
||||
remove_and_print_cv (&q, nsync_time_ms (100), &output); /* time out because 0.1 < 0.5 */
|
||||
remove_and_print_cv (&q, nsync_time_ms (1000), &output); /* "five" */
|
||||
remove_and_print_cv (&q, nsync_time_ms (1000), &output); /* time out: no more to fetch */
|
||||
|
||||
A_PUSH (&output) = 0;
|
||||
if (strcmp (&A (&output, 0), expected) != 0) {
|
||||
TEST_ERROR (t, ("expected = %s\ngot = %s\n", expected, &A (&output, 0)));
|
||||
}
|
||||
A_FREE (&output);
|
||||
string_priority_queue_cv_destroy (&q);
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, example_cv_wait);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
324
third_party/nsync/testing/dll_test.c
vendored
Normal file
324
third_party/nsync/testing/dll_test.c
vendored
Normal file
|
@ -0,0 +1,324 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/fmt/fmt.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
#include "third_party/nsync/testing/array.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
// clang-format off
|
||||
|
||||
/* This tests internal abstractions. */
|
||||
|
||||
typedef A_TYPE (int) a_int; /* an array of 32-bit integers */
|
||||
static const a_int a_int_empty = A_EMPTY; /* the empty array */
|
||||
|
||||
/* Append the integers in the argument list to *a, until the first negative one is found. */
|
||||
static a_int *a_set (a_int *a, ...) {
|
||||
va_list ap;
|
||||
int x;
|
||||
A_SET_LEN (a, 0);
|
||||
va_start (ap, a);
|
||||
for (x = va_arg (ap, int); x >= 0; x = va_arg (ap, int)) {
|
||||
A_PUSH (a) = x;
|
||||
}
|
||||
va_end (ap);
|
||||
return (a);
|
||||
}
|
||||
|
||||
/* Remove the first element from *a. Requires that *a be non-empty. */
|
||||
static void a_remove_first (a_int *a) {
|
||||
int len = A_LEN (a);
|
||||
if (len == 0) {
|
||||
*(volatile int *)0 = 0;
|
||||
} else {
|
||||
memmove (&A (a, 0), &A (a, 1), sizeof (A (a, 0)) * (len - 1));
|
||||
A_SET_LEN (a, len-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Return a malloced, nul-terminated string representation of the elements of *a. */
|
||||
static char *a_string (const a_int *a) {
|
||||
int m = A_LEN (a) * 3 + 3;
|
||||
int n = 0;
|
||||
char *buf = (char *) malloc (m);
|
||||
char single[32];
|
||||
int i;
|
||||
snprintf (buf+n, m-n, "[");
|
||||
n = strlen (buf);
|
||||
for (i = 0; i != A_LEN (a); i++) {
|
||||
int len;
|
||||
snprintf (single, sizeof (single), "%s%lu", i == 0? "": " ",
|
||||
(unsigned long)A (a, i));
|
||||
len = strlen (single);
|
||||
if (m < n + len + 2) {
|
||||
buf = (char *) realloc (buf, m *= 2);
|
||||
}
|
||||
snprintf (buf + n, m-n, "%s", single);
|
||||
n += len;
|
||||
}
|
||||
snprintf (buf+n, m-n, "]");
|
||||
return (buf);
|
||||
}
|
||||
|
||||
/* A list item for use in the tests below */
|
||||
struct list_item_s {
|
||||
nsync_dll_element_ e;
|
||||
int i;
|
||||
};
|
||||
/* Return a pointer to the struct list_item_s containing nsync_dll_element_ *e_. */
|
||||
#define LIST_ITEM(e_) ((struct list_item_s *) ((e_)->container))
|
||||
|
||||
|
||||
/* Check that list l contains elements containing the values in
|
||||
expected, by scanning both forwards and backwards through the list. Also
|
||||
verify that nsync_dll_first_() and nsync_dll_last_() return the first and last element
|
||||
found during those iterations, and that nsync_dll_is_empty_() yields the right value. */
|
||||
static void verify_list (testing t, const char *label, nsync_dll_list_ l,
|
||||
const a_int *expected, const char *file, int line) {
|
||||
nsync_dll_element_ *first;
|
||||
nsync_dll_element_ *last = NULL;
|
||||
nsync_dll_element_ *p;
|
||||
int i = 0;
|
||||
char *expected_str = a_string (expected);
|
||||
for (p = nsync_dll_first_ (l); p != NULL; p = nsync_dll_next_ (l, p)) {
|
||||
if (A (expected, i) != LIST_ITEM (p)->i) {
|
||||
TEST_ERROR (t, ("%s:%d; %s:expected=%s: expected %d as "
|
||||
"value %d in list, but found %d\n",
|
||||
file, line, label, expected_str,
|
||||
A (expected, i), i, LIST_ITEM (p)->i));
|
||||
}
|
||||
last = p;
|
||||
i++;
|
||||
}
|
||||
if (last != nsync_dll_last_ (l)) {
|
||||
TEST_ERROR (t, ("%s:%d: %s:expected=%s: expected %p as "
|
||||
"last item in list, but found %p\n",
|
||||
file, line, label, expected_str, last, nsync_dll_last_ (l)));
|
||||
}
|
||||
if (i != A_LEN (expected)) {
|
||||
TEST_ERROR (t, ("%s:%d: %s:expected=%s: expected %d items in "
|
||||
"list, but found %d\n",
|
||||
file, line, label, expected_str, A_LEN (expected), i));
|
||||
}
|
||||
|
||||
first = NULL;
|
||||
for (p = nsync_dll_last_ (l); p != NULL; p = nsync_dll_prev_ (l, p)) {
|
||||
i--;
|
||||
if (A (expected, i) != LIST_ITEM (p)->i) {
|
||||
TEST_ERROR (t, ("%s:%d: %s:expected=%s: expected %d as "
|
||||
"value %d in reverse list, but found %d\n",
|
||||
file, line, label, expected_str,
|
||||
A (expected, i), i, LIST_ITEM (p)->i));
|
||||
}
|
||||
first = p;
|
||||
}
|
||||
if (first != nsync_dll_first_ (l)) {
|
||||
TEST_ERROR (t, ("%s:%d: %s:expected=%s: expected %p as "
|
||||
"first item in list, but found %p\n",
|
||||
file, line, label, expected_str, first, nsync_dll_last_ (l)));
|
||||
}
|
||||
if (i != 0) {
|
||||
TEST_ERROR (t, ("%s:%d: %s:expected=%s: expected %d items "
|
||||
"in reverse list, but found %d\n",
|
||||
file, line, label, expected_str,
|
||||
A_LEN (expected), A_LEN (expected)-i));
|
||||
}
|
||||
|
||||
if ((A_LEN (expected) == 0) != nsync_dll_is_empty_ (l)) {
|
||||
TEST_ERROR (t, ("%s:%d: %s:expected=%s: expected nsync_dll_is_empty_() "
|
||||
"to yield %d but got %d\n",
|
||||
file, line, label, expected_str,
|
||||
(A_LEN (expected) == 0), nsync_dll_is_empty_ (l)));
|
||||
}
|
||||
free (expected_str);
|
||||
}
|
||||
|
||||
/* Return a new list containing the count integers from start to
|
||||
start+count-1 by appending successive elements to the list.
|
||||
This exercises nsync_dll_make_last_in_list_() using singleton elements. */
|
||||
static nsync_dll_list_ make_list (int start, int count) {
|
||||
nsync_dll_list_ l = NULL;
|
||||
int i;
|
||||
for (i = start; i != start+count; i++) {
|
||||
struct list_item_s *item =
|
||||
(struct list_item_s *) malloc (sizeof (*item));
|
||||
nsync_dll_init_ (&item->e, item);
|
||||
item->i = i;
|
||||
l = nsync_dll_make_last_in_list_ (l, &item->e);
|
||||
}
|
||||
return (l);
|
||||
}
|
||||
|
||||
/* Return a new list containing the count integers from start to
|
||||
start+count-1 by prefixing the list with elements, starting with the last.
|
||||
It exercises nsync_dll_make_first_in_list_() using singleton elements. */
|
||||
static nsync_dll_list_ make_rlist (int start, int count) {
|
||||
nsync_dll_list_ l = NULL;
|
||||
int i;
|
||||
for (i = start + count - 1; i != start-1; i--) {
|
||||
struct list_item_s *item =
|
||||
(struct list_item_s *) malloc (sizeof (*item));
|
||||
nsync_dll_init_ (&item->e, item);
|
||||
item->i = i;
|
||||
l = nsync_dll_make_first_in_list_ (l, &item->e);
|
||||
}
|
||||
return (l);
|
||||
}
|
||||
|
||||
/* Test the functionality of the various doubly-linked list
|
||||
operations internal to the nsync_mu implementation. */
|
||||
static void test_dll (testing t) {
|
||||
int i;
|
||||
a_int expected;
|
||||
struct list_item_s *item;
|
||||
|
||||
nsync_dll_list_ empty = NULL;
|
||||
nsync_dll_list_ list = NULL;
|
||||
|
||||
nsync_dll_list_ x10 = NULL;
|
||||
nsync_dll_list_ x20 = NULL;
|
||||
nsync_dll_list_ x30 = NULL;
|
||||
nsync_dll_list_ x40 = NULL;
|
||||
nsync_dll_list_ x50 = NULL;
|
||||
|
||||
memset (&expected, 0, sizeof (expected));
|
||||
|
||||
/* All lists are initially empty. */
|
||||
verify_list (t, "empty (0)", empty, &a_int_empty, __FILE__, __LINE__);
|
||||
verify_list (t, "list (0)", list, &a_int_empty, __FILE__, __LINE__);
|
||||
verify_list (t, "x10", x10, &a_int_empty, __FILE__, __LINE__);
|
||||
verify_list (t, "x20", x20, &a_int_empty, __FILE__, __LINE__);
|
||||
verify_list (t, "x30", x30, &a_int_empty, __FILE__, __LINE__);
|
||||
verify_list (t, "x40", x40, &a_int_empty, __FILE__, __LINE__);
|
||||
verify_list (t, "x50", x50, &a_int_empty, __FILE__, __LINE__);
|
||||
|
||||
/* Make the xN list have the values N, N+1, N+2. */
|
||||
x10 = make_list (10, 3);
|
||||
verify_list (t, "x10", x10, a_set (&expected, 10, 11, 12, -1), __FILE__, __LINE__);
|
||||
x20 = make_rlist (20, 3);
|
||||
verify_list (t, "x20", x20, a_set (&expected, 20, 21, 22, -1), __FILE__, __LINE__);
|
||||
x30 = make_list (30, 3);
|
||||
verify_list (t, "x30", x30, a_set (&expected, 30, 31, 32, -1), __FILE__, __LINE__);
|
||||
x40 = make_list (40, 3);
|
||||
verify_list (t, "x40", x40, a_set (&expected, 40, 41, 42, -1), __FILE__, __LINE__);
|
||||
x50 = make_list (50, 3);
|
||||
verify_list (t, "x50", x50, a_set (&expected, 50, 51, 52, -1), __FILE__, __LINE__);
|
||||
|
||||
/* Check that adding nothing to an empty list leaves it empty. */
|
||||
list = nsync_dll_make_first_in_list_ (list, NULL);
|
||||
verify_list (t, "list(1)", list, &a_int_empty, __FILE__, __LINE__);
|
||||
list = nsync_dll_make_first_in_list_ (list, nsync_dll_first_ (empty));
|
||||
verify_list (t, "list(2)", list, &a_int_empty, __FILE__, __LINE__);
|
||||
list = nsync_dll_make_first_in_list_ (list, nsync_dll_last_ (empty));
|
||||
verify_list (t, "list(3)", list, &a_int_empty, __FILE__, __LINE__);
|
||||
|
||||
/* Prefix an empty list with some elements. */
|
||||
list = nsync_dll_make_first_in_list_ (list, nsync_dll_first_ (x10));
|
||||
verify_list (t, "list(4)", list, a_set (&expected, 10, 11, 12, -1),
|
||||
__FILE__, __LINE__);
|
||||
|
||||
/* Check that adding nothing no a non-empty list leaves it unchanged. */
|
||||
list = nsync_dll_make_first_in_list_ (list, NULL);
|
||||
verify_list (t, "list(5)", list, a_set (&expected, 10, 11, 12, -1),
|
||||
__FILE__, __LINE__);
|
||||
list = nsync_dll_make_first_in_list_ (list, nsync_dll_first_ (empty));
|
||||
verify_list (t, "list(6)", list, a_set (&expected, 10, 11, 12, -1),
|
||||
__FILE__, __LINE__);
|
||||
list = nsync_dll_make_first_in_list_ (list, nsync_dll_last_ (empty));
|
||||
verify_list (t, "list(7)", list, a_set (&expected, 10, 11, 12, -1),
|
||||
__FILE__, __LINE__);
|
||||
|
||||
/* Check prefixing the list with some elements. */
|
||||
list = nsync_dll_make_first_in_list_ (list, nsync_dll_first_ (x20));
|
||||
verify_list (t, "list(8)", list,
|
||||
a_set (&expected, 20, 21, 22, 10, 11, 12, -1),
|
||||
__FILE__, __LINE__);
|
||||
|
||||
/* Check appending elements to list. */
|
||||
list = nsync_dll_make_last_in_list_ (list, nsync_dll_last_ (x30));
|
||||
verify_list (t, "list(9)", list,
|
||||
a_set (&expected, 20, 21, 22, 10, 11, 12, 30, 31, 32, -1),
|
||||
__FILE__, __LINE__);
|
||||
|
||||
/* Remove the first element. */
|
||||
item = (struct list_item_s *) nsync_dll_first_ (list)->container;
|
||||
list = nsync_dll_remove_ (list, &item->e);
|
||||
verify_list (t, "list(10)", list,
|
||||
a_set (&expected, 21, 22, 10, 11, 12, 30, 31, 32, -1),
|
||||
__FILE__, __LINE__);
|
||||
free (item);
|
||||
|
||||
/* Remove the last element. */
|
||||
item = (struct list_item_s *) nsync_dll_last_ (list)->container;
|
||||
list = nsync_dll_remove_ (list, &item->e);
|
||||
verify_list (t, "list(11)", list,
|
||||
a_set (&expected, 21, 22, 10, 11, 12, 30, 31, -1),
|
||||
__FILE__, __LINE__);
|
||||
free (item);
|
||||
|
||||
/* Remove the third element. */
|
||||
item = (struct list_item_s *) nsync_dll_next_ (list,
|
||||
nsync_dll_next_ (list, nsync_dll_first_ (list)))->container;
|
||||
list = nsync_dll_remove_ (list, &item->e);
|
||||
verify_list (t, "list(12)",
|
||||
list, a_set (&expected, 21, 22, 11, 12, 30, 31, -1),
|
||||
__FILE__, __LINE__);
|
||||
free (item);
|
||||
|
||||
/* Remove all elements. */
|
||||
a_set (&expected, 21, 22, 11, 12, 30, 31, -1);
|
||||
for (i = 0; !nsync_dll_is_empty_ (list); i++) {
|
||||
char buf[32];
|
||||
item = (struct list_item_s *) nsync_dll_first_ (list)->container;
|
||||
list = nsync_dll_remove_ (list, &item->e);
|
||||
a_remove_first (&expected);
|
||||
snprintf (buf, sizeof (buf), "list(13.%d)", i);
|
||||
verify_list (t, buf, list, &expected, __FILE__, __LINE__);
|
||||
free (item);
|
||||
}
|
||||
verify_list (t, "list(14)", list, &a_int_empty, __FILE__, __LINE__);
|
||||
|
||||
/* Append some elements to an empty list. */
|
||||
list = nsync_dll_make_last_in_list_ (list, nsync_dll_last_ (x40));
|
||||
verify_list (t, "list(15)", list,
|
||||
a_set (&expected, 40, 41, 42, -1), __FILE__, __LINE__);
|
||||
|
||||
/* Use nsync_dll_splice_after_() to put {50, 51, 52} just after 41, which is
|
||||
next (first (list)). */
|
||||
nsync_dll_splice_after_ (nsync_dll_next_ (list, nsync_dll_first_ (list)), nsync_dll_first_ (x50));
|
||||
verify_list (t, "list(16)", list,
|
||||
a_set (&expected, 40, 41, 50, 51, 52, 42, -1),
|
||||
__FILE__, __LINE__);
|
||||
|
||||
A_FREE (&expected);
|
||||
|
||||
while (!nsync_dll_is_empty_ (list)) {
|
||||
item = (struct list_item_s *) nsync_dll_first_ (list)->container;
|
||||
list = nsync_dll_remove_ (list, &item->e);
|
||||
free (item);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_dll);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
60
third_party/nsync/testing/heap.h
vendored
Normal file
60
third_party/nsync/testing/heap.h
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef NSYNC_TESTING_HEAP_H_
|
||||
#define NSYNC_TESTING_HEAP_H_
|
||||
/* clang-format off */
|
||||
|
||||
/* A heap.
|
||||
Optionally, elements may have storage for the index to allow deletions from
|
||||
arbitrary elements. A "set" operation sets the field. Use heap_no_set when
|
||||
no field is available.
|
||||
|
||||
Let:
|
||||
set (e,i) sets the index field of the element e to i
|
||||
lt (e0, e1) returns whether element e0 < e1
|
||||
|
||||
If
|
||||
"a" is an array,
|
||||
"n" is is its length,
|
||||
then
|
||||
To add an element e:
|
||||
ensure there are n+1 elements in a[]
|
||||
heap_add (a, n, lt, set, e); // modifies n
|
||||
To remove element i:
|
||||
heap_remove (a, n, lt, set, i); // modifies n
|
||||
To replace element i with element e:
|
||||
heap_adjust (a, n, lt, set, i, e);
|
||||
*/
|
||||
|
||||
|
||||
#define h_up_(i) (((i)-1) >> 1)
|
||||
#define h_down_(i) (((i)<<1) + 1)
|
||||
|
||||
#define h_updownall_(up,a,n,i,lt,set,v,s) \
|
||||
do { \
|
||||
int i_ = (i); \
|
||||
int n_ = (n); \
|
||||
int j_; \
|
||||
if (up) { \
|
||||
for (; i_!=0 && ((j_ = h_up_ (i_)), lt ((v), (a)[j_])); i_ = j_) { \
|
||||
(a)[i_] = (a)[j_]; \
|
||||
set ((a)[i_], i_); \
|
||||
} \
|
||||
} else { \
|
||||
for (; (j_ = h_down_ (i_)) < n_ && ((j_ += (j_+1 < n_ && \
|
||||
lt ((a)[j_+1], (a)[j_]))), lt ((a)[j_], (v))); i_ = j_) { \
|
||||
(a)[i_] = (a)[j_]; \
|
||||
set ((a)[i_], i_); \
|
||||
} \
|
||||
} \
|
||||
s; \
|
||||
} while (0)
|
||||
|
||||
#define heap_no_set(a,b) ((void)0)
|
||||
|
||||
#define heap_add(a,n,lt,set,v) h_updownall_ (1, (a), 0, \
|
||||
(n), lt, set, (v), ((a)[i_]=(v), set ((a)[i_], i_), (n)++))
|
||||
#define heap_remove(a,n,lt,set,i) h_updownall_ (lt ((a)[n_], (a)[i_]), (a), --(n), \
|
||||
(i), lt, set, (a)[n_], ((a)[i_]=(a)[n_], set ((a)[i_], i_)))
|
||||
#define heap_adjust(a,n,lt,set,i,v) h_updownall_ (lt ((v), (a)[i_]), (a), (n), \
|
||||
(i), lt, set, (v), ((a)[i_]=(v), set ((a)[i_], i_)))
|
||||
|
||||
#endif /*NSYNC_TESTING_HEAP_H_*/
|
346
third_party/nsync/testing/mu_starvation_test.c
vendored
Normal file
346
third_party/nsync/testing/mu_starvation_test.c
vendored
Normal file
|
@ -0,0 +1,346 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
// clang-format off
|
||||
|
||||
/* Test the behaviour of mu in situations where starvation might be expected. */
|
||||
|
||||
/* starve_data is the data used by the starvation tests */
|
||||
typedef struct starve_data_s {
|
||||
nsync_mu mu; /* precedes control_mu in locking order */
|
||||
int cancel; /* whether threads should shutdown; under mu */
|
||||
nsync_time start; /* when test started */
|
||||
|
||||
nsync_mu control_mu;
|
||||
int not_yet_started; /* threads not yet started; under control_mu */
|
||||
int not_yet_done; /* threads not yet done; under control_mu */
|
||||
} starve_data;
|
||||
|
||||
/* initialize *sd */
|
||||
static void starve_data_init (starve_data *sd, int threads) {
|
||||
memset ((void *) sd, 0, sizeof (*sd));
|
||||
sd->not_yet_started = threads;
|
||||
sd->not_yet_done = threads;
|
||||
sd->start = nsync_time_now ();
|
||||
}
|
||||
|
||||
/* Loop until *cancel or deadline, and on each iteration
|
||||
acquire *mu in reader mode, and hold it until the next odd or even
|
||||
multiple of period, according to parity. Just before return, decrement *done
|
||||
under *mu. Two threads using these calls are used to hold the
|
||||
mutex continually, in the absence of other activity. */
|
||||
static void starve_with_readers (starve_data *sd, nsync_time period,
|
||||
uint32_t parity, nsync_time deadline) {
|
||||
nsync_time now;
|
||||
uint32_t period_us = (uint32_t) (nsync_time_to_dbl (period) * 1e6);
|
||||
nsync_mu_rlock (&sd->mu);
|
||||
|
||||
nsync_mu_lock (&sd->control_mu);
|
||||
sd->not_yet_started--;
|
||||
nsync_mu_unlock (&sd->control_mu);
|
||||
|
||||
for (now = nsync_time_now ();
|
||||
!sd->cancel && nsync_time_cmp (now, deadline) < 0;
|
||||
now = nsync_time_now ()) {
|
||||
uint32_t new_us;
|
||||
uint32_t now_us = (uint32_t) (nsync_time_to_dbl (nsync_time_sub (now, sd->start)) * 1e6);
|
||||
uint32_t index = (now_us + period_us - 1) / period_us;
|
||||
if ((index & 1) != parity) {
|
||||
index++;
|
||||
}
|
||||
new_us = index * period_us;
|
||||
nsync_time_sleep (nsync_time_from_dbl (1e-6 * (double) (new_us-now_us)));
|
||||
nsync_mu_runlock (&sd->mu);
|
||||
nsync_mu_rlock (&sd->mu);
|
||||
}
|
||||
nsync_mu_runlock (&sd->mu);
|
||||
|
||||
nsync_mu_lock (&sd->control_mu);
|
||||
sd->not_yet_done--;
|
||||
nsync_mu_unlock (&sd->control_mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY4 (starve_with_readers, starve_data *, nsync_time, uint32_t, nsync_time)
|
||||
|
||||
static int started (const void *v) {
|
||||
return (((const starve_data *) v)->not_yet_started == 0);
|
||||
}
|
||||
|
||||
static int done (const void *v) {
|
||||
return (((const starve_data *) v)->not_yet_done == 0);
|
||||
}
|
||||
|
||||
/* Verify the behaviour of nsync_mu in the face of reader threads that conspire
|
||||
keep the lock held continuously in reader mode, even though each of the
|
||||
threads releases and reacquires periodically (while another thread holds the
|
||||
lock). The routine starve_with_readers() is used to achieve this effect.
|
||||
|
||||
We expect that nsync_mu_trylock() will not be able to acquire while this is
|
||||
happening, but that nsync_mu_lock() will be able to acquire, due to the action of the
|
||||
mu's mu_writer_waiting bit. */
|
||||
static void test_starve_with_readers (testing t) {
|
||||
nsync_time finish;
|
||||
int trylock_acquires;
|
||||
int expected_lo;
|
||||
int lock_acquires;
|
||||
|
||||
nsync_time deadline;
|
||||
|
||||
starve_data sd;
|
||||
starve_data_init (&sd, 2); /* two threads, started below */
|
||||
|
||||
/* Threads run for at most 10s. */
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (10000));
|
||||
|
||||
/* These two threads will try to hold a reader lock
|
||||
continuously until cancel is set or deadline is reached,
|
||||
even though each will release the lock every 20ms. */
|
||||
closure_fork (closure_starve_with_readers (
|
||||
&starve_with_readers, &sd, nsync_time_ms (10), 0, deadline));
|
||||
closure_fork (closure_starve_with_readers (
|
||||
&starve_with_readers, &sd, nsync_time_ms (10), 1, deadline));
|
||||
|
||||
/* wait for the threads to acquire their first lock. */
|
||||
nsync_mu_lock (&sd.control_mu);
|
||||
nsync_mu_wait (&sd.control_mu, &started, &sd, NULL);
|
||||
nsync_mu_unlock (&sd.control_mu);
|
||||
|
||||
/* If using an nsync_mu, use nsync_mu_trylock() to attempt to acquire while the
|
||||
readers are hogging the lock. We expect no acquisitions to succeed. */
|
||||
finish = nsync_time_add (nsync_time_now (), nsync_time_ms (500));
|
||||
trylock_acquires = 0; /* number of acquires */
|
||||
while (nsync_time_cmp (nsync_time_now (), finish) < 0) {
|
||||
if (nsync_mu_trylock (&sd.mu)) {
|
||||
trylock_acquires++;
|
||||
nsync_mu_unlock (&sd.mu);
|
||||
}
|
||||
sched_yield ();
|
||||
}
|
||||
if (trylock_acquires != 0) {
|
||||
TEST_ERROR (t, ("expected no acquisitions via nsync_mu_trylock(), got %d\n",
|
||||
trylock_acquires));
|
||||
}
|
||||
|
||||
/* Use nsync_mu_lock() to attempt to acquire while the readers are hogging
|
||||
the lock. We expect several acquisitions to succeed. */
|
||||
expected_lo = 2;
|
||||
finish = nsync_time_add (nsync_time_now (), nsync_time_ms (5000));
|
||||
lock_acquires = 0; /* number of acquires */
|
||||
while (nsync_time_cmp (nsync_time_now (), finish) < 0 && lock_acquires < expected_lo) {
|
||||
nsync_mu_lock (&sd.mu);
|
||||
lock_acquires++;
|
||||
nsync_mu_unlock (&sd.mu);
|
||||
nsync_time_sleep (nsync_time_ms (1));
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_now (), deadline) > 0 && lock_acquires == 1) {
|
||||
lock_acquires = 0; /* hog threads timed out */
|
||||
}
|
||||
if (lock_acquires < expected_lo) {
|
||||
TEST_ERROR (t, ("expected at least %d acquisitions via nsync_mu_lock(), got %d\n",
|
||||
expected_lo, lock_acquires));
|
||||
}
|
||||
|
||||
nsync_mu_lock (&sd.mu);
|
||||
sd.cancel = 1; /* Tell threads to exit. */
|
||||
nsync_mu_unlock (&sd.mu);
|
||||
|
||||
nsync_mu_lock (&sd.control_mu);
|
||||
nsync_mu_wait (&sd.control_mu, &done, &sd, NULL); /* wait for exit. */
|
||||
nsync_mu_unlock (&sd.control_mu);
|
||||
}
|
||||
|
||||
/* Loop until sd.cancel or deadline. On each iteration<
|
||||
acquire sd.mu in writer mode, sleep for hold_time, and release sd.mu.
|
||||
Just before return, decrement sd.not_yet_done under sd.control_mu. */
|
||||
static void starve_with_writer (starve_data *sd, nsync_time hold_time,
|
||||
nsync_time deadline) {
|
||||
nsync_time now;
|
||||
|
||||
nsync_mu_lock (&sd->mu);
|
||||
|
||||
nsync_mu_lock (&sd->control_mu);
|
||||
sd->not_yet_started--;
|
||||
nsync_mu_unlock (&sd->control_mu);
|
||||
|
||||
for (now = nsync_time_now ();
|
||||
!sd->cancel && nsync_time_cmp (now, deadline) < 0;
|
||||
now = nsync_time_now ()) {
|
||||
nsync_time_sleep (hold_time);
|
||||
nsync_mu_unlock (&sd->mu);
|
||||
nsync_mu_lock (&sd->mu);
|
||||
}
|
||||
nsync_mu_unlock (&sd->mu);
|
||||
|
||||
nsync_mu_lock (&sd->control_mu);
|
||||
sd->not_yet_done--;
|
||||
nsync_mu_unlock (&sd->control_mu);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY3 (starve_with_writer, starve_data *, nsync_time, nsync_time)
|
||||
|
||||
/* Verify the behaviour of nsync_mu in the face of a
|
||||
single writer thread that repeatedly hogs the lock by acquiring it and
|
||||
holding it for longer than the runtime's wakeup time, then releasing. The
|
||||
next iteration reacquires the lock moments later, a time much shorter than
|
||||
the runtime's wakeup time. The routine starve_with_writer() is used to
|
||||
achieve this effect.
|
||||
|
||||
These circumstances can make it hard for another thread T to acquire. T
|
||||
will first wait on the mutex's queue. Eventually, it will be woken by the
|
||||
hog thread, but under normal circumstances T will take so long to run that
|
||||
the hog will have reacquired the mutex. Because the hog keeps the lock for
|
||||
longer than the runtime's wakeup time, T will go back to sleep again, and
|
||||
the process repeats indefinitely.
|
||||
|
||||
We expect that incessant attempts via nsync_mu_trylock() and nsync_mu_rtrylock() will
|
||||
occasionally manage to hit the moments when the lock is not held. nsync_mu_lock()
|
||||
and nsync_mu_rlock() will succeed only because of the action of mu's mu_long_wait bit,
|
||||
which will eventually force the hog to wait itself, and allow a waiter
|
||||
to acquire. We expect few acquires because mu_long_wait kicks in only
|
||||
when things look dire. */
|
||||
static void test_starve_with_writer (testing t) {
|
||||
int expected_lo;
|
||||
nsync_time finish;
|
||||
int lock_acquires;
|
||||
int rlock_acquires;
|
||||
int trylock_acquires;
|
||||
int rtrylock_acquires;
|
||||
nsync_time deadline;
|
||||
starve_data sd;
|
||||
starve_data_init (&sd, 1); /* one thread, started below */
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (25000)); /* runs for at most 25s. */
|
||||
|
||||
/* This thread will try to hold a writer lock almost
|
||||
continuously, releasing momentarily every 10ms. */
|
||||
closure_fork (closure_starve_with_writer (&starve_with_writer, &sd,
|
||||
nsync_time_ms (10), deadline));
|
||||
|
||||
nsync_mu_lock (&sd.control_mu);
|
||||
nsync_mu_wait (&sd.control_mu, &started, &sd, NULL);
|
||||
nsync_mu_unlock (&sd.control_mu);
|
||||
|
||||
expected_lo = 0; /* minimum expected operations at each test */
|
||||
finish = nsync_time_zero; /* finish time for each test */
|
||||
|
||||
if (!testing_is_uniprocessor (t)) { /* this test won't work on a uniprocessor */
|
||||
/* Use nsync_mu_trylock() to attempt to acquire while the writer is hogging the
|
||||
lock. We expect some acquisitions to succeed. */
|
||||
expected_lo = 1;
|
||||
finish = nsync_time_add (nsync_time_now (), nsync_time_ms (30000));
|
||||
trylock_acquires = 0; /* number of acquires */
|
||||
while (nsync_time_cmp (nsync_time_now (), finish) < 0 && trylock_acquires < expected_lo) {
|
||||
if (nsync_mu_trylock (&sd.mu)) {
|
||||
trylock_acquires++;
|
||||
nsync_mu_unlock (&sd.mu);
|
||||
}
|
||||
sched_yield ();
|
||||
}
|
||||
if (trylock_acquires < expected_lo) {
|
||||
TEST_ERROR (t, ("expected at least %d acquisitions via "
|
||||
"nsync_mu_trylock(), got %d\n",
|
||||
expected_lo, trylock_acquires));
|
||||
}
|
||||
}
|
||||
|
||||
if (!testing_is_uniprocessor (t)) { /* this test won't work on a uniprocessor */
|
||||
/* Use nsync_mu_rtrylock() to attempt to read-acquire while the writer is
|
||||
hogging the lock. We expect some acquisitions to succeed. */
|
||||
expected_lo = 1;
|
||||
finish = nsync_time_add (nsync_time_now (), nsync_time_ms (30000));
|
||||
rtrylock_acquires = 0; /* number of acquires */
|
||||
while (nsync_time_cmp (nsync_time_now (), finish) < 0 && rtrylock_acquires < expected_lo) {
|
||||
if (nsync_mu_rtrylock (&sd.mu)) {
|
||||
rtrylock_acquires++;
|
||||
nsync_mu_runlock (&sd.mu);
|
||||
}
|
||||
sched_yield ();
|
||||
}
|
||||
if (rtrylock_acquires < expected_lo) {
|
||||
TEST_ERROR (t, ("expected at least %d acquisitions via "
|
||||
"nsync_mu_rtrylock(), got %d\n",
|
||||
expected_lo, rtrylock_acquires));
|
||||
}
|
||||
}
|
||||
|
||||
/* Use nsync_mu_lock() to attempt to acquire while the writer is hogging
|
||||
the lock. We expect several acquisitions to succeed. */
|
||||
expected_lo = 2;
|
||||
finish = nsync_time_add (nsync_time_now (), nsync_time_ms (5000));
|
||||
lock_acquires = 0; /* number of acquires */
|
||||
while (nsync_time_cmp (nsync_time_now (), finish) < 0 && lock_acquires < expected_lo) {
|
||||
nsync_mu_lock (&sd.mu);
|
||||
lock_acquires++;
|
||||
nsync_mu_unlock (&sd.mu);
|
||||
nsync_time_sleep (nsync_time_ms (2));
|
||||
}
|
||||
if (lock_acquires == 1 && nsync_time_cmp (nsync_time_now (), deadline) > 0) {
|
||||
lock_acquires = 0; /* hog thread timed out */
|
||||
}
|
||||
if (lock_acquires < expected_lo) {
|
||||
TEST_ERROR (t, ("expected at least %d acquisitions via nsync_mu_lock(), got %d\n",
|
||||
expected_lo, lock_acquires));
|
||||
}
|
||||
|
||||
/* If enough time remains to run the test, use nsync_mu_rlock() to attempt to
|
||||
acquire while the writer is hogging the lock. We expect several
|
||||
acquisitions to succeed. It's ok not to run the test if we ran out
|
||||
time----it means that a writer couldn't break in (the test case
|
||||
above failed), so a reader is unlikely to manage it either. */
|
||||
expected_lo = 2;
|
||||
finish = nsync_time_add (nsync_time_now (), nsync_time_ms (5000));
|
||||
rlock_acquires = 0; /* number of acquires */
|
||||
if (nsync_time_cmp (finish, deadline) < 0) {
|
||||
while (nsync_time_cmp (nsync_time_now (), finish) < 0 && rlock_acquires < expected_lo) {
|
||||
nsync_mu_rlock (&sd.mu);
|
||||
rlock_acquires++;
|
||||
nsync_mu_runlock (&sd.mu);
|
||||
nsync_time_sleep (nsync_time_ms (2));
|
||||
}
|
||||
if (rlock_acquires == 1 && nsync_time_cmp (nsync_time_now (), deadline) > 0) {
|
||||
rlock_acquires = 0; /* hog thread timed out */
|
||||
}
|
||||
if (rlock_acquires < expected_lo) {
|
||||
TEST_ERROR (t, ("expected at least %d acquisitions via "
|
||||
"nsync_mu_rlock(), got %d\n",
|
||||
expected_lo, rlock_acquires));
|
||||
}
|
||||
}
|
||||
|
||||
nsync_mu_lock (&sd.mu);
|
||||
sd.cancel = 1; /* Tell threads to exit. */
|
||||
nsync_mu_unlock (&sd.mu);
|
||||
|
||||
nsync_mu_lock (&sd.control_mu);
|
||||
nsync_mu_wait (&sd.control_mu, &done, &sd, NULL); /* wait for exit. */
|
||||
nsync_mu_unlock (&sd.control_mu);
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_starve_with_readers);
|
||||
TEST_RUN (tb, test_starve_with_writer);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
|
@ -16,995 +16,17 @@
|
|||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/weirdtypes.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/fmt/fmt.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/stdio/temp.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "third_party/nsync/atomic.internal.h"
|
||||
#include "third_party/nsync/cv.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/races.internal.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
// clang-format off
|
||||
|
||||
/**
|
||||
* @fileoverview *NSYNC Mutex Tests / Benchmarks.
|
||||
*
|
||||
* make -j8 o//examples/mu_test.com
|
||||
* o//examples/mu_test.com -b
|
||||
*
|
||||
*/
|
||||
|
||||
typedef struct testing_base_s *testing_base;
|
||||
typedef const struct testing_base_s *const_testing_base;
|
||||
typedef struct testing_s *testing;
|
||||
|
||||
/* Return a newly initialized testing_base. */
|
||||
testing_base testing_new (int argc, char *argv[], int flags);
|
||||
|
||||
/* Return the index of the first argument in argv[] not processed by testing_new() */
|
||||
int testing_base_argn (testing_base tb);
|
||||
|
||||
/* exit() with the test's exit status */
|
||||
int testing_base_exit (testing_base tb);
|
||||
|
||||
/* Stop and start the benchmark timer. */
|
||||
void testing_stop_timer (testing t);
|
||||
void testing_start_timer (testing t);
|
||||
|
||||
/* Return whether the machine appears to be a uniprocessor.
|
||||
Some tests get different results on uniprocessors, because
|
||||
the probability of certain interleavings of thread actions is
|
||||
greatly reduced. */
|
||||
int testing_is_uniprocessor (testing t);
|
||||
|
||||
/* Given a testing_base, run f (t), where t has type testing.
|
||||
Output will be for a test. */
|
||||
#define TEST_RUN(tb, f) testing_run_ ((tb), &f, #f, 0)
|
||||
|
||||
/* Given a testing_base, run f (t), where t has type testing.
|
||||
Output will be for a benchmark, which should iterate testing_n (t) times. */
|
||||
#define BENCHMARK_RUN(tb, f) testing_run_ ((tb), &f, #f, 1)
|
||||
|
||||
/* Return the iteration count for a benchmark. */
|
||||
int testing_n (testing t);
|
||||
|
||||
/* Output nul-terminated string msg[] to stderr, then abort(). */
|
||||
void testing_panic (const char *msg);
|
||||
|
||||
/* Return a value below 0 if tests should run short, 0 if normal, and a value exceeding
|
||||
0 if tests should run long. */
|
||||
int testing_longshort (testing t);
|
||||
|
||||
/* Return non-zero if the user requested verbose output. */
|
||||
int testing_verbose (testing t);
|
||||
|
||||
/* Output a printf-formated log message associated with *t.
|
||||
Example: TEST_LOG (t, ("wombat %d", some_int));
|
||||
The TEST_ERROR() and TEST_FATAL() forms of the call makr the test as failing.
|
||||
The TEST_FATAL() form causes other subtests not to run. */
|
||||
#define TEST_LOG(t, args) testing_error_ ((t), 0, __FILE__, __LINE__, smprintf args);
|
||||
#define TEST_ERROR(t, args) testing_error_ ((t), 1, __FILE__, __LINE__, smprintf args);
|
||||
#define TEST_FATAL(t, args) testing_error_ ((t), 2, __FILE__, __LINE__, smprintf args);
|
||||
|
||||
/* ---------------------------------------- */
|
||||
|
||||
/* internal details follow */
|
||||
|
||||
/* An internal routine used by TEST_RUN() and BENCHMARK_RUN(). */
|
||||
void testing_run_ (testing_base tb, void (*f) (testing t), const char *name, int is_benchmark);
|
||||
|
||||
/* Output an error message msg, and record status. */
|
||||
void testing_error_ (testing t, int status, const char *file, int line, char *msg);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// time_extra.h
|
||||
|
||||
/* Return a malloced nul-terminated string representing time t, using
|
||||
"decimals" decimal places. */
|
||||
char *nsync_time_str (nsync_time t, int decimals);
|
||||
|
||||
/* Sleep until the specified time. Returns 0 on success, and EINTR
|
||||
if the call was interrupted. */
|
||||
int nsync_time_sleep_until (nsync_time abs_deadline);
|
||||
|
||||
/* Return t as a double. */
|
||||
double nsync_time_to_dbl (nsync_time t);
|
||||
|
||||
/* Return a time corresponding to double d. */
|
||||
nsync_time nsync_time_from_dbl (double d);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// smprintf.h
|
||||
|
||||
char *smprintf (const char *fmt, ...);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// smprintf.c
|
||||
|
||||
char *smprintf (const char *fmt, ...) {
|
||||
int m = strlen (fmt) * 2 + 1;
|
||||
char *buf = (char *) malloc (m);
|
||||
int didnt_fit;
|
||||
do {
|
||||
va_list ap;
|
||||
int x;
|
||||
va_start (ap, fmt);
|
||||
x = vsnprintf (buf, m, fmt, ap);
|
||||
va_end (ap);
|
||||
if (x >= m) {
|
||||
buf = (char *) realloc (buf, m = x+1);
|
||||
didnt_fit = 1;
|
||||
} else if (x < 0 || x == m-1) {
|
||||
buf = (char *) realloc (buf, m *= 2);
|
||||
didnt_fit = 1;
|
||||
} else {
|
||||
didnt_fit = 0;
|
||||
}
|
||||
} while (didnt_fit);
|
||||
return (buf);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// time_extra.c
|
||||
|
||||
char *nsync_time_str (nsync_time t, int decimals) {
|
||||
static const struct {
|
||||
const char *suffix;
|
||||
double multiplier;
|
||||
} scale[] = {
|
||||
{ "ns", 1.0e-9, },
|
||||
{ "us", 1e-6, },
|
||||
{ "ms", 1e-3, },
|
||||
{ "s", 1.0, },
|
||||
{ "hr", 3600.0, },
|
||||
};
|
||||
double s = nsync_time_to_dbl (t);
|
||||
int i = 0;
|
||||
while (i + 1 != sizeof (scale) / sizeof (scale[0]) && scale[i + 1].multiplier <= s) {
|
||||
i++;
|
||||
}
|
||||
return (smprintf ("%.*f%s", decimals, s/scale[i].multiplier, scale[i].suffix));
|
||||
}
|
||||
|
||||
int nsync_time_sleep_until (nsync_time abs_deadline) {
|
||||
int result = 0;
|
||||
nsync_time now;
|
||||
now = nsync_time_now ();
|
||||
if (nsync_time_cmp (abs_deadline, now) > 0) {
|
||||
nsync_time remaining;
|
||||
remaining = nsync_time_sleep (nsync_time_sub (abs_deadline, now));
|
||||
if (nsync_time_cmp (remaining, nsync_time_zero) > 0) {
|
||||
result = EINTR;
|
||||
}
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
double nsync_time_to_dbl (nsync_time t) {
|
||||
return (((double) NSYNC_TIME_SEC (t)) + ((double) NSYNC_TIME_NSEC (t) * 1e-9));
|
||||
}
|
||||
|
||||
nsync_time nsync_time_from_dbl (double d) {
|
||||
time_t s = (time_t) d;
|
||||
if (d < s) {
|
||||
s--;
|
||||
}
|
||||
return (nsync_time_s_ns (s, (unsigned) ((d - (double) s) * 1e9)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// start_thread.c
|
||||
|
||||
struct thd_args {
|
||||
void (*f) (void *);
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static void *body (void *v) {
|
||||
struct thd_args *args = (struct thd_args *) v;
|
||||
(*args->f) (args->arg);
|
||||
free (args);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void nsync_start_thread_ (void (*f) (void *), void *arg) {
|
||||
struct thd_args *args = (struct thd_args *) malloc (sizeof (*args));
|
||||
pthread_t t;
|
||||
args->f = f;
|
||||
args->arg = arg;
|
||||
pthread_create (&t, NULL, body, args);
|
||||
pthread_detach (t);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// closure.h
|
||||
|
||||
/* A run-once, self-freeing closure. */
|
||||
typedef struct closure_s {
|
||||
void (*f0) (void *);
|
||||
} closure;
|
||||
|
||||
/* Run the closure *cl, and free it. */
|
||||
void closure_run (closure *cl);
|
||||
|
||||
/* Fork a new thread running the closure *cl, which will be freed when the
|
||||
thread exits. */
|
||||
void closure_fork (closure *cl);
|
||||
|
||||
/* To create a closure, declare a closure constructor with the right function arguments.
|
||||
|
||||
For functions taking no arguments, use
|
||||
CLOSURE_DECL_BODY0 (foo)
|
||||
to generate the static routine:
|
||||
static closure *closure_foo (void (*f) (void));
|
||||
that will return a closure for any function *f that takes no argument.
|
||||
|
||||
For an 1-argument function, use
|
||||
CLOSURE_DECL_BODY1 (foo, type)
|
||||
to generate the static routine:
|
||||
static closure *closure_foo (void (*f) (type), type x);
|
||||
that will return a closure for any function taking a single argument of the
|
||||
specified type.
|
||||
|
||||
For an 2-argument function, use
|
||||
CLOSURE_DECL_BODY2 (foo, type0, type1)
|
||||
to generate the static routine:
|
||||
static closure *closure_foo (void (*f) (type0, type1), type0 x0, type1 x1);
|
||||
that will return a closure for any function taking a "type0" argument, and
|
||||
a "type1" argument.
|
||||
|
||||
And so on, up to 9 arguments.
|
||||
|
||||
For example, to make closures out of qsort():
|
||||
|
||||
// First, just once (per module) define:
|
||||
// static closure *closure_qsort_args (
|
||||
// void (*f) (void *, size_t, size_t, int (*)(const void *, const void *))
|
||||
// void *x0, size_t x1, size_t x2, int (*x3)(const void *, const void *));
|
||||
// by writing:
|
||||
CLOSURE_DECL_BODY4 (qsort_args, void *, size_t, size_t, int (*)(const void *, const void *))
|
||||
|
||||
// Second, for each closure to be created, write something like this:
|
||||
closure *cl = closure_qsort_args (array, n_elements, sizeof (array[0]), &elem_cmp);
|
||||
|
||||
// Then to run (and free) each closure:
|
||||
closure_run (cl);
|
||||
// This is like calling
|
||||
// qsort (array, n_elements, sizeof (array[0]), &elem_cmp);
|
||||
// free (cl);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal macro details follow. */
|
||||
#define CLOSURE_S0(x,e) e
|
||||
#define CLOSURE_S1(x,e) x##0
|
||||
#define CLOSURE_S2(x,e) x##0, x##1
|
||||
#define CLOSURE_S3(x,e) x##0, x##1, x##2
|
||||
#define CLOSURE_S4(x,e) x##0, x##1, x##2, x##3
|
||||
#define CLOSURE_S5(x,e) x##0, x##1, x##2, x##3, x##4
|
||||
#define CLOSURE_S6(x,e) x##0, x##1, x##2, x##3, x##4, x##5
|
||||
#define CLOSURE_S7(x,e) x##0, x##1, x##2, x##3, x##4, x##5, x##6
|
||||
#define CLOSURE_S8(x,e) x##0, x##1, x##2, x##3, x##4, x##5, x##6, x##7
|
||||
#define CLOSURE_S9(x,e) x##0, x##1, x##2, x##3, x##4, x##5, x##6, x##7, x##8
|
||||
|
||||
#define CLOSURE_P0(x,y,p,s,t)
|
||||
#define CLOSURE_P1(x,y,p,s,t) p x##0 y##0 t
|
||||
#define CLOSURE_P2(x,y,p,s,t) p x##0 y##0 s x##1 y##1 t
|
||||
#define CLOSURE_P3(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 t
|
||||
#define CLOSURE_P4(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 t
|
||||
#define CLOSURE_P5(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 t
|
||||
#define CLOSURE_P6(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 t
|
||||
#define CLOSURE_P7(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 s x##6 y##6 t
|
||||
#define CLOSURE_P8(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 s x##6 y##6 s x##7 y##7 t
|
||||
#define CLOSURE_P9(x,y,p,s,t) p x##0 y##0 s x##1 y##1 s x##2 y##2 s x##3 y##3 s x##4 y##4 s \
|
||||
x##5 y##5 s x##6 y##6 s x##7 y##7 s x##8 y##8 t
|
||||
|
||||
#define CLOSURE_COMMA_ ,
|
||||
#define CLOSURE_SEMI_ ;
|
||||
#define CLOSURE_BLANK_
|
||||
|
||||
#define CLOSURE_DECL_BODY_N_(name, n) \
|
||||
struct closure_s_##name { \
|
||||
void (*f0) (void *); /* must be first; matches closure. */ \
|
||||
void (*f) (CLOSURE_S##n (closure_t_##name##_,void)); \
|
||||
CLOSURE_P##n (closure_t_##name##_, a, CLOSURE_BLANK_, \
|
||||
CLOSURE_SEMI_, CLOSURE_SEMI_) \
|
||||
}; \
|
||||
static void closure_f0_##name (void *v) { \
|
||||
struct closure_s_##name *a = (struct closure_s_##name *) v; \
|
||||
(*a->f) (CLOSURE_S##n (a->a,CLOSURE_BLANK_)); \
|
||||
free (a); \
|
||||
} \
|
||||
static closure *closure_##name (void (*f) (CLOSURE_S##n (closure_t_##name##_,void)) \
|
||||
CLOSURE_P##n (closure_t_##name##_, a, CLOSURE_COMMA_, \
|
||||
CLOSURE_COMMA_, CLOSURE_BLANK_)) { \
|
||||
struct closure_s_##name *cl = (struct closure_s_##name *) malloc (sizeof (*cl)); \
|
||||
cl->f0 = &closure_f0_##name; \
|
||||
cl->f = f; \
|
||||
CLOSURE_P##n (cl->a, = a, CLOSURE_BLANK_, CLOSURE_SEMI_, CLOSURE_SEMI_) \
|
||||
return ((closure *) cl); \
|
||||
}
|
||||
|
||||
|
||||
#define CLOSURE_DECL_BODY0(name) \
|
||||
CLOSURE_DECL_BODY_N_ (name, 0)
|
||||
#define CLOSURE_DECL_BODY1(name, t0) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 1)
|
||||
#define CLOSURE_DECL_BODY2(name, t0, t1) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 2)
|
||||
#define CLOSURE_DECL_BODY3(name, t0, t1, t2) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 3)
|
||||
#define CLOSURE_DECL_BODY4(name, t0, t1, t2, t3) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 4)
|
||||
#define CLOSURE_DECL_BODY5(name, t0, t1, t2, t3, t4) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 5)
|
||||
#define CLOSURE_DECL_BODY6(name, t0, t1, t2, t3, t4, t5) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 6)
|
||||
#define CLOSURE_DECL_BODY7(name, t0, t1, t2, t3, t4, t5, t6) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
typedef t6 closure_t_##name##_6; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 7)
|
||||
#define CLOSURE_DECL_BODY8(name, t0, t1, t2, t3, t4, t5, t6, t7) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
typedef t6 closure_t_##name##_6; \
|
||||
typedef t7 closure_t_##name##_7; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 8)
|
||||
#define CLOSURE_DECL_BODY9(name, t0, t1, t2, t3, t4, t5, t6, t7, t8) \
|
||||
typedef t0 closure_t_##name##_0; \
|
||||
typedef t1 closure_t_##name##_1; \
|
||||
typedef t2 closure_t_##name##_2; \
|
||||
typedef t3 closure_t_##name##_3; \
|
||||
typedef t4 closure_t_##name##_4; \
|
||||
typedef t5 closure_t_##name##_5; \
|
||||
typedef t6 closure_t_##name##_6; \
|
||||
typedef t7 closure_t_##name##_7; \
|
||||
typedef t8 closure_t_##name##_8; \
|
||||
CLOSURE_DECL_BODY_N_ (name, 9)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// closure.c
|
||||
|
||||
void nsync_start_thread_ (void (*f) (void *), void *arg);
|
||||
|
||||
/* Run the closure *cl. */
|
||||
void closure_run (closure *cl) {
|
||||
(*cl->f0) (cl);
|
||||
}
|
||||
|
||||
/* Run the closure (closure *), but wrapped to fix the type. */
|
||||
static void closure_run_body (void *v) {
|
||||
closure_run ((closure *)v);
|
||||
}
|
||||
|
||||
void closure_fork (closure *cl) {
|
||||
nsync_start_thread_ (&closure_run_body, cl);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// atm_log.h
|
||||
|
||||
void nsync_atm_log_ (int c, void *p, uint32_t o, uint32_t n, const char *file, int line);
|
||||
void nsync_atm_log_print_ (void);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// atm_log.c
|
||||
|
||||
#ifndef NSYNC_ATM_LOG
|
||||
#define NSYNC_ATM_LOG 0
|
||||
#endif
|
||||
|
||||
struct atm_log {
|
||||
uintptr_t i;
|
||||
uintptr_t thd_id;
|
||||
uintptr_t c;
|
||||
void *p;
|
||||
uintptr_t o;
|
||||
uintptr_t n;
|
||||
const char *file;
|
||||
uintptr_t line;
|
||||
};
|
||||
|
||||
#define LOG_N 14
|
||||
|
||||
static struct atm_log log_entries[1 << LOG_N];
|
||||
static uint32_t log_i;
|
||||
|
||||
static pthread_mutex_t log_mu;
|
||||
|
||||
static pthread_key_t key;
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
static void do_once (void) {
|
||||
pthread_mutex_init (&log_mu, NULL);
|
||||
pthread_key_create (&key, NULL);
|
||||
}
|
||||
static int thread_id;
|
||||
|
||||
void nsync_atm_log_ (int c, void *p, uint32_t o, uint32_t n, const char *file, int line) {
|
||||
if (NSYNC_ATM_LOG) {
|
||||
struct atm_log *e;
|
||||
uint32_t i;
|
||||
int *pthd_id;
|
||||
int thd_id;
|
||||
|
||||
pthread_once (&once, &do_once);
|
||||
pthd_id = (int *) pthread_getspecific (key);
|
||||
|
||||
pthread_mutex_lock (&log_mu);
|
||||
i = log_i++;
|
||||
if (pthd_id == NULL) {
|
||||
thd_id = thread_id++;
|
||||
pthd_id = (int *) malloc (sizeof (*pthd_id));
|
||||
pthread_setspecific (key, pthd_id);
|
||||
*pthd_id = thd_id;
|
||||
} else {
|
||||
thd_id = *pthd_id;
|
||||
}
|
||||
pthread_mutex_unlock (&log_mu);
|
||||
|
||||
e = &log_entries[i & ((1 << LOG_N) - 1)];
|
||||
e->i = i;
|
||||
e->thd_id = thd_id;
|
||||
e->c = c;
|
||||
e->p = p;
|
||||
e->o = o;
|
||||
e->n = n;
|
||||
e->file = file;
|
||||
e->line = line;
|
||||
}
|
||||
}
|
||||
|
||||
void nsync_atm_log_print_ (void) {
|
||||
if (NSYNC_ATM_LOG) {
|
||||
uint32_t i;
|
||||
pthread_once (&once, &do_once);
|
||||
pthread_mutex_lock (&log_mu);
|
||||
for (i = 0; i != (1 << LOG_N); i++) {
|
||||
struct atm_log *e = &log_entries[i];
|
||||
if (e->file != 0) {
|
||||
fprintf (stderr, "%6lx %3d %c p %16p o %8x n %8x %10s:%d\n",
|
||||
(unsigned long) e->i,
|
||||
(int) e->thd_id,
|
||||
e->c <= ' '? '?' : (char)e->c,
|
||||
e->p,
|
||||
(uint32_t) e->o,
|
||||
(uint32_t) e->n,
|
||||
e->file,
|
||||
(int) e->line);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock (&log_mu);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// testing.c
|
||||
|
||||
struct testing_base_s {
|
||||
int flags; /* flags from testing_new(); r/o after init */
|
||||
int parallelism; /* max parallelism to use; r/o after init */
|
||||
FILE *fp; /* where to send output; pointer is r/o after init */
|
||||
int argn; /* first arg not processed by testing_new(); r/o after init */
|
||||
int argc; /* argc passed to testing_new(); r/o after init */
|
||||
char **argv; /* argv passed to testing_new(); r/o after init */
|
||||
char *prog; /* name of programme, from argv[0] */
|
||||
int suppress_header; /* supress hreader on benchmarks */
|
||||
int run_tests; /* whether to run tests */
|
||||
int run_benchmarks; /* whether to run benchmarks */
|
||||
int benchmarks_running; /* and benchmarks are now running */
|
||||
char *include_pat; /* ,- or |-separated substrings of tests to include */
|
||||
char *exclude_pat; /* ,- or |-separated substrings of tests to exclude */
|
||||
int longshort; /* 0 normal, -1 short, 1 long */
|
||||
int verbose; /* 0 normal; 1 verbose output */
|
||||
|
||||
nsync_mu testing_mu; /* protects fields below */
|
||||
int is_uniprocessor; /* whether the system is a uniprocessor */
|
||||
nsync_dll_list_ children; /* list of testing_s structs whose base is this testing_base_s */
|
||||
int child_count; /* count of testing_s structs whose base is this testing_base_s */
|
||||
int exit_status; /* final exit status */
|
||||
};
|
||||
|
||||
struct testing_s {
|
||||
struct testing_base_s *base; /* r/o after init */
|
||||
int test_status; /* status; merged into common->exit_status */
|
||||
int n; /* benchmark iteration count */
|
||||
nsync_atomic_uint32_ partial_line; /* whether partial test banner emitted last*/
|
||||
FILE *fp; /* where to output; merged into common->fp if != to it */
|
||||
nsync_time start_time; /* timer start time; for benchmarks */
|
||||
nsync_time stop_time; /* when the timer was stopped; for benchmarks */
|
||||
void (*f) (testing); /* test function to run */
|
||||
const char *name; /* name of test */
|
||||
nsync_dll_element_ siblings; /* part of list of siblings */
|
||||
};
|
||||
|
||||
/* Output the header for benchmarks. */
|
||||
static void output_header (FILE *fp, const char *prog) {
|
||||
int i;
|
||||
int hdrlen = fprintf (fp, "%-10s%-40s %9s %8s %8s %8s\n", "Benchmark", prog, "ops", "time",
|
||||
"ops/sec", "time/op");
|
||||
for (i = 1; i < hdrlen; i++) {
|
||||
putc ('-', fp);
|
||||
}
|
||||
putc ('\n', fp);
|
||||
fflush (fp);
|
||||
}
|
||||
|
||||
/* Process a single flag. *pargn is main's argn */
|
||||
static void process_flag (testing_base tb, int *pargn, int argc, char *argv[], int flag,
|
||||
const char *arg) {
|
||||
switch (flag) {
|
||||
case 'b':
|
||||
tb->run_benchmarks = 1;
|
||||
break;
|
||||
case 'B':
|
||||
tb->run_benchmarks = 1;
|
||||
tb->run_tests = 0;
|
||||
break;
|
||||
case 'H':
|
||||
output_header (stdout, "");
|
||||
exit (0);
|
||||
case 'h':
|
||||
tb->suppress_header = 1;
|
||||
break;
|
||||
case 'l':
|
||||
tb->longshort++;
|
||||
break;
|
||||
case 'm':
|
||||
if (*pargn + 1 == argc) {
|
||||
fprintf (stderr, "%s: -m flag expects ,- or |-separated strings\n",
|
||||
argv[0]);
|
||||
exit (2);
|
||||
}
|
||||
tb->include_pat = argv[++*pargn];
|
||||
break;
|
||||
case 'n':
|
||||
if (*pargn + 1 == argc || atoi (argv[1 + *pargn]) < 1) {
|
||||
fprintf (stderr, "%s: -n flag expects parallelism value >= 1\n", argv[0]);
|
||||
exit (2);
|
||||
}
|
||||
tb->parallelism = atoi (argv[++*pargn]);
|
||||
break;
|
||||
case 's':
|
||||
tb->longshort--;
|
||||
break;
|
||||
case 'v':
|
||||
tb->verbose = 1;
|
||||
break;
|
||||
case 'x':
|
||||
if (*pargn + 1 == argc) {
|
||||
fprintf (stderr, "%s: -x flag expects ,- or |-separated strings\n",
|
||||
argv[0]);
|
||||
exit (2);
|
||||
}
|
||||
tb->exclude_pat = argv[++*pargn];
|
||||
break;
|
||||
default:
|
||||
fprintf (stderr, "%s: unrecognized flag '%c' in arg %d: \"%s\"\n", argv[0], flag,
|
||||
*pargn, arg);
|
||||
exit (2);
|
||||
}
|
||||
}
|
||||
|
||||
testing_base testing_new (int argc, char *argv[], int flags) {
|
||||
static const char sep[] = { '/', '\\' };
|
||||
int i;
|
||||
int argn;
|
||||
testing_base tb = (testing_base)malloc (sizeof (*tb));
|
||||
memset ((void *) tb, 0, sizeof (*tb));
|
||||
tb->flags = flags;
|
||||
tb->fp = stderr;
|
||||
tb->argc = argc;
|
||||
tb->argv = argv;
|
||||
tb->parallelism = 1;
|
||||
tb->run_tests = 1;
|
||||
tb->is_uniprocessor = -1;
|
||||
tb->prog = tb->argv[0];
|
||||
for (i = 0; i != sizeof (sep) / sizeof (sep[0]); i++) {
|
||||
char *last = strrchr (tb->prog, sep[i]);
|
||||
if (last != NULL) {
|
||||
tb->prog = last + 1;
|
||||
}
|
||||
}
|
||||
for (argn = 1; argn != argc && argv[argn][0] == '-' &&
|
||||
strcmp (argv[argn], "--") != 0; argn++) {
|
||||
const char *arg = argv[argn];
|
||||
const char *f;
|
||||
for (f = &arg[1]; *f != 0; f++) {
|
||||
process_flag (tb, &argn, argc, argv, *f, arg);
|
||||
}
|
||||
}
|
||||
tb->argn = argn + (argn != argc && strcmp (argv[argn], "--") == 0);
|
||||
return (tb);
|
||||
}
|
||||
|
||||
int testing_verbose (testing t) {
|
||||
return (t->base->verbose);
|
||||
}
|
||||
|
||||
int testing_longshort (testing t) {
|
||||
return (t->base->longshort);
|
||||
}
|
||||
|
||||
int testing_n (testing t) {
|
||||
return (t->n);
|
||||
}
|
||||
|
||||
int testing_base_argn (testing_base tb) {
|
||||
return (tb->argn);
|
||||
}
|
||||
|
||||
/* Return whether *(int *)v is zero. Used with nsync_mu_wait(). */
|
||||
static int int_is_zero (const void *v) {
|
||||
return (*(const int *)v == 0);
|
||||
}
|
||||
|
||||
int testing_base_exit (testing_base tb) {
|
||||
int exit_status;
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &int_is_zero, &tb->child_count, NULL);
|
||||
exit_status = tb->exit_status;
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
free (tb);
|
||||
exit (exit_status);
|
||||
return (exit_status);
|
||||
}
|
||||
|
||||
/* Cleanup code used after running either a test or a benchmark,
|
||||
called at the end of run_test() and run_benchmark(). */
|
||||
static void finish_run (testing t) {
|
||||
testing_base tb = t->base;
|
||||
fflush (t->fp);
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
if (t->fp != tb->fp) {
|
||||
int c;
|
||||
rewind (t->fp);
|
||||
while ((c = getc (t->fp)) != EOF) {
|
||||
putc (c, tb->fp);
|
||||
}
|
||||
fclose (t->fp);
|
||||
fflush (tb->fp);
|
||||
}
|
||||
if (tb->exit_status < t->test_status) {
|
||||
tb->exit_status = t->test_status;
|
||||
}
|
||||
tb->children = nsync_dll_remove_ (tb->children, &t->siblings);
|
||||
tb->child_count--;
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
free (t);
|
||||
}
|
||||
|
||||
/* Run the test (*t->f)(t), and report on t->fp how long it took and its final
|
||||
status, which is set to non-zero if the test reported errors. */
|
||||
static void run_test (testing t) {
|
||||
testing_base tb = t->base;
|
||||
char *elapsed_str = 0;
|
||||
fprintf (t->fp, "%-25s %-45s ", tb->prog, t->name);
|
||||
fflush (t->fp);
|
||||
ATM_STORE (&t->partial_line, 1);
|
||||
t->test_status = 0;
|
||||
t->n = 0;
|
||||
t->stop_time = nsync_time_zero;
|
||||
t->start_time = nsync_time_now ();
|
||||
(*t->f) (t);
|
||||
elapsed_str = nsync_time_str (nsync_time_sub (nsync_time_now (), t->start_time), 2);
|
||||
if (!ATM_LOAD (&t->partial_line)) {
|
||||
fprintf (t->fp, "%-25s %-45s %s %8s\n", tb->prog, t->name,
|
||||
t->test_status != 0? "failed": "passed", elapsed_str);
|
||||
} else {
|
||||
fprintf (t->fp, "%s %8s\n", t->test_status != 0? "failed": "passed", elapsed_str);
|
||||
}
|
||||
ATM_STORE (&t->partial_line, 0);
|
||||
fflush (t->fp);
|
||||
free (elapsed_str);
|
||||
finish_run (t);
|
||||
}
|
||||
|
||||
/* Run the benchmark (*t->f)(t) repeatedly, specifying successively greater
|
||||
numbers of iterations, measuring how long it takes each time. Eventually,
|
||||
it takes long enough to get a reasonable estimate of how long each iteration
|
||||
takes, which is reported on t->fp. */
|
||||
static void run_benchmark (testing t) {
|
||||
char *elapsed_str = 0;
|
||||
char *time_per_op_str = 0;
|
||||
double elapsed;
|
||||
int n = 1;
|
||||
double target = 2.0;
|
||||
int longshort = testing_longshort (t);
|
||||
if (longshort < 0) {
|
||||
target = 1e-3 * (2000 >> -longshort);
|
||||
} else if (longshort > 0) {
|
||||
target = 1e-3 * (2000 << longshort);
|
||||
}
|
||||
do {
|
||||
int32_t mul;
|
||||
t->test_status = 0;
|
||||
t->n = n;
|
||||
t->stop_time = nsync_time_zero;
|
||||
t->start_time = nsync_time_now ();
|
||||
(*t->f) (t);
|
||||
elapsed = nsync_time_to_dbl (nsync_time_sub (nsync_time_now (), t->start_time));
|
||||
if (elapsed < 1e-1) {
|
||||
elapsed = 1e-1;
|
||||
}
|
||||
mul = (int32_t) (target / elapsed);
|
||||
while (elapsed * mul * 4 < target * 5) {
|
||||
mul++;
|
||||
}
|
||||
if (mul > 1 && elapsed * mul * 2 < target * 3 && n < INT_MAX / mul) {
|
||||
n *= mul;
|
||||
} else if (n < INT_MAX / 2) {
|
||||
n *= 2;
|
||||
}
|
||||
} while (t->test_status == 0 && elapsed < target && n != t->n);
|
||||
elapsed_str = nsync_time_str (nsync_time_from_dbl (elapsed), 2);
|
||||
time_per_op_str = nsync_time_str (nsync_time_from_dbl (elapsed / t->n), 2);
|
||||
fprintf (t->fp, "%-50s %9d %8s %8.2g %8s%s\n", t->name, t->n, elapsed_str,
|
||||
((double)t->n) / elapsed, time_per_op_str,
|
||||
t->test_status != 0 ? " *** failed ***" : "");
|
||||
free (elapsed_str);
|
||||
free (time_per_op_str);
|
||||
finish_run (t);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY1 (testing, testing)
|
||||
|
||||
/* Return whether there's a "spare thread"; that is, whether the current count
|
||||
of child threads is less than the allowed parallelism. */
|
||||
static int spare_thread (const void *v) {
|
||||
const_testing_base tb = (const_testing_base) v;
|
||||
return (tb->child_count < tb->parallelism);
|
||||
}
|
||||
|
||||
/* Return whether nul-terminated string str[] contains a string listed in
|
||||
comma-separated (or vertical bar-separted) strings in nul-terminated string
|
||||
pat[]. A dollar at the end of a string in pat[] matches the end of
|
||||
string in str[]. */
|
||||
static int match (const char *pat, const char *str) {
|
||||
static const char seps[] = ",|";
|
||||
int found = 0;
|
||||
char Xbuf[128];
|
||||
int m = sizeof (Xbuf) - 1;
|
||||
char *mbuf = NULL;
|
||||
char *buf = Xbuf;
|
||||
int i = 0;
|
||||
while (!found && pat[i] != '\0') {
|
||||
int blen = strcspn (&pat[i], seps);
|
||||
int e = i + blen;
|
||||
if (blen > m) {
|
||||
m = blen + 128;
|
||||
buf = mbuf = (char *) realloc (mbuf, m + 1);
|
||||
}
|
||||
memcpy (buf, &pat[i], blen);
|
||||
buf[blen] = '\0';
|
||||
if (blen > 0 && buf[blen - 1] == '$') {
|
||||
int slen = strlen (str);
|
||||
buf[--blen] = 0;
|
||||
found = (slen >= blen &&
|
||||
strcmp (&str[slen-blen], buf) == 0);
|
||||
} else {
|
||||
found = (strstr (str, buf) != NULL);
|
||||
}
|
||||
i = e + strspn (&pat[e], seps);
|
||||
}
|
||||
free (mbuf);
|
||||
return (found);
|
||||
}
|
||||
|
||||
void testing_run_ (testing_base tb, void (*f) (testing t), const char *name, int is_benchmark) {
|
||||
int exit_status;
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
exit_status = tb->exit_status;
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
if (exit_status < 2 &&
|
||||
(!is_benchmark || tb->run_benchmarks) &&
|
||||
(is_benchmark || tb->run_tests) &&
|
||||
(tb->include_pat == NULL || match (tb->include_pat, name)) &&
|
||||
(tb->exclude_pat == NULL || !match (tb->exclude_pat, name))) {
|
||||
testing t = (testing) malloc (sizeof (*t));
|
||||
memset ((void *) t, 0, sizeof (*t));
|
||||
nsync_dll_init_ (&t->siblings, t);
|
||||
t->base = tb;
|
||||
t->f = f;
|
||||
t->name = name;
|
||||
if (tb->parallelism == 1) {
|
||||
t->fp = tb->fp;
|
||||
} else {
|
||||
t->fp = tmpfile ();
|
||||
}
|
||||
if (!is_benchmark) {
|
||||
if (tb->benchmarks_running) {
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &int_is_zero, &tb->child_count, NULL);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
tb->benchmarks_running = 0;
|
||||
}
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &spare_thread, tb, NULL);
|
||||
tb->child_count++;
|
||||
tb->children = nsync_dll_make_last_in_list_ (tb->children, &t->siblings);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
closure_fork (closure_testing (&run_test, t));
|
||||
} else {
|
||||
if (!tb->benchmarks_running) {
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &int_is_zero, &tb->child_count, NULL);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
if (!tb->suppress_header) {
|
||||
output_header (tb->fp, tb->prog);
|
||||
}
|
||||
tb->benchmarks_running = 1;
|
||||
}
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &spare_thread, tb, NULL);
|
||||
tb->child_count++;
|
||||
tb->children = nsync_dll_make_last_in_list_ (tb->children, &t->siblings);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
closure_fork (closure_testing (&run_benchmark, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Used to decide whether the test is running on a uniprocessor. */
|
||||
struct is_uniprocessor_s {
|
||||
double count; /* count of iterations while *state==1 */
|
||||
nsync_atomic_uint32_ done; /* set to 1 when thread finishes */
|
||||
char dummy[256]; /* so structs don't share cache line */
|
||||
};
|
||||
|
||||
/* An anciliary thread that waits until *state is 1, then increments s->count
|
||||
while *state stays 1, and then sets s->done to 1 before exiting. */
|
||||
static void uniprocessor_check (nsync_atomic_uint32_ *state, struct is_uniprocessor_s *s) {
|
||||
IGNORE_RACES_START ();
|
||||
while (ATM_LOAD_ACQ (state) != 1) {
|
||||
}
|
||||
while (ATM_LOAD_ACQ (state) == 1) {
|
||||
s->count++;
|
||||
}
|
||||
ATM_STORE_REL (&s->done, 1);
|
||||
IGNORE_RACES_END ();
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY2 (uniprocessor_check, nsync_atomic_uint32_ *, struct is_uniprocessor_s *)
|
||||
|
||||
/* Return whether the test is running on a uniprocessor.
|
||||
|
||||
Some of the tests rely on interleaving of actions between threads.
|
||||
Particular interleavings are much less likely on uniprocessors, so the tests
|
||||
do not run, or do not declare an error if the system is a uniprocessor.
|
||||
|
||||
Operating systems vary significantly in how one may ask how many procerssors
|
||||
are present, so we use a heuristic based on comparing the timing of a single
|
||||
thread, and two concurrent threads. */
|
||||
int testing_is_uniprocessor (testing t) {
|
||||
int is_uniprocessor;
|
||||
nsync_mu_lock (&t->base->testing_mu);
|
||||
is_uniprocessor = t->base->is_uniprocessor;
|
||||
if (is_uniprocessor == -1) {
|
||||
int i;
|
||||
struct is_uniprocessor_s s[3];
|
||||
nsync_atomic_uint32_ state;
|
||||
for (i = 0; i != 3; i++) {
|
||||
s[i].count = 0.0;
|
||||
s[i].done = 0;
|
||||
}
|
||||
|
||||
ATM_STORE_REL (&state, 0);
|
||||
closure_fork (closure_uniprocessor_check (&uniprocessor_check, &state, &s[0]));
|
||||
nsync_time_sleep (nsync_time_ms (100));
|
||||
ATM_STORE_REL (&state, 1);
|
||||
nsync_time_sleep (nsync_time_ms (400));
|
||||
ATM_STORE_REL (&state, 2);
|
||||
while (!ATM_LOAD_ACQ (&s[0].done)) {
|
||||
}
|
||||
|
||||
ATM_STORE_REL (&state, 0);
|
||||
closure_fork (closure_uniprocessor_check (&uniprocessor_check, &state, &s[1]));
|
||||
closure_fork (closure_uniprocessor_check (&uniprocessor_check, &state, &s[2]));
|
||||
nsync_time_sleep (nsync_time_ms (100));
|
||||
ATM_STORE_REL (&state, 1);
|
||||
nsync_time_sleep (nsync_time_ms (400));
|
||||
ATM_STORE_REL (&state, 2);
|
||||
while (!ATM_LOAD_ACQ (&s[1].done) || !ATM_LOAD_ACQ (&s[2].done)) {
|
||||
}
|
||||
t->base->is_uniprocessor = ((s[1].count + s[2].count) / s[0].count) < 1.7;
|
||||
is_uniprocessor = t->base->is_uniprocessor;
|
||||
}
|
||||
nsync_mu_unlock (&t->base->testing_mu);
|
||||
return (is_uniprocessor);
|
||||
}
|
||||
|
||||
void testing_stop_timer (testing t) {
|
||||
if (nsync_time_cmp (t->stop_time, nsync_time_zero) != 0) {
|
||||
abort ();
|
||||
}
|
||||
t->stop_time = nsync_time_now ();
|
||||
}
|
||||
|
||||
void testing_start_timer (testing t) {
|
||||
if (nsync_time_cmp (t->stop_time, nsync_time_zero) == 0) {
|
||||
abort ();
|
||||
}
|
||||
t->start_time = nsync_time_add (t->start_time,
|
||||
nsync_time_sub (nsync_time_now (), t->stop_time));
|
||||
t->stop_time = nsync_time_zero;
|
||||
}
|
||||
|
||||
void testing_error_ (testing t, int test_status, const char *file, int line, char *msg) {
|
||||
int len = strlen (msg);
|
||||
int addnl = (len == 0 || msg[len - 1] != '\n');
|
||||
if (t->test_status < test_status) {
|
||||
t->test_status = test_status;
|
||||
}
|
||||
if (ATM_LOAD (&t->partial_line)) {
|
||||
ATM_STORE (&t->partial_line, 0);
|
||||
fprintf (t->fp, "\n%s: %s:%d: %s%s",
|
||||
test_status == 2? "fatal": test_status == 1? "error": "info", file, line, msg,
|
||||
addnl? "\n": "");
|
||||
} else {
|
||||
fprintf (t->fp, "%s: %s:%d: %s%s",
|
||||
test_status == 2? "fatal": test_status == 1? "error": "info", file, line, msg,
|
||||
addnl? "\n": "");
|
||||
}
|
||||
free (msg);
|
||||
}
|
||||
|
||||
void nsync_panic_(const char *);
|
||||
|
||||
/* Abort after printing the nul-terminated string s[]. */
|
||||
void testing_panic (const char *s) {
|
||||
nsync_atm_log_print_ ();
|
||||
nsync_panic_ (s);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// mu_test.c
|
||||
|
||||
/* The state shared between the threads in each of the tests below. */
|
||||
typedef struct test_data_s {
|
||||
testing t;
|
181
third_party/nsync/testing/mu_wait_example_test.c
vendored
Normal file
181
third_party/nsync/testing/mu_wait_example_test.c
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/fmt/fmt.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/testing/array.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/heap.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
// clang-format off
|
||||
|
||||
/* Example use of nsync_mu_wait(): A priority queue of strings whose
|
||||
"remove_with_deadline" operation has a deadline. */
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* An array used as a heap of strings. */
|
||||
typedef A_TYPE (const char *) a_string;
|
||||
|
||||
static int str_lt (const char *e0, const char *e1) {
|
||||
return (strcmp (e0, e1) < 0);
|
||||
}
|
||||
|
||||
static void no_set (const char *a, int b) {
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* A priority queue of strings, which emits the lexicographically least string
|
||||
available. */
|
||||
typedef struct string_priority_queue_mu_s {
|
||||
nsync_mu mu; /* protects heap */
|
||||
a_string heap;
|
||||
} string_priority_queue_mu;
|
||||
|
||||
/* A wait condition for non-empty. */
|
||||
static int spq_is_non_empty (const void *v) {
|
||||
const string_priority_queue_mu *q = (const string_priority_queue_mu *) v;
|
||||
return (A_LEN (&q->heap) != 0);
|
||||
}
|
||||
|
||||
/* Adds "s" to the queue *q. */
|
||||
static void string_priority_queue_mu_add (string_priority_queue_mu *q, const char *s) {
|
||||
int alen;
|
||||
nsync_mu_lock (&q->mu);
|
||||
alen = A_LEN (&q->heap);
|
||||
A_PUSH (&q->heap) = s;
|
||||
heap_add (&A (&q->heap, 0), alen, str_lt, no_set, s);
|
||||
nsync_mu_unlock (&q->mu);
|
||||
}
|
||||
|
||||
/* Wait until queue *q is non-empty, then remove a string from its
|
||||
beginning, and return it; or if abs_deadline is reached before the
|
||||
queue becomes non-empty, return NULL. */
|
||||
static const char *string_priority_queue_mu_remove_with_deadline (
|
||||
string_priority_queue_mu *q, nsync_time abs_deadline) {
|
||||
const char *s = NULL;
|
||||
nsync_mu_lock (&q->mu);
|
||||
if (nsync_mu_wait_with_deadline (&q->mu, &spq_is_non_empty, q, NULL,
|
||||
abs_deadline, NULL) == 0) {
|
||||
int alen = A_LEN (&q->heap);
|
||||
if (alen != 0) {
|
||||
s = A (&q->heap, 0);
|
||||
heap_remove (&A (&q->heap, 0), alen, str_lt, no_set, 0);
|
||||
A_DISCARD (&q->heap, 1);
|
||||
}
|
||||
}
|
||||
nsync_mu_unlock (&q->mu);
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Free resources associates with *q */
|
||||
static void string_priority_queue_mu_destroy (string_priority_queue_mu *q) {
|
||||
A_FREE (&q->heap);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Add strings s[0, ..., n-1] to *q, with the specified delay between additions. */
|
||||
static void add_and_wait_mu (string_priority_queue_mu *q,
|
||||
nsync_time delay, int n, const char *s[]) {
|
||||
int i;
|
||||
for (i = 0; i != n; i++) {
|
||||
string_priority_queue_mu_add (q, s[i]);
|
||||
nsync_time_sleep (delay);
|
||||
}
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY4 (add_and_wait_mu, string_priority_queue_mu *,
|
||||
nsync_time, int, const char **)
|
||||
|
||||
typedef A_TYPE (char) a_char;
|
||||
|
||||
static void a_char_append (a_char *a, const char *str) {
|
||||
while (*str != 0) {
|
||||
A_PUSH (a) = *str;
|
||||
str++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the first item from *q and output it on stdout,
|
||||
or output "timeout: <delay>" if no value can be found before "delay" elapses. */
|
||||
static void remove_and_print_mu (string_priority_queue_mu *q, nsync_time delay, a_char *output) {
|
||||
const char *s;
|
||||
if ((s = string_priority_queue_mu_remove_with_deadline (q,
|
||||
nsync_time_add (nsync_time_now (), delay))) != NULL) {
|
||||
a_char_append (output, s);
|
||||
a_char_append (output, "\n");
|
||||
} else {
|
||||
char buf[64];
|
||||
snprintf (buf, sizeof (buf), "timeout %gs\n",
|
||||
nsync_time_to_dbl (delay));
|
||||
a_char_append (output, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Demonstrate the use of nsync_mu_wait() via a priority queue of strings.
|
||||
See the routine string_priority_queue_mu_remove_with_deadline(), above. */
|
||||
static void example_mu_wait (testing t) {
|
||||
static const char *input[] = { "one", "two", "three", "four", "five" };
|
||||
string_priority_queue_mu q;
|
||||
a_char output;
|
||||
static const char *expected =
|
||||
"one\n"
|
||||
"three\n"
|
||||
"two\n"
|
||||
"timeout 0.1s\n"
|
||||
"four\n"
|
||||
"timeout 0.1s\n"
|
||||
"five\n"
|
||||
"timeout 1s\n";
|
||||
|
||||
memset ((void *) &q, 0, sizeof (q));
|
||||
memset (&output, 0, sizeof (output));
|
||||
|
||||
closure_fork (closure_add_and_wait_mu (&add_and_wait_mu, &q, nsync_time_ms (500),
|
||||
NELEM (input), input));
|
||||
|
||||
/* delay: "one", "two", "three"; not yet "four" */
|
||||
nsync_time_sleep (nsync_time_ms (1200));
|
||||
|
||||
remove_and_print_mu (&q, nsync_time_ms (1000), &output); /* "one" */
|
||||
remove_and_print_mu (&q, nsync_time_ms (1000), &output); /* "three" (less than "two") */
|
||||
remove_and_print_mu (&q, nsync_time_ms (1000), &output); /* "two" */
|
||||
remove_and_print_mu (&q, nsync_time_ms (100), &output); /* time out because 1.3 < 0.5*3 */
|
||||
remove_and_print_mu (&q, nsync_time_ms (1000), &output); /* "four" */
|
||||
remove_and_print_mu (&q, nsync_time_ms (100), &output); /* time out because 0.1 < 0.5 */
|
||||
remove_and_print_mu (&q, nsync_time_ms (1000), &output); /* "five" */
|
||||
remove_and_print_mu (&q, nsync_time_ms (1000), &output); /* time out: no more to fetch */
|
||||
|
||||
A_PUSH (&output) = 0;
|
||||
if (strcmp (&A (&output, 0), expected) != 0) {
|
||||
TEST_ERROR (t, ("expected = %s\ngot = %s\n", expected, &A (&output, 0)));
|
||||
}
|
||||
A_FREE (&output);
|
||||
string_priority_queue_mu_destroy (&q);
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, example_mu_wait);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
347
third_party/nsync/testing/mu_wait_test.c
vendored
Normal file
347
third_party/nsync/testing/mu_wait_test.c
vendored
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/note.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
// clang-format off
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
/* A FIFO queue with up to limit elements.
|
||||
The storage for the queue expands as necessary up to limit. */
|
||||
typedef struct mu_queue_s {
|
||||
int limit; /* max value of count---should not be changed after initialization */
|
||||
nsync_mu mu; /* protects fields below */
|
||||
int pos; /* index of first in-use element */
|
||||
int count; /* number of elements in use */
|
||||
void *data[1]; /* in use elements are data[pos, ..., (pos+count-1)%limit] */
|
||||
} mu_queue;
|
||||
|
||||
/* Return a pointer to new mu_queue. */
|
||||
static mu_queue *mu_queue_new (int limit) {
|
||||
mu_queue *q;
|
||||
int size = offsetof (struct mu_queue_s, data) + sizeof (q->data[0]) * limit;
|
||||
q = (mu_queue *) malloc (size);
|
||||
memset ((void *) q, 0, size);
|
||||
q->limit = limit;
|
||||
return (q);
|
||||
}
|
||||
|
||||
static int mu_queue_non_empty (const void *v) {
|
||||
const mu_queue *q = (const mu_queue *) v;
|
||||
return (q->count != 0);
|
||||
}
|
||||
static int mu_queue_non_full (const void *v) {
|
||||
const mu_queue *q = (const mu_queue *) v;
|
||||
return (q->count != q->limit);
|
||||
}
|
||||
|
||||
/* Add v to the end of the FIFO *q and return non-zero, or if the FIFO already
|
||||
has limit elements and continues to do so until abs_deadline, do nothing and
|
||||
return 0. */
|
||||
static int mu_queue_put (mu_queue *q, void *v, nsync_time abs_deadline) {
|
||||
int added = 0;
|
||||
nsync_mu_lock (&q->mu);
|
||||
if (nsync_mu_wait_with_deadline (&q->mu, &mu_queue_non_full,
|
||||
q, NULL, abs_deadline, NULL) == 0) {
|
||||
int i = q->pos + q->count;
|
||||
if (q->count == q->limit) {
|
||||
testing_panic ("q->count == q->limit");
|
||||
}
|
||||
if (q->limit <= i) {
|
||||
i -= q->limit;
|
||||
}
|
||||
q->data[i] = v;
|
||||
q->count++;
|
||||
added = 1;
|
||||
}
|
||||
nsync_mu_unlock (&q->mu);
|
||||
return (added);
|
||||
}
|
||||
|
||||
/* Remove the first value from the front of the FIFO *q and return it,
|
||||
or if the FIFO is empty and continues to be so until abs_deadline,
|
||||
do nothing and return NULL. */
|
||||
static void *mu_queue_get (mu_queue *q, nsync_time abs_deadline) {
|
||||
void *v = NULL;
|
||||
nsync_mu_lock (&q->mu);
|
||||
if (nsync_mu_wait_with_deadline (&q->mu, &mu_queue_non_empty,
|
||||
q, NULL, abs_deadline, NULL) == 0) {
|
||||
if (q->count == 0) {
|
||||
testing_panic ("q->count == 0");
|
||||
}
|
||||
v = q->data[q->pos];
|
||||
q->data[q->pos] = NULL;
|
||||
q->pos++;
|
||||
q->count--;
|
||||
if (q->pos == q->limit) {
|
||||
q->pos = 0;
|
||||
}
|
||||
}
|
||||
nsync_mu_unlock (&q->mu);
|
||||
return (v);
|
||||
}
|
||||
|
||||
/* --------------------------- */
|
||||
|
||||
static char ptr_to_int_c;
|
||||
#define INT_TO_PTR(x) ((x) + &ptr_to_int_c)
|
||||
#define PTR_TO_INT(p) (((char *) (p)) - &ptr_to_int_c)
|
||||
|
||||
/* Put count integers on *q, in the sequence start*3, (start+1)*3, (start+2)*3, .... */
|
||||
static void producer_mu_n (testing t, mu_queue *q, int start, int count) {
|
||||
int i;
|
||||
for (i = 0; i != count; i++) {
|
||||
if (!mu_queue_put (q, INT_TO_PTR ((start+i)*3), nsync_time_no_deadline)) {
|
||||
TEST_FATAL (t, ("mu_queue_put() returned 0 with no deadline"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY4 (producer_mu_n, testing , mu_queue *, int, int)
|
||||
|
||||
/* Get count integers from *q, and check that they are in the
|
||||
sequence start*3, (start+1)*3, (start+2)*3, .... */
|
||||
static void consumer_mu_n (testing t, mu_queue *q, int start, int count) {
|
||||
int i;
|
||||
for (i = 0; i != count; i++) {
|
||||
void *v = mu_queue_get (q, nsync_time_no_deadline);
|
||||
int x;
|
||||
if (v == NULL) {
|
||||
TEST_FATAL (t, ("mu_queue_get() returned 0 with no deadline"));
|
||||
}
|
||||
x = PTR_TO_INT (v);
|
||||
if (x != (start+i)*3) {
|
||||
TEST_FATAL (t, ("mu_queue_get() returned bad value; want %d, got %d",
|
||||
(start+i)*3, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The number of elements passed from producer to consumer in the
|
||||
test_mu_producer_consumer*() tests below. */
|
||||
#define MU_PRODUCER_CONSUMER_N (100000)
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**0. */
|
||||
static void test_mu_producer_consumer0 (testing t) {
|
||||
mu_queue *q = mu_queue_new (1);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**1. */
|
||||
static void test_mu_producer_consumer1 (testing t) {
|
||||
mu_queue *q = mu_queue_new (10);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**2. */
|
||||
static void test_mu_producer_consumer2 (testing t) {
|
||||
mu_queue *q = mu_queue_new (100);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**3. */
|
||||
static void test_mu_producer_consumer3 (testing t) {
|
||||
mu_queue *q = mu_queue_new (1000);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**4. */
|
||||
static void test_mu_producer_consumer4 (testing t) {
|
||||
mu_queue *q = mu_queue_new (10000);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**5. */
|
||||
static void test_mu_producer_consumer5 (testing t) {
|
||||
mu_queue *q = mu_queue_new (100000);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Send a stream of integers from a producer thread to
|
||||
a consumer thread via a queue with limit 10**6. */
|
||||
static void test_mu_producer_consumer6 (testing t) {
|
||||
mu_queue *q = mu_queue_new (1000000);
|
||||
closure_fork (closure_producer_mu_n (&producer_mu_n, t, q, 0, MU_PRODUCER_CONSUMER_N));
|
||||
consumer_mu_n (t, q, 0, MU_PRODUCER_CONSUMER_N);
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* A perpetually false wait condition. */
|
||||
static int false_condition (const void *v) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* The following values control how aggressively we police the timeout. */
|
||||
#define TOO_EARLY_MS 1
|
||||
#define TOO_LATE_MS 100 /* longer, to accommodate scheduling delays */
|
||||
#define TOO_LATE_ALLOWED 25 /* number of iterations permitted to violate too_late */
|
||||
|
||||
/* Check timeouts on a mu wait_with_deadline(). */
|
||||
static void test_mu_deadline (testing t) {
|
||||
int i;
|
||||
int too_late_violations;
|
||||
nsync_mu mu;
|
||||
nsync_time too_early;
|
||||
nsync_time too_late;
|
||||
|
||||
nsync_mu_init (&mu);
|
||||
too_early = nsync_time_ms (TOO_EARLY_MS);
|
||||
too_late = nsync_time_ms (TOO_LATE_MS);
|
||||
too_late_violations = 0;
|
||||
nsync_mu_lock (&mu);;
|
||||
for (i = 0; i != 50; i++) {
|
||||
nsync_time end_time;
|
||||
nsync_time start_time;
|
||||
nsync_time expected_end_time;
|
||||
start_time = nsync_time_now ();
|
||||
expected_end_time = nsync_time_add (start_time, nsync_time_ms (87));
|
||||
if (nsync_mu_wait_with_deadline (&mu, &false_condition, NULL, NULL,
|
||||
expected_end_time, NULL) != ETIMEDOUT) {
|
||||
TEST_FATAL (t, ("nsync_mu_wait() returned non-expired for a timeout"));
|
||||
}
|
||||
end_time = nsync_time_now ();
|
||||
if (nsync_time_cmp (end_time, nsync_time_sub (expected_end_time, too_early)) < 0) {
|
||||
char *elapsed_str = nsync_time_str (nsync_time_sub (expected_end_time, end_time), 2);
|
||||
TEST_ERROR (t, ("nsync_mu_wait() returned %s too early", elapsed_str));
|
||||
free (elapsed_str);
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_add (expected_end_time, too_late), end_time) < 0) {
|
||||
too_late_violations++;
|
||||
}
|
||||
}
|
||||
nsync_mu_unlock (&mu);
|
||||
if (too_late_violations > TOO_LATE_ALLOWED) {
|
||||
TEST_ERROR (t, ("nsync_mu_wait() returned too late %d (> %d) times",
|
||||
too_late_violations, TOO_LATE_ALLOWED));
|
||||
}
|
||||
}
|
||||
|
||||
/* Check cancellations on a mu wait_with_deadline(). */
|
||||
static void test_mu_cancel (testing t) {
|
||||
int i;
|
||||
nsync_time future_time;
|
||||
int too_late_violations;
|
||||
nsync_mu mu;
|
||||
nsync_time too_early;
|
||||
nsync_time too_late;
|
||||
|
||||
nsync_mu_init (&mu);
|
||||
too_early = nsync_time_ms (TOO_EARLY_MS);
|
||||
too_late = nsync_time_ms (TOO_LATE_MS);
|
||||
|
||||
/* The loops below cancel after 87 milliseconds, like the timeout tests above. */
|
||||
|
||||
future_time = nsync_time_add (nsync_time_now (), nsync_time_ms (3600000)); /* test cancels with timeout */
|
||||
|
||||
too_late_violations = 0;
|
||||
nsync_mu_lock (&mu);
|
||||
for (i = 0; i != 50; i++) {
|
||||
nsync_time end_time;
|
||||
nsync_time start_time;
|
||||
nsync_time expected_end_time;
|
||||
int x;
|
||||
nsync_note cancel;
|
||||
|
||||
start_time = nsync_time_now ();
|
||||
expected_end_time = nsync_time_add (start_time, nsync_time_ms (87));
|
||||
cancel = nsync_note_new (NULL, expected_end_time);
|
||||
|
||||
x = nsync_mu_wait_with_deadline (&mu, &false_condition, NULL, NULL,
|
||||
future_time, cancel);
|
||||
if (x != ECANCELED) {
|
||||
TEST_FATAL (t, ("nsync_mu_wait() return non-cancelled (%d) for "
|
||||
"a cancellation; expected %d",
|
||||
x, ECANCELED));
|
||||
}
|
||||
end_time = nsync_time_now ();
|
||||
if (nsync_time_cmp (end_time, nsync_time_sub (expected_end_time, too_early)) < 0) {
|
||||
char *elapsed_str = nsync_time_str (nsync_time_sub (expected_end_time, end_time), 2);
|
||||
TEST_ERROR (t, ("nsync_mu_wait() returned %s too early", elapsed_str));
|
||||
free (elapsed_str);
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_add (expected_end_time, too_late), end_time) < 0) {
|
||||
too_late_violations++;
|
||||
}
|
||||
|
||||
/* Check that an already cancelled wait returns immediately. */
|
||||
start_time = nsync_time_now ();
|
||||
x = nsync_mu_wait_with_deadline (&mu, &false_condition, NULL, NULL,
|
||||
nsync_time_no_deadline, cancel);
|
||||
if (x != ECANCELED) {
|
||||
TEST_FATAL (t, ("nsync_mu_wait() returned non-cancelled for a "
|
||||
"cancellation; expected %d",
|
||||
x, ECANCELED));
|
||||
}
|
||||
end_time = nsync_time_now ();
|
||||
if (nsync_time_cmp (end_time, start_time) < 0) {
|
||||
char *elapsed_str = nsync_time_str (nsync_time_sub (expected_end_time, end_time), 2);
|
||||
TEST_ERROR (t, ("nsync_mu_wait() returned %s too early", elapsed_str));
|
||||
free (elapsed_str);
|
||||
}
|
||||
if (nsync_time_cmp (nsync_time_add (start_time, too_late), end_time) < 0) {
|
||||
too_late_violations++;
|
||||
}
|
||||
nsync_note_free (cancel);
|
||||
}
|
||||
nsync_mu_unlock (&mu);
|
||||
if (too_late_violations > TOO_LATE_ALLOWED) {
|
||||
TEST_ERROR (t, ("nsync_mu_wait() returned too late %d (> %d) times",
|
||||
too_late_violations, TOO_LATE_ALLOWED));
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_mu_producer_consumer0);
|
||||
TEST_RUN (tb, test_mu_producer_consumer1);
|
||||
TEST_RUN (tb, test_mu_producer_consumer2);
|
||||
TEST_RUN (tb, test_mu_producer_consumer3);
|
||||
TEST_RUN (tb, test_mu_producer_consumer4);
|
||||
TEST_RUN (tb, test_mu_producer_consumer5);
|
||||
TEST_RUN (tb, test_mu_producer_consumer6);
|
||||
TEST_RUN (tb, test_mu_deadline);
|
||||
TEST_RUN (tb, test_mu_cancel);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
334
third_party/nsync/testing/note_test.c
vendored
Normal file
334
third_party/nsync/testing/note_test.c
vendored
Normal file
|
@ -0,0 +1,334 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "third_party/nsync/note.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
// clang-format off
|
||||
|
||||
/* Verify the properties of a prenotified note. */
|
||||
static void test_note_prenotified (testing t) {
|
||||
int i;
|
||||
nsync_note n = nsync_note_new (NULL, nsync_time_zero /* prenotified */);
|
||||
nsync_time expiry;
|
||||
expiry = nsync_note_expiry (n);
|
||||
if (nsync_time_cmp (expiry, nsync_time_zero) != 0) {
|
||||
TEST_ERROR (t, ("prenotified note time mismatch 0"));
|
||||
}
|
||||
for (i = 0; i != 2; i++) {
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("prenotified note is not notified (test, %d)", i));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("prenotified note is not notified (poll, %d)", i));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("prenotified note is not notified (infinite wait, %d)", i));
|
||||
}
|
||||
nsync_note_notify (n);
|
||||
}
|
||||
expiry = nsync_note_expiry (n);
|
||||
if (nsync_time_cmp (expiry, nsync_time_zero) != 0) {
|
||||
TEST_ERROR (t, ("prenotified note time mismatch 1"));
|
||||
}
|
||||
nsync_note_free (n);
|
||||
}
|
||||
|
||||
/* Verify the properties of a unnotified note. */
|
||||
static void test_note_unnotified (testing t) {
|
||||
nsync_time start;
|
||||
nsync_time waited;
|
||||
nsync_time deadline;
|
||||
nsync_note n = nsync_note_new (NULL, nsync_time_no_deadline);
|
||||
nsync_time expiry;
|
||||
expiry = nsync_note_expiry (n);
|
||||
if (nsync_time_cmp (expiry, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("unnotified note time mismatch 0"));
|
||||
}
|
||||
|
||||
if (nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("unnotified note is notified (test)"));
|
||||
}
|
||||
if (nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("notified note is notified (poll)"));
|
||||
}
|
||||
start = nsync_time_now ();
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (1000));
|
||||
if (nsync_note_wait (n, deadline)) {
|
||||
TEST_ERROR (t, ("unnotified note is notified (1s wait)"));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("timed wait on unnotified note returned too quickly (1s wait took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("timed wait on unnotified note returned too slowly (1s wait took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
|
||||
nsync_note_notify (n);
|
||||
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("notified note is not notified (test)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("notified note is not notified (poll)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("notified note is not notified (infinite wait)"));
|
||||
}
|
||||
|
||||
expiry = nsync_note_expiry (n);
|
||||
if (nsync_time_cmp (expiry, nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("unnotified note time mismatch 1"));
|
||||
}
|
||||
|
||||
nsync_note_free (n);
|
||||
}
|
||||
|
||||
/* Test expiry on a note. */
|
||||
static void test_note_expiry (testing t) {
|
||||
nsync_time start;
|
||||
nsync_time waited;
|
||||
nsync_time deadline;
|
||||
nsync_note n;
|
||||
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (1000));
|
||||
n = nsync_note_new (NULL, deadline);
|
||||
start = nsync_time_now ();
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("expired note is not notified"));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("note expired too quickly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("timed expired too slowly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (test)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (poll)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (infinite wait)"));
|
||||
}
|
||||
nsync_note_free (n);
|
||||
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (1000));
|
||||
n = nsync_note_new (NULL, deadline);
|
||||
start = nsync_time_now ();
|
||||
while (!nsync_note_is_notified (n)) {
|
||||
nsync_time_sleep (nsync_time_ms (10));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("note expired too quickly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("timed expired too slowly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (test)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (poll)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (infinite wait)"));
|
||||
}
|
||||
nsync_note_free (n);
|
||||
}
|
||||
|
||||
static void notify_at (nsync_note n, nsync_time abs_deadline) {
|
||||
nsync_time_sleep_until (abs_deadline);
|
||||
nsync_note_notify (n);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY2 (notify, nsync_note, nsync_time)
|
||||
|
||||
/* Test notification of a note. */
|
||||
static void test_note_notify (testing t) {
|
||||
nsync_time start;
|
||||
nsync_time waited;
|
||||
nsync_time deadline;
|
||||
nsync_note n;
|
||||
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (10000));
|
||||
n = nsync_note_new (NULL, deadline);
|
||||
closure_fork (closure_notify (¬ify_at, n, nsync_time_add (nsync_time_now (), nsync_time_ms (1000))));
|
||||
start = nsync_time_now ();
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("expired note is not notified"));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("note expired too quickly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("timed expired too slowly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (test)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (poll)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (infinite wait)"));
|
||||
}
|
||||
nsync_note_free (n);
|
||||
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (10000));
|
||||
n = nsync_note_new (NULL, deadline);
|
||||
closure_fork (closure_notify (¬ify_at, n, nsync_time_add (nsync_time_now (), nsync_time_ms (1000))));
|
||||
start = nsync_time_now ();
|
||||
while (!nsync_note_is_notified (n)) {
|
||||
nsync_time_sleep (nsync_time_ms (10));
|
||||
}
|
||||
waited = nsync_time_sub (nsync_time_now (), start);
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (900)) < 0) {
|
||||
TEST_ERROR (t, ("note expired too quickly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (nsync_time_cmp (waited, nsync_time_ms (2000)) > 0) {
|
||||
TEST_ERROR (t, ("timed expired too slowly (1s expiry took %s)",
|
||||
nsync_time_str (waited, 2)));
|
||||
}
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (test)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_zero)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (poll)"));
|
||||
}
|
||||
if (!nsync_note_wait (n, nsync_time_no_deadline)) {
|
||||
TEST_ERROR (t, ("expired note note is not notified (infinite wait)"));
|
||||
}
|
||||
nsync_note_free (n);
|
||||
}
|
||||
|
||||
/* Test notification of parent/child note. */
|
||||
static void test_note_in_tree (testing t) {
|
||||
int i;
|
||||
enum { /* Indexes of nodes that form a heap in the array node[]. */
|
||||
parent_i = 0,
|
||||
focus_i = 1,
|
||||
sibling_i = 2,
|
||||
child0_i = 3,
|
||||
child1_i = 4,
|
||||
nephew0_i = 5,
|
||||
nephew1_i = 6,
|
||||
grandchild00 = 7,
|
||||
grandchild01 = 8,
|
||||
grandchild10 = 9,
|
||||
grandchild11 = 10,
|
||||
|
||||
count_i = 11
|
||||
};
|
||||
nsync_note node[count_i];
|
||||
|
||||
/* Initialize heap structure in the nodes. No deadlines. */
|
||||
node[0] = nsync_note_new (NULL, nsync_time_no_deadline);
|
||||
for (i = 1; i != count_i; i++) {
|
||||
node[i] = nsync_note_new (node[(i-1)/2], nsync_time_no_deadline);
|
||||
}
|
||||
|
||||
/* check that the nodes are not yet notified. */
|
||||
for (i = 0; i != count_i; i++) {
|
||||
if (nsync_note_is_notified (node[i])) {
|
||||
TEST_ERROR (t, ("unnotified note %d is notified", i));
|
||||
}
|
||||
}
|
||||
|
||||
/* Notify the focus node */
|
||||
nsync_note_notify (node[focus_i]);
|
||||
|
||||
/* Check that the right nodes have been notified. */
|
||||
for (i = 0; i != count_i; i++) {
|
||||
int is_notified = nsync_note_is_notified (node[i]);
|
||||
if (i == parent_i || i == sibling_i || i == nephew0_i || i == nephew1_i) {
|
||||
/* Parent, sibling, and nephew nodes should not have been notified. */
|
||||
if (is_notified) {
|
||||
TEST_ERROR (t, ("unnotified note %d is notified", i));
|
||||
}
|
||||
} else if (!is_notified) { /* But the node and its descendents should be. */
|
||||
TEST_ERROR (t, ("notified note %d is not notified", i));
|
||||
}
|
||||
}
|
||||
for (i = 0; i != count_i; i++) {
|
||||
nsync_note_free (node[i]);
|
||||
}
|
||||
|
||||
/* Initialize heap structure in the nodes. The focus node has a 1s deadline. */
|
||||
node[0] = nsync_note_new (NULL, nsync_time_no_deadline);
|
||||
for (i = 1; i != count_i; i++) {
|
||||
nsync_time deadline;
|
||||
deadline = nsync_time_add (nsync_time_now (), nsync_time_ms (1000));
|
||||
if (i != focus_i) {
|
||||
deadline = nsync_time_no_deadline;
|
||||
}
|
||||
node[i] = nsync_note_new (node[(i - 1) / 2], deadline);
|
||||
}
|
||||
|
||||
/* check that the nodes are not yet notified. */
|
||||
for (i = 0; i != count_i; i++) {
|
||||
if (nsync_note_is_notified (node[i])) {
|
||||
TEST_ERROR (t, ("unnotified note %d is notified", i));
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for timer to go off. */
|
||||
nsync_time_sleep (nsync_time_ms (1100));
|
||||
|
||||
/* Check that the right nodes have been notified. */
|
||||
for (i = 0; i != count_i; i++) {
|
||||
int is_notified = nsync_note_is_notified (node[i]);
|
||||
if (i == parent_i || i == sibling_i || i == nephew0_i || i == nephew1_i) {
|
||||
/* Parent, sibling, and nephew nodes should not have been notified. */
|
||||
if (is_notified) {
|
||||
TEST_ERROR (t, ("unnotified note %d is notified", i));
|
||||
}
|
||||
} else if (!is_notified) { /* But the node and its descendents should be. */
|
||||
TEST_ERROR (t, ("notified note %d is not notified", i));
|
||||
}
|
||||
}
|
||||
for (i = 0; i != count_i; i++) {
|
||||
nsync_note_free (node[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_note_prenotified);
|
||||
TEST_RUN (tb, test_note_unnotified);
|
||||
TEST_RUN (tb, test_note_expiry);
|
||||
TEST_RUN (tb, test_note_notify);
|
||||
TEST_RUN (tb, test_note_in_tree);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
165
third_party/nsync/testing/once_test.c
vendored
Normal file
165
third_party/nsync/testing/once_test.c
vendored
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "third_party/nsync/counter.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/once.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
// clang-format off
|
||||
|
||||
/* This tests nsync_once */
|
||||
|
||||
/* Data structure for each test of nsync_once */
|
||||
struct once_test_s {
|
||||
nsync_once once; /* the nsync_once under test */
|
||||
int counter; /* a counter that should be incremented once */
|
||||
nsync_counter done; /* reaches 0 when all threads done */
|
||||
testing t; /* the test handle */
|
||||
};
|
||||
|
||||
/* Per-thread data structure */
|
||||
struct once_test_thread_s {
|
||||
int id; /* thread id */
|
||||
struct once_test_s *s; /* the per-test structure */
|
||||
};
|
||||
|
||||
#define N 4 /* number of threads used per test */
|
||||
static struct once_test_thread_s ott[N]; /* data structure per thread */
|
||||
static nsync_mu ott_s_mu = NSYNC_MU_INIT;
|
||||
|
||||
/* Increment s->counter by a power of two chosen by the thread id. Called
|
||||
via one of the nsync_run_once* calls. */
|
||||
static void once_arg_func (void *v) {
|
||||
struct once_test_thread_s *lott = (struct once_test_thread_s *) v;
|
||||
struct once_test_s *s;
|
||||
nsync_mu_lock (&ott_s_mu);
|
||||
s = lott->s;
|
||||
nsync_mu_unlock (&ott_s_mu);
|
||||
if (s->counter != 0) {
|
||||
TEST_ERROR (s->t, ("once_arg_func found counter!=0"));
|
||||
}
|
||||
s->counter += 1 << (2 * lott->id);
|
||||
}
|
||||
|
||||
/* Call once_arg_func() on the first thread structure. */
|
||||
static void once_func0 (void) {
|
||||
once_arg_func (&ott[0]);
|
||||
}
|
||||
|
||||
/* Call once_arg_func() on the second thread structure. */
|
||||
static void once_func1 (void) {
|
||||
once_arg_func (&ott[1]);
|
||||
}
|
||||
|
||||
/* Pause for a short time, then use one of the nsync_run_once* calls on
|
||||
ott->s->once, chosen using the thread id. This is the body of each test
|
||||
thread. */
|
||||
static void once_thread (struct once_test_thread_s *lott) {
|
||||
struct once_test_s *s;
|
||||
nsync_mu_lock (&ott_s_mu);
|
||||
s = lott->s;
|
||||
nsync_mu_unlock (&ott_s_mu);
|
||||
nsync_time_sleep (nsync_time_s_ns (0, 1 * 1000 * 1000));
|
||||
switch (lott->id & 3) {
|
||||
case 0: nsync_run_once (&s->once, &once_func0); break;
|
||||
case 1: nsync_run_once_spin (&s->once, &once_func1); break;
|
||||
case 2: nsync_run_once_arg (&s->once, &once_arg_func, lott); break;
|
||||
case 3: nsync_run_once_arg_spin (&s->once, &once_arg_func, lott); break;
|
||||
}
|
||||
nsync_counter_add (s->done, -1);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY1 (once_thread, struct once_test_thread_s *)
|
||||
|
||||
/* Test the functionality of nsync_once */
|
||||
static void test_once_run (testing t) {
|
||||
int i;
|
||||
int j;
|
||||
for (j = 0; j != N; j++) {
|
||||
ott[j].id = j;
|
||||
}
|
||||
for (i = 0; i != 250; i++) {
|
||||
struct once_test_s *s =
|
||||
(struct once_test_s *) malloc (sizeof (*s));
|
||||
memset ((void *) s, 0, sizeof (*s));
|
||||
s->counter = 0;
|
||||
s->done = nsync_counter_new (N);
|
||||
s->t = t;
|
||||
for (j = 0; j != N; j++) {
|
||||
nsync_mu_lock (&ott_s_mu);
|
||||
ott[j].s = s;
|
||||
nsync_mu_unlock (&ott_s_mu);
|
||||
}
|
||||
for (j = 0; j != N; j++) {
|
||||
closure_fork (closure_once_thread (&once_thread,
|
||||
&ott[j]));
|
||||
}
|
||||
if (nsync_counter_wait (s->done,
|
||||
nsync_time_no_deadline) != 0) {
|
||||
TEST_ERROR (t, ("s.done not decremented to 0"));
|
||||
}
|
||||
if (s->counter == 0) {
|
||||
TEST_ERROR (t, ("s.counter wasn't incremented"));
|
||||
}
|
||||
/* The counter is expected to be a power of two, because each
|
||||
counter is incremented only via a single nsync_once (so at
|
||||
most one increment should occur) and always by a power of
|
||||
two. */
|
||||
if ((s->counter & (s->counter-1)) != 0) {
|
||||
TEST_ERROR (t, ("s.counter incremented repeatedly: %x",
|
||||
s->counter));
|
||||
}
|
||||
nsync_counter_free (s->done);
|
||||
free (s);
|
||||
}
|
||||
}
|
||||
|
||||
/* Do nothing. */
|
||||
static void no_op (void) {
|
||||
}
|
||||
|
||||
/* Measure the performance of repeated use of nsync_run_once. */
|
||||
static void benchmark_nsync_once (testing t) {
|
||||
static nsync_once o = NSYNC_ONCE_INIT;
|
||||
int n = testing_n (t);
|
||||
int i;
|
||||
for (i = 0; i != n; i++) {
|
||||
nsync_run_once (&o, &no_op);
|
||||
}
|
||||
}
|
||||
|
||||
/* Measure the performance of repeated use of pthread_once. */
|
||||
static void benchmark_native_once (testing t) {
|
||||
static pthread_once_t o = PTHREAD_ONCE_INIT;
|
||||
int n = testing_n (t);
|
||||
int i;
|
||||
for (i = 0; i != n; i++) {
|
||||
pthread_once (&o, &no_op);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_once_run);
|
||||
BENCHMARK_RUN (tb, benchmark_nsync_once);
|
||||
BENCHMARK_RUN (tb, benchmark_native_once);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
389
third_party/nsync/testing/pingpong_test.c
vendored
Normal file
389
third_party/nsync/testing/pingpong_test.c
vendored
Normal file
|
@ -0,0 +1,389 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/thread2.h"
|
||||
#include "third_party/nsync/cv.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/waiter.h"
|
||||
// clang-format off
|
||||
|
||||
/* The benchmarks in this file use various mechanisms to
|
||||
ping-pong back and forth between two threads as they count i from
|
||||
0 to limit.
|
||||
The data structure contains multiple synchronization primitives,
|
||||
but each benchmark uses only those it needs.
|
||||
|
||||
The setting of GOMAXPROCS, and the exact choices of the thread scheduler can
|
||||
have great effect on the timings. */
|
||||
typedef struct ping_pong_s {
|
||||
nsync_mu mu;
|
||||
nsync_cv cv[2];
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_rwlock_t rwmutex;
|
||||
pthread_cond_t cond[2];
|
||||
|
||||
pthread_cond_t done_cond;
|
||||
int outstanding;
|
||||
|
||||
int i;
|
||||
int limit;
|
||||
} ping_pong;
|
||||
|
||||
static void ping_pong_init (ping_pong *pp, int limit) {
|
||||
memset ((void *) pp, 0, sizeof (*pp));
|
||||
pthread_mutex_init (&pp->mutex, NULL);
|
||||
pthread_rwlock_init (&pp->rwmutex, NULL);
|
||||
pthread_cond_init (&pp->cond[0], NULL);
|
||||
pthread_cond_init (&pp->cond[1], NULL);
|
||||
pthread_cond_init (&pp->done_cond, NULL);
|
||||
pp->outstanding = 2;
|
||||
pp->limit = limit;
|
||||
}
|
||||
|
||||
static void ping_pong_done (ping_pong *pp) {
|
||||
pthread_mutex_lock (&pp->mutex);
|
||||
pp->outstanding--;
|
||||
if (pp->outstanding == 0) {
|
||||
pthread_cond_broadcast (&pp->done_cond);
|
||||
}
|
||||
pthread_mutex_unlock (&pp->mutex);
|
||||
}
|
||||
|
||||
static void ping_pong_destroy (ping_pong *pp) {
|
||||
pthread_mutex_lock (&pp->mutex);
|
||||
while (pp->outstanding != 0) {
|
||||
pthread_cond_wait (&pp->done_cond, &pp->mutex);
|
||||
}
|
||||
pthread_mutex_unlock (&pp->mutex);
|
||||
|
||||
pthread_mutex_destroy (&pp->mutex);
|
||||
pthread_rwlock_destroy (&pp->rwmutex);
|
||||
pthread_cond_destroy (&pp->cond[0]);
|
||||
pthread_cond_destroy (&pp->cond[1]);
|
||||
pthread_cond_destroy (&pp->done_cond);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
CLOSURE_DECL_BODY2 (ping_pong, ping_pong *, int)
|
||||
|
||||
/* void pthread_mutex_lock */
|
||||
static void void_pthread_mutex_lock (void *mu) {
|
||||
pthread_mutex_lock ((pthread_mutex_t *) mu);
|
||||
}
|
||||
|
||||
/* void pthread_mutex_unlock */
|
||||
static void void_pthread_mutex_unlock (void *mu) {
|
||||
pthread_mutex_unlock ((pthread_mutex_t *) mu);
|
||||
}
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mutex_cv(). */
|
||||
static void mutex_cv_ping_pong (ping_pong *pp, int parity) {
|
||||
pthread_mutex_lock (&pp->mutex);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
nsync_cv_wait_with_deadline_generic (&pp->cv[parity], &pp->mutex,
|
||||
&void_pthread_mutex_lock,
|
||||
&void_pthread_mutex_unlock,
|
||||
nsync_time_no_deadline, NULL);
|
||||
}
|
||||
pp->i++;
|
||||
nsync_cv_signal (&pp->cv[1-parity]);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock (&pp->mutex);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of pthread_mutex_t/nsync_cv used to ping-pong back and
|
||||
forth between two threads. */
|
||||
static void benchmark_ping_pong_mutex_cv (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mutex_cv_ping_pong, &pp, 0));
|
||||
mutex_cv_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mu_cv(). */
|
||||
static void mu_cv_ping_pong (ping_pong *pp, int parity) {
|
||||
nsync_mu_lock (&pp->mu);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
nsync_cv_wait (&pp->cv[parity], &pp->mu);
|
||||
}
|
||||
pp->i++;
|
||||
nsync_cv_signal (&pp->cv[1-parity]);
|
||||
}
|
||||
nsync_mu_unlock (&pp->mu);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of nsync_mu/nsync_cv used to
|
||||
ping-pong back and forth between two threads. */
|
||||
static void benchmark_ping_pong_mu_cv (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mu_cv_ping_pong, &pp, 0));
|
||||
mu_cv_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mu_cv_unexpired_deadline(). */
|
||||
static void mu_cv_unexpired_deadline_ping_pong (ping_pong *pp, int parity) {
|
||||
nsync_time deadline_in1hour;
|
||||
deadline_in1hour = nsync_time_add (nsync_time_now (), nsync_time_ms (3600000));
|
||||
nsync_mu_lock (&pp->mu);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
nsync_cv_wait_with_deadline (&pp->cv[parity], &pp->mu,
|
||||
deadline_in1hour, NULL);
|
||||
}
|
||||
pp->i++;
|
||||
nsync_cv_signal (&pp->cv[1 - parity]);
|
||||
}
|
||||
nsync_mu_unlock (&pp->mu);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of nsync_mu/nsync_cv used to ping-pong back and forth
|
||||
between two threads, with an unexpired deadline pending. */
|
||||
static void benchmark_ping_pong_mu_cv_unexpired_deadline (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mu_cv_unexpired_deadline_ping_pong, &pp, 0));
|
||||
mu_cv_unexpired_deadline_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
/* even_ping_pong and odd_ping_pong are wait conditions used by mu_ping_pong. */
|
||||
static int even_ping_pong (const void *v) {
|
||||
return ((((const ping_pong *) v)->i & 1) == 0);
|
||||
}
|
||||
|
||||
static int odd_ping_pong (const void *v) {
|
||||
return ((((const ping_pong *) v)->i & 1) == 1);
|
||||
}
|
||||
|
||||
typedef int (*condition_func) (const void *v);
|
||||
static const condition_func condition[] = { &even_ping_pong, &odd_ping_pong };
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mu_unexpired_deadline(). */
|
||||
static void mu_unexpired_deadline_ping_pong (ping_pong *pp, int parity) {
|
||||
nsync_time deadline_in1hour;
|
||||
deadline_in1hour = nsync_time_add (nsync_time_now (), nsync_time_ms (3600000));
|
||||
nsync_mu_lock (&pp->mu);
|
||||
while (pp->i < pp->limit) {
|
||||
nsync_mu_wait_with_deadline (&pp->mu, condition[parity], pp, NULL,
|
||||
deadline_in1hour, NULL);
|
||||
pp->i++;
|
||||
}
|
||||
nsync_mu_unlock (&pp->mu);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of nsync_mu's wait_with_deadline() primitive used to
|
||||
ping-pong back and forth between two threads with an unexpired deadline
|
||||
pending. */
|
||||
static void benchmark_ping_pong_mu_unexpired_deadline (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mu_unexpired_deadline_ping_pong, &pp, 0));
|
||||
mu_unexpired_deadline_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mutex_cond_unexpired_deadline(). */
|
||||
static void mutex_cond_unexpired_deadline_ping_pong (ping_pong *pp, int parity) {
|
||||
struct timespec ts;
|
||||
clock_gettime (CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += 3600;
|
||||
pthread_mutex_lock (&pp->mutex);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
pthread_cond_timedwait (&pp->cond[parity], &pp->mutex, &ts);
|
||||
}
|
||||
pp->i++;
|
||||
pthread_cond_signal (&pp->cond[1-parity]);
|
||||
}
|
||||
pthread_mutex_unlock (&pp->mutex);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of pthread_mutex_t/pthread_cond_t used to ping-pong
|
||||
back and forth between two threads. */
|
||||
static void benchmark_ping_pong_mutex_cond_unexpired_deadline (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mutex_cond_unexpired_deadline_ping_pong, &pp, 0));
|
||||
mutex_cond_unexpired_deadline_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mutex_cond(). */
|
||||
static void mutex_cond_ping_pong (ping_pong *pp, int parity) {
|
||||
pthread_mutex_lock (&pp->mutex);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
pthread_cond_wait (&pp->cond[parity], &pp->mutex);
|
||||
}
|
||||
pp->i++;
|
||||
pthread_cond_signal (&pp->cond[1-parity]);
|
||||
}
|
||||
pthread_mutex_unlock (&pp->mutex);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of pthread_mutex_t/pthread_cond_t used to ping-pong
|
||||
back and forth between two threads. */
|
||||
static void benchmark_ping_pong_mutex_cond (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mutex_cond_ping_pong, &pp, 0));
|
||||
mutex_cond_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_mu(). */
|
||||
static void mu_ping_pong (ping_pong *pp, int parity) {
|
||||
nsync_mu_lock (&pp->mu);
|
||||
while (pp->i < pp->limit) {
|
||||
nsync_mu_wait (&pp->mu, condition[parity], pp, NULL);
|
||||
pp->i++;
|
||||
}
|
||||
nsync_mu_unlock (&pp->mu);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of nsync_mu's conditional
|
||||
critical sections, used to ping-pong back and forth between two threads. */
|
||||
static void benchmark_ping_pong_mu (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&mu_ping_pong, &pp, 0));
|
||||
mu_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* void pthread_rwlock_wrlock */
|
||||
static void void_pthread_rwlock_wrlock (void *mu) {
|
||||
pthread_rwlock_wrlock ((pthread_rwlock_t *) mu);
|
||||
}
|
||||
|
||||
/* void pthread_rwlock_unlock */
|
||||
static void void_pthread_rwlock_unlock (void *mu) {
|
||||
pthread_rwlock_unlock ((pthread_rwlock_t *) mu);
|
||||
}
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_rwmutex_cv(). */
|
||||
static void rw_mutex_cv_ping_pong (ping_pong *pp, int parity) {
|
||||
pthread_rwlock_wrlock (&pp->rwmutex);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
nsync_cv_wait_with_deadline_generic (&pp->cv[parity], &pp->rwmutex,
|
||||
&void_pthread_rwlock_wrlock,
|
||||
&void_pthread_rwlock_unlock,
|
||||
nsync_time_no_deadline, NULL);
|
||||
}
|
||||
pp->i++;
|
||||
nsync_cv_signal (&pp->cv[1-parity]);
|
||||
}
|
||||
pthread_rwlock_unlock (&pp->rwmutex);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of pthread_rwlock_t/nsync_cv used to ping-pong back and
|
||||
forth between two threads. */
|
||||
static void benchmark_ping_pong_rwmutex_cv (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&rw_mutex_cv_ping_pong, &pp, 0));
|
||||
rw_mutex_cv_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
/* Run by each thread in benchmark_ping_pong_wait_n_cv(). */
|
||||
static void wait_n_cv_ping_pong (ping_pong *pp, int parity) {
|
||||
struct nsync_waitable_s waitable;
|
||||
struct nsync_waitable_s *pwaitable = &waitable;
|
||||
waitable.v = &pp->cv[parity];
|
||||
waitable.funcs = &nsync_cv_waitable_funcs;
|
||||
nsync_mu_lock (&pp->mu);
|
||||
while (pp->i < pp->limit) {
|
||||
while ((pp->i & 1) == parity) {
|
||||
nsync_wait_n (&pp->mu, (void (*) (void *)) &nsync_mu_lock,
|
||||
(void (*) (void *)) &nsync_mu_unlock,
|
||||
nsync_time_no_deadline, 1, &pwaitable);
|
||||
}
|
||||
pp->i++;
|
||||
nsync_cv_signal (&pp->cv[1 - parity]);
|
||||
}
|
||||
nsync_mu_unlock (&pp->mu);
|
||||
ping_pong_done (pp);
|
||||
}
|
||||
|
||||
/* Measure the wakeup speed of nsync_mu/nsync_cv using nsync_wait_n, used to
|
||||
ping-pong back and forth between two threads. */
|
||||
static void benchmark_ping_pong_wait_n_cv (testing t) {
|
||||
ping_pong pp;
|
||||
ping_pong_init (&pp, testing_n (t));
|
||||
closure_fork (closure_ping_pong (&wait_n_cv_ping_pong, &pp, 0));
|
||||
wait_n_cv_ping_pong (&pp, 1);
|
||||
ping_pong_destroy (&pp);
|
||||
}
|
||||
|
||||
/* --------------------------------------- */
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mu);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mu_unexpired_deadline);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mu_cv);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mu_cv_unexpired_deadline);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mutex_cond);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mutex_cond_unexpired_deadline);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_mutex_cv);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_rwmutex_cv);
|
||||
BENCHMARK_RUN (tb, benchmark_ping_pong_wait_n_cv);
|
||||
|
||||
return (testing_base_exit (tb));
|
||||
}
|
45
third_party/nsync/testing/smprintf.c
vendored
Normal file
45
third_party/nsync/testing/smprintf.c
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/fmt/fmt.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
// clang-format off
|
||||
|
||||
char *smprintf (const char *fmt, ...) {
|
||||
int m = strlen (fmt) * 2 + 1;
|
||||
char *buf = (char *) malloc (m);
|
||||
int didnt_fit;
|
||||
do {
|
||||
va_list ap;
|
||||
int x;
|
||||
va_start (ap, fmt);
|
||||
x = vsnprintf (buf, m, fmt, ap);
|
||||
va_end (ap);
|
||||
if (x >= m) {
|
||||
buf = (char *) realloc (buf, m = x+1);
|
||||
didnt_fit = 1;
|
||||
} else if (x < 0 || x == m-1) {
|
||||
buf = (char *) realloc (buf, m *= 2);
|
||||
didnt_fit = 1;
|
||||
} else {
|
||||
didnt_fit = 0;
|
||||
}
|
||||
} while (didnt_fit);
|
||||
return (buf);
|
||||
}
|
6
third_party/nsync/testing/smprintf.h
vendored
Normal file
6
third_party/nsync/testing/smprintf.h
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef NSYNC_TESTING_SMPRINTF_H_
|
||||
#define NSYNC_TESTING_SMPRINTF_H_
|
||||
|
||||
char *smprintf(const char *, ...);
|
||||
|
||||
#endif /*NSYNC_TESTING_SMPRINTF_H_*/
|
41
third_party/nsync/testing/start_thread.c
vendored
Normal file
41
third_party/nsync/testing/start_thread.c
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/thread/thread.h"
|
||||
// clang-format off
|
||||
|
||||
struct thd_args {
|
||||
void (*f) (void *);
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static void *body (void *v) {
|
||||
struct thd_args *args = (struct thd_args *) v;
|
||||
(*args->f) (args->arg);
|
||||
free (args);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void nsync_start_thread_ (void (*f) (void *), void *arg) {
|
||||
struct thd_args *args = (struct thd_args *) malloc (sizeof (*args));
|
||||
pthread_t t;
|
||||
args->f = f;
|
||||
args->arg = arg;
|
||||
pthread_create (&t, NULL, body, args);
|
||||
pthread_detach (t);
|
||||
}
|
512
third_party/nsync/testing/testing.c
vendored
Normal file
512
third_party/nsync/testing/testing.c
vendored
Normal file
|
@ -0,0 +1,512 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/log/log.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/stdio/temp.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/atomic.internal.h"
|
||||
#include "third_party/nsync/common.internal.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#include "third_party/nsync/mu_wait.h"
|
||||
#include "third_party/nsync/races.internal.h"
|
||||
#include "third_party/nsync/testing/atm_log.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
// clang-format off
|
||||
|
||||
struct testing_base_s {
|
||||
int flags; /* flags from testing_new(); r/o after init */
|
||||
int parallelism; /* max parallelism to use; r/o after init */
|
||||
FILE *fp; /* where to send output; pointer is r/o after init */
|
||||
int argn; /* first arg not processed by testing_new(); r/o after init */
|
||||
int argc; /* argc passed to testing_new(); r/o after init */
|
||||
char **argv; /* argv passed to testing_new(); r/o after init */
|
||||
char *prog; /* name of programme, from argv[0] */
|
||||
int suppress_header; /* supress hreader on benchmarks */
|
||||
int run_tests; /* whether to run tests */
|
||||
int run_benchmarks; /* whether to run benchmarks */
|
||||
int benchmarks_running; /* and benchmarks are now running */
|
||||
char *include_pat; /* ,- or |-separated substrings of tests to include */
|
||||
char *exclude_pat; /* ,- or |-separated substrings of tests to exclude */
|
||||
int longshort; /* 0 normal, -1 short, 1 long */
|
||||
int verbose; /* 0 normal; 1 verbose output */
|
||||
|
||||
nsync_mu testing_mu; /* protects fields below */
|
||||
int is_uniprocessor; /* whether the system is a uniprocessor */
|
||||
nsync_dll_list_ children; /* list of testing_s structs whose base is this testing_base_s */
|
||||
int child_count; /* count of testing_s structs whose base is this testing_base_s */
|
||||
int exit_status; /* final exit status */
|
||||
};
|
||||
|
||||
struct testing_s {
|
||||
struct testing_base_s *base; /* r/o after init */
|
||||
int test_status; /* status; merged into common->exit_status */
|
||||
int n; /* benchmark iteration count */
|
||||
nsync_atomic_uint32_ partial_line; /* whether partial test banner emitted last*/
|
||||
FILE *fp; /* where to output; merged into common->fp if != to it */
|
||||
nsync_time start_time; /* timer start time; for benchmarks */
|
||||
nsync_time stop_time; /* when the timer was stopped; for benchmarks */
|
||||
void (*f) (testing); /* test function to run */
|
||||
const char *name; /* name of test */
|
||||
nsync_dll_element_ siblings; /* part of list of siblings */
|
||||
};
|
||||
|
||||
/* Output the header for benchmarks. */
|
||||
static void output_header (FILE *fp, const char *prog) {
|
||||
int i;
|
||||
int hdrlen = fprintf (fp, "%-10s%-40s %9s %8s %8s %8s\n", "Benchmark", prog, "ops", "time",
|
||||
"ops/sec", "time/op");
|
||||
for (i = 1; i < hdrlen; i++) {
|
||||
putc ('-', fp);
|
||||
}
|
||||
putc ('\n', fp);
|
||||
fflush (fp);
|
||||
}
|
||||
|
||||
/* Process a single flag. *pargn is main's argn */
|
||||
static void process_flag (testing_base tb, int *pargn, int argc, char *argv[], int flag,
|
||||
const char *arg) {
|
||||
switch (flag) {
|
||||
case 'b':
|
||||
tb->run_benchmarks = 1;
|
||||
break;
|
||||
case 'B':
|
||||
tb->run_benchmarks = 1;
|
||||
tb->run_tests = 0;
|
||||
break;
|
||||
case 'H':
|
||||
output_header (stdout, "");
|
||||
exit (0);
|
||||
case 'h':
|
||||
tb->suppress_header = 1;
|
||||
break;
|
||||
case 'l':
|
||||
tb->longshort++;
|
||||
break;
|
||||
case 'm':
|
||||
if (*pargn + 1 == argc) {
|
||||
fprintf (stderr, "%s: -m flag expects ,- or |-separated strings\n",
|
||||
argv[0]);
|
||||
exit (2);
|
||||
}
|
||||
tb->include_pat = argv[++*pargn];
|
||||
break;
|
||||
case 'n':
|
||||
if (*pargn + 1 == argc || atoi (argv[1 + *pargn]) < 1) {
|
||||
fprintf (stderr, "%s: -n flag expects parallelism value >= 1\n", argv[0]);
|
||||
exit (2);
|
||||
}
|
||||
tb->parallelism = atoi (argv[++*pargn]);
|
||||
break;
|
||||
case 's':
|
||||
tb->longshort--;
|
||||
break;
|
||||
case 'v':
|
||||
tb->verbose = 1;
|
||||
break;
|
||||
case 'x':
|
||||
if (*pargn + 1 == argc) {
|
||||
fprintf (stderr, "%s: -x flag expects ,- or |-separated strings\n",
|
||||
argv[0]);
|
||||
exit (2);
|
||||
}
|
||||
tb->exclude_pat = argv[++*pargn];
|
||||
break;
|
||||
default:
|
||||
fprintf (stderr, "%s: unrecognized flag '%c' in arg %d: \"%s\"\n", argv[0], flag,
|
||||
*pargn, arg);
|
||||
exit (2);
|
||||
}
|
||||
}
|
||||
|
||||
testing_base testing_new (int argc, char *argv[], int flags) {
|
||||
static const char sep[] = { '/', '\\' };
|
||||
int i;
|
||||
int argn;
|
||||
testing_base tb = (testing_base)malloc (sizeof (*tb));
|
||||
ShowCrashReports ();
|
||||
memset ((void *) tb, 0, sizeof (*tb));
|
||||
tb->flags = flags;
|
||||
tb->fp = stderr;
|
||||
tb->argc = argc;
|
||||
tb->argv = argv;
|
||||
tb->parallelism = 1;
|
||||
tb->run_tests = 1;
|
||||
tb->is_uniprocessor = -1;
|
||||
tb->prog = tb->argv[0];
|
||||
for (i = 0; i != sizeof (sep) / sizeof (sep[0]); i++) {
|
||||
char *last = strrchr (tb->prog, sep[i]);
|
||||
if (last != NULL) {
|
||||
tb->prog = last + 1;
|
||||
}
|
||||
}
|
||||
for (argn = 1; argn != argc && argv[argn][0] == '-' &&
|
||||
strcmp (argv[argn], "--") != 0; argn++) {
|
||||
const char *arg = argv[argn];
|
||||
const char *f;
|
||||
for (f = &arg[1]; *f != 0; f++) {
|
||||
process_flag (tb, &argn, argc, argv, *f, arg);
|
||||
}
|
||||
}
|
||||
tb->argn = argn + (argn != argc && strcmp (argv[argn], "--") == 0);
|
||||
return (tb);
|
||||
}
|
||||
|
||||
int testing_verbose (testing t) {
|
||||
return (t->base->verbose);
|
||||
}
|
||||
|
||||
int testing_longshort (testing t) {
|
||||
return (t->base->longshort);
|
||||
}
|
||||
|
||||
int testing_n (testing t) {
|
||||
return (t->n);
|
||||
}
|
||||
|
||||
int testing_base_argn (testing_base tb) {
|
||||
return (tb->argn);
|
||||
}
|
||||
|
||||
/* Return whether *(int *)v is zero. Used with nsync_mu_wait(). */
|
||||
static int int_is_zero (const void *v) {
|
||||
return (*(const int *)v == 0);
|
||||
}
|
||||
|
||||
int testing_base_exit (testing_base tb) {
|
||||
int exit_status;
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &int_is_zero, &tb->child_count, NULL);
|
||||
exit_status = tb->exit_status;
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
free (tb);
|
||||
exit (exit_status);
|
||||
return (exit_status);
|
||||
}
|
||||
|
||||
/* Cleanup code used after running either a test or a benchmark,
|
||||
called at the end of run_test() and run_benchmark(). */
|
||||
static void finish_run (testing t) {
|
||||
testing_base tb = t->base;
|
||||
fflush (t->fp);
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
if (t->fp != tb->fp) {
|
||||
int c;
|
||||
rewind (t->fp);
|
||||
while ((c = getc (t->fp)) != EOF) {
|
||||
putc (c, tb->fp);
|
||||
}
|
||||
fclose (t->fp);
|
||||
fflush (tb->fp);
|
||||
}
|
||||
if (tb->exit_status < t->test_status) {
|
||||
tb->exit_status = t->test_status;
|
||||
}
|
||||
tb->children = nsync_dll_remove_ (tb->children, &t->siblings);
|
||||
tb->child_count--;
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
free (t);
|
||||
}
|
||||
|
||||
/* Run the test (*t->f)(t), and report on t->fp how long it took and its final
|
||||
status, which is set to non-zero if the test reported errors. */
|
||||
static void run_test (testing t) {
|
||||
testing_base tb = t->base;
|
||||
char *elapsed_str = 0;
|
||||
fprintf (t->fp, "%-25s %-45s ", tb->prog, t->name);
|
||||
fflush (t->fp);
|
||||
ATM_STORE (&t->partial_line, 1);
|
||||
t->test_status = 0;
|
||||
t->n = 0;
|
||||
t->stop_time = nsync_time_zero;
|
||||
t->start_time = nsync_time_now ();
|
||||
(*t->f) (t);
|
||||
elapsed_str = nsync_time_str (nsync_time_sub (nsync_time_now (), t->start_time), 2);
|
||||
if (!ATM_LOAD (&t->partial_line)) {
|
||||
fprintf (t->fp, "%-25s %-45s %s %8s\n", tb->prog, t->name,
|
||||
t->test_status != 0? "failed": "passed", elapsed_str);
|
||||
} else {
|
||||
fprintf (t->fp, "%s %8s\n", t->test_status != 0? "failed": "passed", elapsed_str);
|
||||
}
|
||||
ATM_STORE (&t->partial_line, 0);
|
||||
fflush (t->fp);
|
||||
free (elapsed_str);
|
||||
finish_run (t);
|
||||
}
|
||||
|
||||
/* Run the benchmark (*t->f)(t) repeatedly, specifying successively greater
|
||||
numbers of iterations, measuring how long it takes each time. Eventually,
|
||||
it takes long enough to get a reasonable estimate of how long each iteration
|
||||
takes, which is reported on t->fp. */
|
||||
static void run_benchmark (testing t) {
|
||||
char *elapsed_str = 0;
|
||||
char *time_per_op_str = 0;
|
||||
double elapsed;
|
||||
int n = 1;
|
||||
double target = 2.0;
|
||||
int longshort = testing_longshort (t);
|
||||
if (longshort < 0) {
|
||||
target = 1e-3 * (2000 >> -longshort);
|
||||
} else if (longshort > 0) {
|
||||
target = 1e-3 * (2000 << longshort);
|
||||
}
|
||||
do {
|
||||
int32_t mul;
|
||||
t->test_status = 0;
|
||||
t->n = n;
|
||||
t->stop_time = nsync_time_zero;
|
||||
t->start_time = nsync_time_now ();
|
||||
(*t->f) (t);
|
||||
elapsed = nsync_time_to_dbl (nsync_time_sub (nsync_time_now (), t->start_time));
|
||||
if (elapsed < 1e-1) {
|
||||
elapsed = 1e-1;
|
||||
}
|
||||
mul = (int32_t) (target / elapsed);
|
||||
while (elapsed * mul * 4 < target * 5) {
|
||||
mul++;
|
||||
}
|
||||
if (mul > 1 && elapsed * mul * 2 < target * 3 && n < INT_MAX / mul) {
|
||||
n *= mul;
|
||||
} else if (n < INT_MAX / 2) {
|
||||
n *= 2;
|
||||
}
|
||||
} while (t->test_status == 0 && elapsed < target && n != t->n);
|
||||
elapsed_str = nsync_time_str (nsync_time_from_dbl (elapsed), 2);
|
||||
time_per_op_str = nsync_time_str (nsync_time_from_dbl (elapsed / t->n), 2);
|
||||
fprintf (t->fp, "%-50s %9d %8s %8.2g %8s%s\n", t->name, t->n, elapsed_str,
|
||||
((double)t->n) / elapsed, time_per_op_str,
|
||||
t->test_status != 0 ? " *** failed ***" : "");
|
||||
free (elapsed_str);
|
||||
free (time_per_op_str);
|
||||
finish_run (t);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY1 (testing, testing)
|
||||
|
||||
/* Return whether there's a "spare thread"; that is, whether the current count
|
||||
of child threads is less than the allowed parallelism. */
|
||||
static int spare_thread (const void *v) {
|
||||
const_testing_base tb = (const_testing_base) v;
|
||||
return (tb->child_count < tb->parallelism);
|
||||
}
|
||||
|
||||
/* Return whether nul-terminated string str[] contains a string listed in
|
||||
comma-separated (or vertical bar-separted) strings in nul-terminated string
|
||||
pat[]. A dollar at the end of a string in pat[] matches the end of
|
||||
string in str[]. */
|
||||
static int match (const char *pat, const char *str) {
|
||||
static const char seps[] = ",|";
|
||||
int found = 0;
|
||||
char Xbuf[128];
|
||||
int m = sizeof (Xbuf) - 1;
|
||||
char *mbuf = NULL;
|
||||
char *buf = Xbuf;
|
||||
int i = 0;
|
||||
while (!found && pat[i] != '\0') {
|
||||
int blen = strcspn (&pat[i], seps);
|
||||
int e = i + blen;
|
||||
if (blen > m) {
|
||||
m = blen + 128;
|
||||
buf = mbuf = (char *) realloc (mbuf, m + 1);
|
||||
}
|
||||
memcpy (buf, &pat[i], blen);
|
||||
buf[blen] = '\0';
|
||||
if (blen > 0 && buf[blen - 1] == '$') {
|
||||
int slen = strlen (str);
|
||||
buf[--blen] = 0;
|
||||
found = (slen >= blen &&
|
||||
strcmp (&str[slen-blen], buf) == 0);
|
||||
} else {
|
||||
found = (strstr (str, buf) != NULL);
|
||||
}
|
||||
i = e + strspn (&pat[e], seps);
|
||||
}
|
||||
free (mbuf);
|
||||
return (found);
|
||||
}
|
||||
|
||||
void testing_run_ (testing_base tb, void (*f) (testing t), const char *name, int is_benchmark) {
|
||||
int exit_status;
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
exit_status = tb->exit_status;
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
if (exit_status < 2 &&
|
||||
(!is_benchmark || tb->run_benchmarks) &&
|
||||
(is_benchmark || tb->run_tests) &&
|
||||
(tb->include_pat == NULL || match (tb->include_pat, name)) &&
|
||||
(tb->exclude_pat == NULL || !match (tb->exclude_pat, name))) {
|
||||
testing t = (testing) malloc (sizeof (*t));
|
||||
memset ((void *) t, 0, sizeof (*t));
|
||||
nsync_dll_init_ (&t->siblings, t);
|
||||
t->base = tb;
|
||||
t->f = f;
|
||||
t->name = name;
|
||||
if (tb->parallelism == 1) {
|
||||
t->fp = tb->fp;
|
||||
} else {
|
||||
t->fp = tmpfile ();
|
||||
}
|
||||
if (!is_benchmark) {
|
||||
if (tb->benchmarks_running) {
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &int_is_zero, &tb->child_count, NULL);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
tb->benchmarks_running = 0;
|
||||
}
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &spare_thread, tb, NULL);
|
||||
tb->child_count++;
|
||||
tb->children = nsync_dll_make_last_in_list_ (tb->children, &t->siblings);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
closure_fork (closure_testing (&run_test, t));
|
||||
} else {
|
||||
if (!tb->benchmarks_running) {
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &int_is_zero, &tb->child_count, NULL);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
if (!tb->suppress_header) {
|
||||
output_header (tb->fp, tb->prog);
|
||||
}
|
||||
tb->benchmarks_running = 1;
|
||||
}
|
||||
nsync_mu_lock (&tb->testing_mu);
|
||||
nsync_mu_wait (&tb->testing_mu, &spare_thread, tb, NULL);
|
||||
tb->child_count++;
|
||||
tb->children = nsync_dll_make_last_in_list_ (tb->children, &t->siblings);
|
||||
nsync_mu_unlock (&tb->testing_mu);
|
||||
closure_fork (closure_testing (&run_benchmark, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Used to decide whether the test is running on a uniprocessor. */
|
||||
struct is_uniprocessor_s {
|
||||
double count; /* count of iterations while *state==1 */
|
||||
nsync_atomic_uint32_ done; /* set to 1 when thread finishes */
|
||||
char dummy[256]; /* so structs don't share cache line */
|
||||
};
|
||||
|
||||
/* An anciliary thread that waits until *state is 1, then increments s->count
|
||||
while *state stays 1, and then sets s->done to 1 before exiting. */
|
||||
static void uniprocessor_check (nsync_atomic_uint32_ *state, struct is_uniprocessor_s *s) {
|
||||
IGNORE_RACES_START ();
|
||||
while (ATM_LOAD_ACQ (state) != 1) {
|
||||
}
|
||||
while (ATM_LOAD_ACQ (state) == 1) {
|
||||
s->count++;
|
||||
}
|
||||
ATM_STORE_REL (&s->done, 1);
|
||||
IGNORE_RACES_END ();
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY2 (uniprocessor_check, nsync_atomic_uint32_ *, struct is_uniprocessor_s *)
|
||||
|
||||
/* Return whether the test is running on a uniprocessor.
|
||||
|
||||
Some of the tests rely on interleaving of actions between threads.
|
||||
Particular interleavings are much less likely on uniprocessors, so the tests
|
||||
do not run, or do not declare an error if the system is a uniprocessor.
|
||||
|
||||
Operating systems vary significantly in how one may ask how many procerssors
|
||||
are present, so we use a heuristic based on comparing the timing of a single
|
||||
thread, and two concurrent threads. */
|
||||
int testing_is_uniprocessor (testing t) {
|
||||
int is_uniprocessor;
|
||||
nsync_mu_lock (&t->base->testing_mu);
|
||||
is_uniprocessor = t->base->is_uniprocessor;
|
||||
if (is_uniprocessor == -1) {
|
||||
int i;
|
||||
struct is_uniprocessor_s s[3];
|
||||
nsync_atomic_uint32_ state;
|
||||
for (i = 0; i != 3; i++) {
|
||||
s[i].count = 0.0;
|
||||
s[i].done = 0;
|
||||
}
|
||||
|
||||
ATM_STORE_REL (&state, 0);
|
||||
closure_fork (closure_uniprocessor_check (&uniprocessor_check, &state, &s[0]));
|
||||
nsync_time_sleep (nsync_time_ms (100));
|
||||
ATM_STORE_REL (&state, 1);
|
||||
nsync_time_sleep (nsync_time_ms (400));
|
||||
ATM_STORE_REL (&state, 2);
|
||||
while (!ATM_LOAD_ACQ (&s[0].done)) {
|
||||
}
|
||||
|
||||
ATM_STORE_REL (&state, 0);
|
||||
closure_fork (closure_uniprocessor_check (&uniprocessor_check, &state, &s[1]));
|
||||
closure_fork (closure_uniprocessor_check (&uniprocessor_check, &state, &s[2]));
|
||||
nsync_time_sleep (nsync_time_ms (100));
|
||||
ATM_STORE_REL (&state, 1);
|
||||
nsync_time_sleep (nsync_time_ms (400));
|
||||
ATM_STORE_REL (&state, 2);
|
||||
while (!ATM_LOAD_ACQ (&s[1].done) || !ATM_LOAD_ACQ (&s[2].done)) {
|
||||
}
|
||||
t->base->is_uniprocessor = ((s[1].count + s[2].count) / s[0].count) < 1.7;
|
||||
is_uniprocessor = t->base->is_uniprocessor;
|
||||
}
|
||||
nsync_mu_unlock (&t->base->testing_mu);
|
||||
return (is_uniprocessor);
|
||||
}
|
||||
|
||||
void testing_stop_timer (testing t) {
|
||||
if (nsync_time_cmp (t->stop_time, nsync_time_zero) != 0) {
|
||||
abort ();
|
||||
}
|
||||
t->stop_time = nsync_time_now ();
|
||||
}
|
||||
|
||||
void testing_start_timer (testing t) {
|
||||
if (nsync_time_cmp (t->stop_time, nsync_time_zero) == 0) {
|
||||
abort ();
|
||||
}
|
||||
t->start_time = nsync_time_add (t->start_time,
|
||||
nsync_time_sub (nsync_time_now (), t->stop_time));
|
||||
t->stop_time = nsync_time_zero;
|
||||
}
|
||||
|
||||
void testing_error_ (testing t, int test_status, const char *file, int line, char *msg) {
|
||||
int len = strlen (msg);
|
||||
int addnl = (len == 0 || msg[len - 1] != '\n');
|
||||
if (t->test_status < test_status) {
|
||||
t->test_status = test_status;
|
||||
}
|
||||
if (ATM_LOAD (&t->partial_line)) {
|
||||
ATM_STORE (&t->partial_line, 0);
|
||||
fprintf (t->fp, "\n%s: %s:%d: %s%s",
|
||||
test_status == 2? "fatal": test_status == 1? "error": "info", file, line, msg,
|
||||
addnl? "\n": "");
|
||||
} else {
|
||||
fprintf (t->fp, "%s: %s:%d: %s%s",
|
||||
test_status == 2? "fatal": test_status == 1? "error": "info", file, line, msg,
|
||||
addnl? "\n": "");
|
||||
}
|
||||
free (msg);
|
||||
}
|
||||
|
||||
/* Abort after printing the nul-terminated string s[]. */
|
||||
void testing_panic (const char *s) {
|
||||
nsync_atm_log_print_ ();
|
||||
nsync_panic_ (s);
|
||||
}
|
67
third_party/nsync/testing/testing.h
vendored
Normal file
67
third_party/nsync/testing/testing.h
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
#ifndef NSYNC_TESTING_TESTING_H_
|
||||
#define NSYNC_TESTING_TESTING_H_
|
||||
/* clang-format off */
|
||||
|
||||
typedef struct testing_base_s *testing_base;
|
||||
typedef const struct testing_base_s *const_testing_base;
|
||||
typedef struct testing_s *testing;
|
||||
|
||||
/* Return a newly initialized testing_base. */
|
||||
testing_base testing_new (int argc, char *argv[], int flags);
|
||||
|
||||
/* Return the index of the first argument in argv[] not processed by testing_new() */
|
||||
int testing_base_argn (testing_base tb);
|
||||
|
||||
/* exit() with the test's exit status */
|
||||
int testing_base_exit (testing_base tb);
|
||||
|
||||
/* Stop and start the benchmark timer. */
|
||||
void testing_stop_timer (testing t);
|
||||
void testing_start_timer (testing t);
|
||||
|
||||
/* Return whether the machine appears to be a uniprocessor.
|
||||
Some tests get different results on uniprocessors, because
|
||||
the probability of certain interleavings of thread actions is
|
||||
greatly reduced. */
|
||||
int testing_is_uniprocessor (testing t);
|
||||
|
||||
/* Given a testing_base, run f (t), where t has type testing.
|
||||
Output will be for a test. */
|
||||
#define TEST_RUN(tb, f) testing_run_ ((tb), &f, #f, 0)
|
||||
|
||||
/* Given a testing_base, run f (t), where t has type testing.
|
||||
Output will be for a benchmark, which should iterate testing_n (t) times. */
|
||||
#define BENCHMARK_RUN(tb, f) testing_run_ ((tb), &f, #f, 1)
|
||||
|
||||
/* Return the iteration count for a benchmark. */
|
||||
int testing_n (testing t);
|
||||
|
||||
/* Output nul-terminated string msg[] to stderr, then abort(). */
|
||||
void testing_panic (const char *msg);
|
||||
|
||||
/* Return a value below 0 if tests should run short, 0 if normal, and a value exceeding
|
||||
0 if tests should run long. */
|
||||
int testing_longshort (testing t);
|
||||
|
||||
/* Return non-zero if the user requested verbose output. */
|
||||
int testing_verbose (testing t);
|
||||
|
||||
/* Output a printf-formated log message associated with *t.
|
||||
Example: TEST_LOG (t, ("wombat %d", some_int));
|
||||
The TEST_ERROR() and TEST_FATAL() forms of the call makr the test as failing.
|
||||
The TEST_FATAL() form causes other subtests not to run. */
|
||||
#define TEST_LOG(t, args) testing_error_ ((t), 0, __FILE__, __LINE__, smprintf args);
|
||||
#define TEST_ERROR(t, args) testing_error_ ((t), 1, __FILE__, __LINE__, smprintf args);
|
||||
#define TEST_FATAL(t, args) testing_error_ ((t), 2, __FILE__, __LINE__, smprintf args);
|
||||
|
||||
/* ---------------------------------------- */
|
||||
|
||||
/* internal details follow */
|
||||
|
||||
/* An internal routine used by TEST_RUN() and BENCHMARK_RUN(). */
|
||||
void testing_run_ (testing_base tb, void (*f) (testing t), const char *name, int is_benchmark);
|
||||
|
||||
/* Output an error message msg, and record status. */
|
||||
void testing_error_ (testing t, int status, const char *file, int line, char *msg);
|
||||
|
||||
#endif /*NSYNC_TESTING_TESTING_H_*/
|
66
third_party/nsync/testing/testing.mk
vendored
Normal file
66
third_party/nsync/testing/testing.mk
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
|
||||
#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
|
||||
|
||||
PKGS += THIRD_PARTY_NSYNC_TESTING
|
||||
|
||||
THIRD_PARTY_NSYNC_TESTING_LIB = $(THIRD_PARTY_NSYNC_TESTING_DEPS) $(THIRD_PARTY_NSYNC_TESTING_A)
|
||||
THIRD_PARTY_NSYNC_TESTING_A = o/$(MODE)/third_party/nsync/testing/lib.a
|
||||
THIRD_PARTY_NSYNC_TESTING_FILES = $(wildcard third_party/nsync/testing/*)
|
||||
THIRD_PARTY_NSYNC_TESTING_SRCS = $(filter %.c,$(THIRD_PARTY_NSYNC_TESTING_FILES))
|
||||
THIRD_PARTY_NSYNC_TESTING_HDRS = $(filter %.h,$(THIRD_PARTY_NSYNC_TESTING_FILES))
|
||||
THIRD_PARTY_NSYNC_TESTING_SRCS_TEST = $(filter %_test.c,$(THIRD_PARTY_NSYNC_TESTING_SRCS))
|
||||
THIRD_PARTY_NSYNC_TESTING_OBJS = $(THIRD_PARTY_NSYNC_TESTING_SRCS:%.c=o/$(MODE)/%.o)
|
||||
THIRD_PARTY_NSYNC_TESTING_COMS = $(THIRD_PARTY_NSYNC_TESTING_SRCS_TEST:%.c=o/$(MODE)/%.com)
|
||||
THIRD_PARTY_NSYNC_TESTING_BINS = $(THIRD_PARTY_NSYNC_TESTING_COMS) $(THIRD_PARTY_NSYNC_TESTING_COMS:%=%.dbg)
|
||||
# THIRD_PARTY_NSYNC_TESTING_TESTS = $(THIRD_PARTY_NSYNC_TESTING_SRCS_TEST:%.c=o/$(MODE)/%.com.ok)
|
||||
# THIRD_PARTY_NSYNC_TESTING_CHECKS = $(THIRD_PARTY_NSYNC_TESTING_SRCS_TEST:%.c=o/$(MODE)/%.com.runs)
|
||||
|
||||
THIRD_PARTY_NSYNC_TESTING_DIRECTDEPS = \
|
||||
LIBC_CALLS \
|
||||
LIBC_FMT \
|
||||
LIBC_INTRIN \
|
||||
LIBC_LOG \
|
||||
LIBC_MEM \
|
||||
LIBC_NEXGEN32E \
|
||||
LIBC_RUNTIME \
|
||||
LIBC_STDIO \
|
||||
LIBC_STR \
|
||||
LIBC_STUBS \
|
||||
LIBC_SYSV \
|
||||
LIBC_THREAD \
|
||||
THIRD_PARTY_NSYNC \
|
||||
THIRD_PARTY_NSYNC_MEM
|
||||
|
||||
THIRD_PARTY_NSYNC_TESTING_DEPS := \
|
||||
$(call uniq,$(foreach x,$(THIRD_PARTY_NSYNC_TESTING_DIRECTDEPS),$($(x))))
|
||||
|
||||
$(THIRD_PARTY_NSYNC_TESTING_A): \
|
||||
third_party/nsync/testing/ \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_A).pkg \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_OBJS)
|
||||
|
||||
$(THIRD_PARTY_NSYNC_TESTING_A).pkg: \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_OBJS) \
|
||||
$(foreach x,$(THIRD_PARTY_NSYNC_TESTING_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
o/$(MODE)/third_party/nsync/testing/%_test.com.dbg: \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_DEPS) \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_A) \
|
||||
o/$(MODE)/third_party/nsync/testing/%_test.o \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_A).pkg \
|
||||
$(CRT) \
|
||||
$(APE_NO_MODIFY_SELF)
|
||||
@$(APELINK)
|
||||
|
||||
$(THIRD_PARTY_NSYNC_TESTING_OBJS): third_party/nsync/testing/testing.mk
|
||||
o/$(MODE)/third_party/nsync/testing/mu_test.com.runs: private QUOTA = -C64
|
||||
|
||||
.PHONY: o/$(MODE)/third_party/nsync/testing
|
||||
o/$(MODE)/third_party/nsync/testing: \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_CHECKS) \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_BINS)
|
||||
|
||||
.PHONY: o/$(MODE)/third_party/nsync/test
|
||||
o/$(MODE)/third_party/nsync/test: \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_CHECKS) \
|
||||
$(THIRD_PARTY_NSYNC_TESTING_TESTS)
|
68
third_party/nsync/testing/time_extra.c
vendored
Normal file
68
third_party/nsync/testing/time_extra.c
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/weirdtypes.h"
|
||||
#include "libc/errno.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
// clang-format off
|
||||
|
||||
char *nsync_time_str (nsync_time t, int decimals) {
|
||||
static const struct {
|
||||
const char *suffix;
|
||||
double multiplier;
|
||||
} scale[] = {
|
||||
{ "ns", 1.0e-9, },
|
||||
{ "us", 1e-6, },
|
||||
{ "ms", 1e-3, },
|
||||
{ "s", 1.0, },
|
||||
{ "hr", 3600.0, },
|
||||
};
|
||||
double s = nsync_time_to_dbl (t);
|
||||
int i = 0;
|
||||
while (i + 1 != sizeof (scale) / sizeof (scale[0]) && scale[i + 1].multiplier <= s) {
|
||||
i++;
|
||||
}
|
||||
return (smprintf ("%.*f%s", decimals, s/scale[i].multiplier, scale[i].suffix));
|
||||
}
|
||||
|
||||
int nsync_time_sleep_until (nsync_time abs_deadline) {
|
||||
int result = 0;
|
||||
nsync_time now;
|
||||
now = nsync_time_now ();
|
||||
if (nsync_time_cmp (abs_deadline, now) > 0) {
|
||||
nsync_time remaining;
|
||||
remaining = nsync_time_sleep (nsync_time_sub (abs_deadline, now));
|
||||
if (nsync_time_cmp (remaining, nsync_time_zero) > 0) {
|
||||
result = EINTR;
|
||||
}
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
|
||||
double nsync_time_to_dbl (nsync_time t) {
|
||||
return (((double) NSYNC_TIME_SEC (t)) + ((double) NSYNC_TIME_NSEC (t) * 1e-9));
|
||||
}
|
||||
|
||||
nsync_time nsync_time_from_dbl (double d) {
|
||||
time_t s = (time_t) d;
|
||||
if (d < s) {
|
||||
s--;
|
||||
}
|
||||
return (nsync_time_s_ns (s, (unsigned) ((d - (double) s) * 1e9)));
|
||||
}
|
19
third_party/nsync/testing/time_extra.h
vendored
Normal file
19
third_party/nsync/testing/time_extra.h
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef NSYNC_TESTING_TIME_EXTRA_H_
|
||||
#define NSYNC_TESTING_TIME_EXTRA_H_
|
||||
#include "third_party/nsync/time.h"
|
||||
|
||||
/* Return a malloced nul-terminated string representing time t, using
|
||||
"decimals" decimal places. */
|
||||
char *nsync_time_str(nsync_time t, int decimals);
|
||||
|
||||
/* Sleep until the specified time. Returns 0 on success, and EINTR
|
||||
if the call was interrupted. */
|
||||
int nsync_time_sleep_until(nsync_time abs_deadline);
|
||||
|
||||
/* Return t as a double. */
|
||||
double nsync_time_to_dbl(nsync_time t);
|
||||
|
||||
/* Return a time corresponding to double d. */
|
||||
nsync_time nsync_time_from_dbl(double d);
|
||||
|
||||
#endif /*NSYNC_TESTING_TIME_EXTRA_H_*/
|
190
third_party/nsync/testing/wait_test.c
vendored
Normal file
190
third_party/nsync/testing/wait_test.c
vendored
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2016 Google Inc. │
|
||||
│ │
|
||||
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
||||
│ you may not use this file except in compliance with the License. │
|
||||
│ You may obtain a copy of the License at │
|
||||
│ │
|
||||
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
||||
│ │
|
||||
│ Unless required by applicable law or agreed to in writing, software │
|
||||
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
||||
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
||||
│ See the License for the specific language governing permissions and │
|
||||
│ limitations under the License. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/nsync/counter.h"
|
||||
#include "third_party/nsync/note.h"
|
||||
#include "third_party/nsync/testing/array.h"
|
||||
#include "third_party/nsync/testing/closure.h"
|
||||
#include "third_party/nsync/testing/smprintf.h"
|
||||
#include "third_party/nsync/testing/testing.h"
|
||||
#include "third_party/nsync/testing/time_extra.h"
|
||||
#include "third_party/nsync/time.h"
|
||||
#include "third_party/nsync/waiter.h"
|
||||
// clang-format off
|
||||
|
||||
static void decrement_at (nsync_counter c, nsync_time abs_deadline, nsync_counter done) {
|
||||
nsync_time_sleep_until (abs_deadline);
|
||||
nsync_counter_add (c, -1);
|
||||
nsync_counter_add (done, -1);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY3 (decrement, nsync_counter, nsync_time, nsync_counter)
|
||||
|
||||
static void notify_at (nsync_note n, nsync_time abs_deadline, nsync_counter done) {
|
||||
nsync_time_sleep_until (abs_deadline);
|
||||
nsync_note_notify (n);
|
||||
nsync_counter_add (done, -1);
|
||||
}
|
||||
|
||||
CLOSURE_DECL_BODY3 (notify, nsync_note, nsync_time, nsync_counter)
|
||||
|
||||
typedef A_TYPE (struct nsync_waitable_s) a_waitable;
|
||||
typedef A_TYPE (struct nsync_waitable_s *) a_pwaitable;
|
||||
|
||||
/* Test nsync_wait_n(). */
|
||||
static void test_wait_n (testing t) {
|
||||
int i;
|
||||
int j;
|
||||
int k;
|
||||
int ncounter = 10;
|
||||
int nnote = 10;
|
||||
int nnote_expire = 10;
|
||||
for (i = 0; i != 30; i++) {
|
||||
nsync_counter done = nsync_counter_new (0);
|
||||
nsync_time now;
|
||||
nsync_time deadline;
|
||||
a_waitable aw;
|
||||
a_pwaitable apw;
|
||||
memset (&aw, 0, sizeof (aw));
|
||||
memset (&apw, 0, sizeof (apw));
|
||||
now = nsync_time_now ();
|
||||
deadline = nsync_time_add (now, nsync_time_ms (100));
|
||||
for (j = A_LEN (&aw); A_LEN (&aw) < j+ncounter;) {
|
||||
nsync_counter c = nsync_counter_new (0);
|
||||
struct nsync_waitable_s *w = &A_PUSH (&aw);
|
||||
w->v = c;
|
||||
w->funcs = &nsync_counter_waitable_funcs;
|
||||
for (k = 0; k != 4 && A_LEN (&aw) < j+ncounter; k++) {
|
||||
nsync_counter_add (c, 1);
|
||||
nsync_counter_add (done, 1);
|
||||
closure_fork (closure_decrement (&decrement_at, c, deadline, done));
|
||||
}
|
||||
}
|
||||
for (j = A_LEN (&aw); A_LEN (&aw) < j+nnote;) {
|
||||
nsync_note n = nsync_note_new (NULL, nsync_time_no_deadline);
|
||||
struct nsync_waitable_s *w = &A_PUSH (&aw);
|
||||
w->v = n;
|
||||
w->funcs = &nsync_note_waitable_funcs;
|
||||
nsync_counter_add (done, 1);
|
||||
closure_fork (closure_notify (¬ify_at, n, deadline, done));
|
||||
for (k = 0; k != 4 && A_LEN (&aw) < j+nnote; k++) {
|
||||
nsync_note cn = nsync_note_new (n, nsync_time_no_deadline);
|
||||
struct nsync_waitable_s *lw = &A_PUSH (&aw);
|
||||
lw->v = cn;
|
||||
lw->funcs = &nsync_note_waitable_funcs;
|
||||
}
|
||||
}
|
||||
for (j = A_LEN (&aw); A_LEN (&aw) < j+nnote_expire;) {
|
||||
nsync_note n = nsync_note_new (NULL, deadline);
|
||||
struct nsync_waitable_s *w = &A_PUSH (&aw);
|
||||
w->v = n;
|
||||
w->funcs = &nsync_note_waitable_funcs;
|
||||
nsync_counter_add (done, 1);
|
||||
closure_fork (closure_notify (¬ify_at, n, deadline, done));
|
||||
for (k = 0; k != 4 && A_LEN (&aw) < j+nnote; k++) {
|
||||
nsync_note cn = nsync_note_new (n, nsync_time_no_deadline);
|
||||
struct nsync_waitable_s *lw = &A_PUSH (&aw);
|
||||
lw->v = cn;
|
||||
lw->funcs = &nsync_note_waitable_funcs;
|
||||
}
|
||||
}
|
||||
if (ncounter + nnote + nnote_expire != A_LEN (&aw)) {
|
||||
TEST_ERROR (t, ("array length not equal to number of counters"));
|
||||
}
|
||||
for (j = 0; j != A_LEN (&aw); j++) {
|
||||
A_PUSH (&apw) = &A (&aw, j);
|
||||
}
|
||||
while (A_LEN (&apw) != 0) {
|
||||
k = nsync_wait_n (NULL, NULL, NULL, nsync_time_no_deadline,
|
||||
A_LEN (&apw), &A (&apw, 0));
|
||||
if (k == A_LEN (&apw)) {
|
||||
TEST_ERROR (t, ("nsync_wait_n returned with no waiter ready"));
|
||||
}
|
||||
A (&apw, k) = A (&apw, A_LEN (&apw) - 1);
|
||||
A_DISCARD (&apw, 1);
|
||||
}
|
||||
nsync_counter_wait (done, nsync_time_no_deadline);
|
||||
for (k = 0; k != ncounter; k++) {
|
||||
nsync_counter_free ((nsync_counter) A (&aw, k).v);
|
||||
}
|
||||
for (; k < A_LEN (&aw); k++) {
|
||||
nsync_note_free ((nsync_note) A (&aw, k).v);
|
||||
}
|
||||
A_FREE (&apw);
|
||||
A_FREE (&aw);
|
||||
nsync_counter_free (done);
|
||||
}
|
||||
}
|
||||
|
||||
/* Call *nsync_note_waitable_funcs.ready_time, and return its result, but
|
||||
before returning, notify the nsync_note. This is used by
|
||||
test_wait_n_ready_while_queuing() to wrap nsync_note's normal *ready_time
|
||||
function, so that the behaviour of nsync_wait_n() can be checked when a
|
||||
notification happens while the enqueueing process. */
|
||||
static nsync_time note_ready_time_wrapper (void *v, struct nsync_waiter_s *nw) {
|
||||
nsync_note n = (nsync_note) v;
|
||||
nsync_time result;
|
||||
result = (*nsync_note_waitable_funcs.ready_time) (v, nw);
|
||||
nsync_note_notify (n);
|
||||
return (result);
|
||||
}
|
||||
|
||||
/* The following test checks that nsync_wait_n() behaves correctly if
|
||||
some object becomes ready during the enqueuing process. */
|
||||
static void test_wait_n_ready_while_queuing (testing t) {
|
||||
struct nsync_waitable_s w[2];
|
||||
struct nsync_waitable_s *pw[2];
|
||||
int count;
|
||||
int woken;
|
||||
|
||||
/* This test works by wrapping nsync_note's *ready_time function so
|
||||
that the note is notified just after nsync_wait_n() checks that it
|
||||
if not notified on entry. */
|
||||
struct nsync_waitable_funcs_s wrapped_note_waitable_funcs;
|
||||
wrapped_note_waitable_funcs = nsync_note_waitable_funcs;
|
||||
wrapped_note_waitable_funcs.ready_time = ¬e_ready_time_wrapper;
|
||||
|
||||
for (count = 0; count != sizeof (w) / sizeof (w[0]); count++) {
|
||||
nsync_note n = nsync_note_new (NULL, nsync_time_no_deadline);
|
||||
if (nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("nsync_note is unexpectedly notified"));
|
||||
}
|
||||
w[count].v = n;
|
||||
w[count].funcs = &wrapped_note_waitable_funcs;
|
||||
pw[count] = &w[count];
|
||||
}
|
||||
woken = nsync_wait_n (NULL, NULL, NULL, nsync_time_no_deadline,
|
||||
count, pw);
|
||||
if (woken != 0) {
|
||||
TEST_ERROR (t, ("nsync_wait_n unexpectedly failed to find pw[0] notified"));
|
||||
}
|
||||
for (count = 0; count != sizeof (w) / sizeof (w[0]); count++) {
|
||||
nsync_note n = (nsync_note) w[count].v;
|
||||
if (!nsync_note_is_notified (n)) {
|
||||
TEST_ERROR (t, ("nsync_note is unexpectedly not notified"));
|
||||
}
|
||||
nsync_note_free (n);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
testing_base tb = testing_new (argc, argv, 0);
|
||||
TEST_RUN (tb, test_wait_n);
|
||||
TEST_RUN (tb, test_wait_n_ready_while_queuing);
|
||||
return (testing_base_exit (tb));
|
||||
}
|
2
third_party/nsync/wait_s.internal.h
vendored
2
third_party/nsync/wait_s.internal.h
vendored
|
@ -12,10 +12,10 @@ COSMOPOLITAN_C_START_
|
|||
nsync_waiter_s. */
|
||||
struct nsync_waiter_s {
|
||||
uint32_t tag; /* used for debugging */
|
||||
uint32_t flags; /* see below */
|
||||
nsync_dll_element_ q; /* used to link children of parent */
|
||||
nsync_atomic_uint32_ waiting; /* non-zero <=> the waiter is waiting */
|
||||
struct nsync_semaphore_s_ *sem; /* *sem will be Ved when waiter is woken */
|
||||
uint32_t flags; /* see below */
|
||||
};
|
||||
|
||||
/* set if waiter is embedded in Mu/CV's internal structures */
|
||||
|
|
1
third_party/nsync/yield.c
vendored
1
third_party/nsync/yield.c
vendored
|
@ -17,6 +17,7 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "third_party/nsync/common.internal.h"
|
||||
// clang-format off
|
||||
|
|
|
@ -4668,12 +4668,10 @@ UNIX MODULE
|
|||
absolute deadline expires or we're woken up by another process that
|
||||
calls unix.Memory:wake().
|
||||
|
||||
The `expect` parameter is used only upon entry to synchronize the
|
||||
transition to kernelspace. The kernel doesn't actually poll the
|
||||
memory location. It uses `expect` to make sure the process doesn't
|
||||
get added to the wait list unless it's sure that it needs to wait,
|
||||
since the kernel can only control the ordering of wait / wake calls
|
||||
across processes.
|
||||
The `expect` parameter is the value you expect the word to have and
|
||||
this function will return if that's not the case. Please note this
|
||||
parameter doesn't imply the kernel will poll the value for you, and
|
||||
you still need to call wake() when you know the memory's changed.
|
||||
|
||||
The default behavior is to wait until the heat death of the universe
|
||||
if necessary. You may alternatively specify an absolute deadline. If
|
||||
|
@ -4681,18 +4679,11 @@ UNIX MODULE
|
|||
this routine is non-blocking. Otherwise we'll block at most until
|
||||
the current time reaches the absolute deadline.
|
||||
|
||||
Futexes are currently supported on Linux, FreeBSD, OpenBSD. On other
|
||||
platforms this method calls sched_yield() and will either (1) return
|
||||
unix.EINTR if a deadline is specified, otherwise (2) 0 is returned.
|
||||
This means futexes will *work* on Windows, Mac, and NetBSD but they
|
||||
won't be scalable in terms of CPU usage when many processes wait on
|
||||
one process that holds a lock for a long time. In the future we may
|
||||
polyfill futexes in userspace for these platforms to improve things
|
||||
for folks who've adopted this api. If lock scalability is something
|
||||
you need on Windows and MacOS today, then consider fcntl() which is
|
||||
well-supported on all supported platforms but requires using files.
|
||||
Please test your use case though, because it's kind of an edge case
|
||||
to have the scenario above, and chances are this op will work fine.
|
||||
Futexes are supported natively on Linux, FreeBSD, and OpenBSD. When
|
||||
this interface is used on other platforms this method will manually
|
||||
poll the memory location with exponential backoff. Doing this works
|
||||
well enough that we're passing the *NSYNC unit tests, but is not as
|
||||
low latency as having kernel supported futexes.
|
||||
|
||||
`EINTR` if a signal is delivered while waiting on deadline. Callers
|
||||
should use futexes inside a loop that is able to cope with spurious
|
||||
|
|
Loading…
Add table
Reference in a new issue