diff --git a/Makefile b/Makefile index 6d7294685..e759e31f0 100644 --- a/Makefile +++ b/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 diff --git a/libc/calls/clock_nanosleep.c b/libc/calls/clock_nanosleep.c index 3a587725c..d47a333bd 100644 --- a/libc/calls/clock_nanosleep.c +++ b/libc/calls/clock_nanosleep.c @@ -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; } diff --git a/libc/calls/nanosleep.c b/libc/calls/nanosleep.c index 12184ada2..7d83bcd5f 100644 --- a/libc/calls/nanosleep.c +++ b/libc/calls/nanosleep.c @@ -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); diff --git a/libc/calls/posix_fadvise.c b/libc/calls/posix_fadvise.c index c37861695..ea2b0d5e7 100644 --- a/libc/calls/posix_fadvise.c +++ b/libc/calls/posix_fadvise.c @@ -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; diff --git a/libc/calls/posix_madvise.c b/libc/calls/posix_madvise.c index ac5f6a498..a4a18b4eb 100644 --- a/libc/calls/posix_madvise.c +++ b/libc/calls/posix_madvise.c @@ -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; } diff --git a/libc/intrin/kerrnodocs.S b/libc/intrin/kerrnodocs.S index 0920d4f9a..8f80253ad 100644 --- a/libc/intrin/kerrnodocs.S +++ b/libc/intrin/kerrnodocs.S @@ -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" diff --git a/libc/intrin/kerrnonames.S b/libc/intrin/kerrnonames.S index 08a9f3073..4167cf5da 100644 --- a/libc/intrin/kerrnonames.S +++ b/libc/intrin/kerrnonames.S @@ -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 diff --git a/libc/intrin/sys_umtx_op.S b/libc/intrin/sys_umtx_op.S index 85b1d16b3..677990917 100644 --- a/libc/intrin/sys_umtx_op.S +++ b/libc/intrin/sys_umtx_op.S @@ -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 diff --git a/libc/intrin/sys_umtx_timedwait_uint.c b/libc/intrin/sys_umtx_timedwait_uint.c new file mode 100644 index 000000000..2fee75138 --- /dev/null +++ b/libc/intrin/sys_umtx_timedwait_uint.c @@ -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); +} diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index bd31fa9bd..784fc49d9 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -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) diff --git a/libc/thread/freebsd.internal.h b/libc/thread/freebsd.internal.h index d29c132f2..1b1324b9e 100644 --- a/libc/thread/freebsd.internal.h +++ b/libc/thread/freebsd.internal.h @@ -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) */ diff --git a/libc/thread/pthread_attr_destroy.c b/libc/thread/pthread_attr_destroy.c index d93ffa7d2..299e737a1 100644 --- a/libc/thread/pthread_attr_destroy.c +++ b/libc/thread/pthread_attr_destroy.c @@ -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. diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index bff730685..e32ae5b96 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -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, diff --git a/libc/thread/pthread_detach.c b/libc/thread/pthread_detach.c index edeef7ec9..fdf06dd62 100644 --- a/libc/thread/pthread_detach.c +++ b/libc/thread/pthread_detach.c @@ -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; diff --git a/libc/thread/pthread_join.c b/libc/thread/pthread_join.c index 9d54c799b..88bf72559 100644 --- a/libc/thread/pthread_join.c +++ b/libc/thread/pthread_join.c @@ -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; diff --git a/libc/thread/wait0.c b/libc/thread/wait0.c index 18732ff29..f5dfb0c23 100644 --- a/libc/thread/wait0.c +++ b/libc/thread/wait0.c @@ -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) { diff --git a/test/libc/calls/_timespec_test.c b/test/libc/calls/_timespec_test.c index e51b3b4f5..5da410048 100644 --- a/test/libc/calls/_timespec_test.c +++ b/test/libc/calls/_timespec_test.c @@ -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) { diff --git a/test/libc/calls/nanosleep_test.c b/test/libc/calls/nanosleep_test.c index a57197ebc..8b8ca9603 100644 --- a/test/libc/calls/nanosleep_test.c +++ b/test/libc/calls/nanosleep_test.c @@ -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); +} diff --git a/third_party/nsync/common.internal.h b/third_party/nsync/common.internal.h index 4db9254e1..226a490ab 100644 --- a/third_party/nsync/common.internal.h +++ b/third_party/nsync/common.internal.h @@ -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; diff --git a/third_party/nsync/futex.c b/third_party/nsync/futex.c index a3f290778..4b8398a05 100644 --- a/third_party/nsync/futex.c +++ b/third_party/nsync/futex.c @@ -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; } diff --git a/third_party/nsync/testing/array.c b/third_party/nsync/testing/array.c new file mode 100644 index 000000000..093920408 --- /dev/null +++ b/third_party/nsync/testing/array.c @@ -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; + } +} diff --git a/third_party/nsync/testing/array.h b/third_party/nsync/testing/array.h new file mode 100644 index 000000000..91f362891 --- /dev/null +++ b/third_party/nsync/testing/array.h @@ -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_*/ diff --git a/third_party/nsync/testing/atm_log.c b/third_party/nsync/testing/atm_log.c new file mode 100644 index 000000000..f5952c9ed --- /dev/null +++ b/third_party/nsync/testing/atm_log.c @@ -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); + } +} diff --git a/third_party/nsync/testing/atm_log.h b/third_party/nsync/testing/atm_log.h new file mode 100644 index 000000000..778ad4791 --- /dev/null +++ b/third_party/nsync/testing/atm_log.h @@ -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_*/ diff --git a/third_party/nsync/testing/closure.c b/third_party/nsync/testing/closure.c new file mode 100644 index 000000000..4a6b58098 --- /dev/null +++ b/third_party/nsync/testing/closure.c @@ -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); +} diff --git a/third_party/nsync/testing/closure.h b/third_party/nsync/testing/closure.h new file mode 100644 index 000000000..96b53a128 --- /dev/null +++ b/third_party/nsync/testing/closure.h @@ -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_*/ diff --git a/third_party/nsync/testing/counter_test.c b/third_party/nsync/testing/counter_test.c new file mode 100644 index 000000000..4f19384c5 --- /dev/null +++ b/third_party/nsync/testing/counter_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/cv_mu_timeout_stress_test.c b/third_party/nsync/testing/cv_mu_timeout_stress_test.c new file mode 100644 index 000000000..5da2e88b8 --- /dev/null +++ b/third_party/nsync/testing/cv_mu_timeout_stress_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/cv_test.c b/third_party/nsync/testing/cv_test.c new file mode 100644 index 000000000..7a06d71ca --- /dev/null +++ b/third_party/nsync/testing/cv_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/cv_wait_example_test.c b/third_party/nsync/testing/cv_wait_example_test.c new file mode 100644 index 000000000..8d96ec3d2 --- /dev/null +++ b/third_party/nsync/testing/cv_wait_example_test.c @@ -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: " 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)); +} diff --git a/third_party/nsync/testing/dll_test.c b/third_party/nsync/testing/dll_test.c new file mode 100644 index 000000000..f305db0d9 --- /dev/null +++ b/third_party/nsync/testing/dll_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/heap.h b/third_party/nsync/testing/heap.h new file mode 100644 index 000000000..207e309ea --- /dev/null +++ b/third_party/nsync/testing/heap.h @@ -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_*/ diff --git a/third_party/nsync/testing/mu_starvation_test.c b/third_party/nsync/testing/mu_starvation_test.c new file mode 100644 index 000000000..100897379 --- /dev/null +++ b/third_party/nsync/testing/mu_starvation_test.c @@ -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)); +} diff --git a/examples/mu_test.c b/third_party/nsync/testing/mu_test.c similarity index 53% rename from examples/mu_test.c rename to third_party/nsync/testing/mu_test.c index e69aa6614..d3b644f76 100644 --- a/examples/mu_test.c +++ b/third_party/nsync/testing/mu_test.c @@ -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; diff --git a/third_party/nsync/testing/mu_wait_example_test.c b/third_party/nsync/testing/mu_wait_example_test.c new file mode 100644 index 000000000..f1f417007 --- /dev/null +++ b/third_party/nsync/testing/mu_wait_example_test.c @@ -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: " 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)); +} diff --git a/third_party/nsync/testing/mu_wait_test.c b/third_party/nsync/testing/mu_wait_test.c new file mode 100644 index 000000000..d5c9e1e4d --- /dev/null +++ b/third_party/nsync/testing/mu_wait_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/note_test.c b/third_party/nsync/testing/note_test.c new file mode 100644 index 000000000..750bcab8f --- /dev/null +++ b/third_party/nsync/testing/note_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/once_test.c b/third_party/nsync/testing/once_test.c new file mode 100644 index 000000000..5de2e8b57 --- /dev/null +++ b/third_party/nsync/testing/once_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/pingpong_test.c b/third_party/nsync/testing/pingpong_test.c new file mode 100644 index 000000000..dfd6d77ed --- /dev/null +++ b/third_party/nsync/testing/pingpong_test.c @@ -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)); +} diff --git a/third_party/nsync/testing/smprintf.c b/third_party/nsync/testing/smprintf.c new file mode 100644 index 000000000..ca9156acf --- /dev/null +++ b/third_party/nsync/testing/smprintf.c @@ -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); +} diff --git a/third_party/nsync/testing/smprintf.h b/third_party/nsync/testing/smprintf.h new file mode 100644 index 000000000..e375303d0 --- /dev/null +++ b/third_party/nsync/testing/smprintf.h @@ -0,0 +1,6 @@ +#ifndef NSYNC_TESTING_SMPRINTF_H_ +#define NSYNC_TESTING_SMPRINTF_H_ + +char *smprintf(const char *, ...); + +#endif /*NSYNC_TESTING_SMPRINTF_H_*/ diff --git a/third_party/nsync/testing/start_thread.c b/third_party/nsync/testing/start_thread.c new file mode 100644 index 000000000..ad0a15237 --- /dev/null +++ b/third_party/nsync/testing/start_thread.c @@ -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); +} diff --git a/third_party/nsync/testing/testing.c b/third_party/nsync/testing/testing.c new file mode 100644 index 000000000..0243af185 --- /dev/null +++ b/third_party/nsync/testing/testing.c @@ -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); +} diff --git a/third_party/nsync/testing/testing.h b/third_party/nsync/testing/testing.h new file mode 100644 index 000000000..4e85698bd --- /dev/null +++ b/third_party/nsync/testing/testing.h @@ -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_*/ diff --git a/third_party/nsync/testing/testing.mk b/third_party/nsync/testing/testing.mk new file mode 100644 index 000000000..c7d2291d6 --- /dev/null +++ b/third_party/nsync/testing/testing.mk @@ -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) diff --git a/third_party/nsync/testing/time_extra.c b/third_party/nsync/testing/time_extra.c new file mode 100644 index 000000000..d7d43b974 --- /dev/null +++ b/third_party/nsync/testing/time_extra.c @@ -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))); +} diff --git a/third_party/nsync/testing/time_extra.h b/third_party/nsync/testing/time_extra.h new file mode 100644 index 000000000..e59be4e4e --- /dev/null +++ b/third_party/nsync/testing/time_extra.h @@ -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_*/ diff --git a/third_party/nsync/testing/wait_test.c b/third_party/nsync/testing/wait_test.c new file mode 100644 index 000000000..af097522e --- /dev/null +++ b/third_party/nsync/testing/wait_test.c @@ -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)); +} diff --git a/third_party/nsync/wait_s.internal.h b/third_party/nsync/wait_s.internal.h index 33ef9fc3c..ee84040c0 100644 --- a/third_party/nsync/wait_s.internal.h +++ b/third_party/nsync/wait_s.internal.h @@ -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 */ diff --git a/third_party/nsync/yield.c b/third_party/nsync/yield.c index bf7081c66..3314c8101 100644 --- a/third_party/nsync/yield.c +++ b/third_party/nsync/yield.c @@ -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 diff --git a/tool/net/help.txt b/tool/net/help.txt index 64ccc52e1..82407b4af 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -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