From 30afd6ddbbcb15f931a934b973cae621bf2644c6 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 21 Jul 2024 06:41:30 -0700 Subject: [PATCH] Improve multithreading --- libc/calls/sig.c | 2 - libc/calls/sigaltstack.c | 4 +- libc/calls/struct/sigaltstack.internal.h | 4 +- libc/intrin/describeflags.h | 4 + .../{pthread_unref.c => describemsyncflags.c} | 27 +-- libc/intrin/describesigaltstack.c | 8 +- libc/intrin/describesigaltstackflags.c | 30 +++ libc/intrin/maps.c | 13 +- libc/intrin/mmap.c | 3 +- libc/intrin/msync.c | 4 +- libc/mem/leaks.c | 2 +- libc/runtime/cxa_thread_atexit.c | 11 + libc/testlib/testmain.c | 11 +- libc/thread/posixthread.internal.h | 13 +- libc/thread/pt.internal.h | 2 + libc/thread/pthread_attr_getguardsize.c | 2 +- libc/thread/pthread_attr_getsigaltstack_np.c | 34 ++++ .../pthread_attr_getsigaltstacksize_np.c | 36 ++++ libc/thread/pthread_attr_setguardsize.c | 18 +- libc/thread/pthread_attr_setsigaltstack_np.c | 42 ++++ .../pthread_attr_setsigaltstacksize_np.c | 78 ++++++++ libc/thread/pthread_attr_setstack.c | 3 + libc/thread/pthread_attr_setstacksize.c | 3 + libc/thread/pthread_create.c | 189 ++++++++++++------ libc/thread/pthread_decimate_np.c | 2 +- libc/thread/pthread_detach.c | 4 +- libc/thread/pthread_exit.c | 27 ++- libc/thread/pthread_timedjoin_np.c | 65 +++--- libc/thread/thread.h | 8 +- test/libc/calls/stackoverflow5_test.c | 86 ++++++++ test/libc/intrin/cosmo_once_test.c | 6 +- test/libc/intrin/mmap_test.c | 13 +- .../pthread_cancel_deferred_cond_test.c | 40 ++++ .../thread/pthread_cancel_masked_cond_test.c | 43 ++++ .../thread/pthread_cancel_masked_read_test.c | 41 ++++ .../thread/pthread_create_inherit_mask_test.c | 30 +++ test/libc/thread/pthread_create_test.c | 8 +- test/libc/thread/pthread_detach_test.c | 10 +- 38 files changed, 752 insertions(+), 174 deletions(-) rename libc/intrin/{pthread_unref.c => describemsyncflags.c} (75%) create mode 100644 libc/intrin/describesigaltstackflags.c create mode 100644 libc/thread/pthread_attr_getsigaltstack_np.c create mode 100644 libc/thread/pthread_attr_getsigaltstacksize_np.c create mode 100644 libc/thread/pthread_attr_setsigaltstack_np.c create mode 100644 libc/thread/pthread_attr_setsigaltstacksize_np.c create mode 100644 test/libc/calls/stackoverflow5_test.c create mode 100644 test/libc/thread/pthread_cancel_deferred_cond_test.c create mode 100644 test/libc/thread/pthread_cancel_masked_cond_test.c create mode 100644 test/libc/thread/pthread_cancel_masked_read_test.c create mode 100644 test/libc/thread/pthread_create_inherit_mask_test.c diff --git a/libc/calls/sig.c b/libc/calls/sig.c index c97e41d2a..a52ad2bc7 100644 --- a/libc/calls/sig.c +++ b/libc/calls/sig.c @@ -396,9 +396,7 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) { textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) { int rc; BLOCK_SIGNALS; - _pthread_ref(pt); rc = __sig_killer(pt, sig, sic); - _pthread_unref(pt); ALLOW_SIGNALS; return rc; } diff --git a/libc/calls/sigaltstack.c b/libc/calls/sigaltstack.c index 01079570f..0e246d749 100644 --- a/libc/calls/sigaltstack.c +++ b/libc/calls/sigaltstack.c @@ -146,7 +146,7 @@ int sigaltstack(const struct sigaltstack *neu, struct sigaltstack *old) { } else { rc = sigaltstack_cosmo(neu, old); } - STRACE("sigaltstack(%s, [%s]) → %d% m", DescribeSigaltstk(0, neu), - DescribeSigaltstk(0, old), rc); + STRACE("sigaltstack(%s, [%s]) → %d% m", DescribeSigaltstack(0, neu), + DescribeSigaltstack(0, old), rc); return rc; } diff --git a/libc/calls/struct/sigaltstack.internal.h b/libc/calls/struct/sigaltstack.internal.h index 43c21abb9..c95eea696 100644 --- a/libc/calls/struct/sigaltstack.internal.h +++ b/libc/calls/struct/sigaltstack.internal.h @@ -4,8 +4,8 @@ #include "libc/mem/alloca.h" COSMOPOLITAN_C_START_ -const char *DescribeSigaltstk(char[128], int, const struct sigaltstack *); -#define DescribeSigaltstk(rc, ss) DescribeSigaltstk(alloca(128), rc, ss) +const char *DescribeSigaltstack(char[128], int, const struct sigaltstack *); +#define DescribeSigaltstack(rc, ss) DescribeSigaltstack(alloca(128), rc, ss) COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGALTSTACK_INTERNAL_H_ */ diff --git a/libc/intrin/describeflags.h b/libc/intrin/describeflags.h index dc38a93e5..9bcf96218 100644 --- a/libc/intrin/describeflags.h +++ b/libc/intrin/describeflags.h @@ -27,6 +27,7 @@ const char *DescribeItimer(char[12], int) libcesque; const char *DescribeMapFlags(char[64], int) libcesque; const char *DescribeMapping(char[8], int, int) libcesque; const char *DescribeMremapFlags(char[30], int) libcesque; +const char *DescribeMsyncFlags(char[48], int) libcesque; const char *DescribeNtConsoleInFlags(char[256], uint32_t) libcesque; const char *DescribeNtConsoleOutFlags(char[128], uint32_t) libcesque; const char *DescribeNtCreationDisposition(uint32_t) libcesque; @@ -54,6 +55,7 @@ const char *DescribeRlimitName(char[20], int) libcesque; const char *DescribeSchedPolicy(char[48], int) libcesque; const char *DescribeSeccompOperation(int) libcesque; const char *DescribeSiCode(char[20], int, int) libcesque; +const char *DescribeSigaltstackFlags(char[22], int) libcesque; const char *DescribeSleepFlags(char[16], int) libcesque; const char *DescribeSockLevel(char[12], int) libcesque; const char *DescribeSockOptname(char[32], int, int) libcesque; @@ -82,6 +84,7 @@ const char *DescribeWhichPrio(char[12], int) libcesque; #define DescribeMapFlags(x) DescribeMapFlags(alloca(64), x) #define DescribeMapping(x, y) DescribeMapping(alloca(8), x, y) #define DescribeMremapFlags(x) DescribeMremapFlags(alloca(30), x) +#define DescribeMsyncFlags(x) DescribeMsyncFlags(alloca(48), x) #define DescribeNtConsoleInFlags(x) DescribeNtConsoleInFlags(alloca(256), x) #define DescribeNtConsoleOutFlags(x) DescribeNtConsoleOutFlags(alloca(128), x) #define DescribeNtFileAccessFlags(x) DescribeNtFileAccessFlags(alloca(512), x) @@ -107,6 +110,7 @@ const char *DescribeWhichPrio(char[12], int) libcesque; #define DescribeRlimitName(rl) DescribeRlimitName(alloca(20), rl) #define DescribeSchedPolicy(x) DescribeSchedPolicy(alloca(48), x) #define DescribeSiCode(x, y) DescribeSiCode(alloca(20), x, y) +#define DescribeSigaltstackFlags(x) DescribeSigaltstackFlags(alloca(22), x) #define DescribeSleepFlags(x) DescribeSleepFlags(alloca(16), x) #define DescribeSockLevel(x) DescribeSockLevel(alloca(12), x) #define DescribeSockOptname(x, y) DescribeSockOptname(alloca(32), x, y) diff --git a/libc/intrin/pthread_unref.c b/libc/intrin/describemsyncflags.c similarity index 75% rename from libc/intrin/pthread_unref.c rename to libc/intrin/describemsyncflags.c index 6ff6a7a5c..481493489 100644 --- a/libc/intrin/pthread_unref.c +++ b/libc/intrin/describemsyncflags.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,20 +16,15 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/intrin/atomic.h" -#include "libc/intrin/weaken.h" -#include "libc/thread/posixthread.internal.h" +#include "libc/intrin/describeflags.h" +#include "libc/macros.internal.h" +#include "libc/sysv/consts/msync.h" -static bool _pthread_deref(struct PosixThread *pt) { - int refs = atomic_load_explicit(&pt->pt_refs, memory_order_acquire); - return !refs || !atomic_fetch_sub(&pt->pt_refs, 1); -} - -void _pthread_unref(struct PosixThread *pt) { - if (_pthread_deref(pt)) { - unassert(_weaken(_pthread_free)); - _weaken(_pthread_free)(pt, false); - _weaken(_pthread_decimate)(); - } +const char *(DescribeMsyncFlags)(char buf[48], int x) { + const struct DescribeFlags kMsyncFlags[] = { + {MS_SYNC, "SYNC"}, // + {MS_ASYNC, "ASYNC"}, // + {MS_INVALIDATE, "INVALIDATE"}, // + }; + return DescribeFlags(buf, 48, kMsyncFlags, ARRAYLEN(kMsyncFlags), "MS_", x); } diff --git a/libc/intrin/describesigaltstack.c b/libc/intrin/describesigaltstack.c index 90770f3a6..32cdb3bc2 100644 --- a/libc/intrin/describesigaltstack.c +++ b/libc/intrin/describesigaltstack.c @@ -21,8 +21,8 @@ #include "libc/intrin/describeflags.h" #include "libc/intrin/kprintf.h" -const char *(DescribeSigaltstk)(char buf[128], int rc, - const struct sigaltstack *ss) { +const char *(DescribeSigaltstack)(char buf[128], int rc, + const struct sigaltstack *ss) { if (rc == -1) return "n/a"; if (!ss) @@ -30,8 +30,8 @@ const char *(DescribeSigaltstk)(char buf[128], int rc, if (kisdangerous(ss)) { ksnprintf(buf, 128, "%p", ss); } else { - ksnprintf(buf, 128, "{.ss_sp=%p, .ss_flags=%#lx, .ss_size=%'zu}", ss->ss_sp, - ss->ss_flags, ss->ss_size); + ksnprintf(buf, 128, "{.ss_sp=%p, .ss_flags=%s, .ss_size=%'zu}", ss->ss_sp, + DescribeSigaltstackFlags(ss->ss_flags), ss->ss_size); } return buf; } diff --git a/libc/intrin/describesigaltstackflags.c b/libc/intrin/describesigaltstackflags.c new file mode 100644 index 000000000..e9c7c6e8b --- /dev/null +++ b/libc/intrin/describesigaltstackflags.c @@ -0,0 +1,30 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/intrin/describeflags.h" +#include "libc/macros.internal.h" +#include "libc/sysv/consts/ss.h" + +const char *(DescribeSigaltstackFlags)(char buf[22], int x) { + const struct DescribeFlags kSigaltstackFlags[] = { + {SS_ONSTACK, "ONSTACK"}, // + {SS_DISABLE, "DISABLE"}, // + }; + return DescribeFlags(buf, 48, kSigaltstackFlags, ARRAYLEN(kSigaltstackFlags), + "SS_", x); +} diff --git a/libc/intrin/maps.c b/libc/intrin/maps.c index e7d913206..3d042e5d2 100644 --- a/libc/intrin/maps.c +++ b/libc/intrin/maps.c @@ -91,12 +91,21 @@ privileged bool __maps_lock(void) { tib = __get_tls_privileged(); if (atomic_fetch_add_explicit(&tib->tib_relock_maps, 1, memory_order_relaxed)) return true; + int backoff = 0; while (atomic_exchange_explicit(&__maps.lock, 1, memory_order_acquire)) { + if (backoff < 7) { + volatile int i; + for (i = 0; i != 1 << backoff; i++) { + } + backoff++; + } else { + // STRACE("pthread_delay_np(__maps)"); #if defined(__GNUC__) && defined(__aarch64__) - __asm__ volatile("yield"); + __asm__ volatile("yield"); #elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) - __asm__ volatile("pause"); + __asm__ volatile("pause"); #endif + } } return false; } diff --git a/libc/intrin/mmap.c b/libc/intrin/mmap.c index ac4ceeb5a..47e102a4c 100644 --- a/libc/intrin/mmap.c +++ b/libc/intrin/mmap.c @@ -834,7 +834,8 @@ void *mremap(void *old_addr, size_t old_size, size_t new_size, int flags, ...) { */ int munmap(void *addr, size_t size) { int rc = __munmap(addr, size); - STRACE("munmap(%p, %'zu) → %d% m", addr, size, rc); + STRACE("munmap(%p, %'zu) → %d% m (%'zu bytes total)", addr, size, rc, + __maps.pages * __pagesize); return rc; } diff --git a/libc/intrin/msync.c b/libc/intrin/msync.c index 69bf5730b..d3e43e26d 100644 --- a/libc/intrin/msync.c +++ b/libc/intrin/msync.c @@ -23,6 +23,7 @@ #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/intrin/describeflags.h" #include "libc/intrin/strace.h" #include "libc/macros.internal.h" #include "libc/sysv/errfuns.h" @@ -93,6 +94,7 @@ int msync(void *addr, size_t size, int flags) { END_CANCELATION_POINT; Finished: - STRACE("msync(%p, %'zu, %#x) → %d% m", addr, size, flags, rc); + STRACE("msync(%p, %'zu, %s) → %d% m", addr, size, DescribeMsyncFlags(flags), + rc); return rc; } diff --git a/libc/mem/leaks.c b/libc/mem/leaks.c index 570124226..8c7ac7f9d 100644 --- a/libc/mem/leaks.c +++ b/libc/mem/leaks.c @@ -76,7 +76,7 @@ void CheckForMemoryLeaks(void) { // validate usage of this api if (_weaken(_pthread_decimate)) - _weaken(_pthread_decimate)(); + _weaken(_pthread_decimate)(false); if (!pthread_orphan_np()) kprintf("warning: called CheckForMemoryLeaks() from non-orphaned thread\n"); diff --git a/libc/runtime/cxa_thread_atexit.c b/libc/runtime/cxa_thread_atexit.c index 4e8a2efff..76b89ec89 100644 --- a/libc/runtime/cxa_thread_atexit.c +++ b/libc/runtime/cxa_thread_atexit.c @@ -79,11 +79,22 @@ void __cxa_thread_finalize(void) { struct Dtor *dtor; struct CosmoTib *tib; tib = __get_tls(); + + // "Any cancellation cleanup handlers that have been pushed and not + // yet popped shall be popped in the reverse order that they were + // pushed and then executed." ──Quoth POSIX.1-2017 _pthread_unwind(tib); + + // "After all cancellation cleanup handlers have been executed, if the + // thread has any thread-specific data, appropriate destructor + // functions shall be called in an unspecified order." + // ──Quoth POSIX.1-2017 if (tib->tib_nsync) _weaken(nsync_waiter_destroy)(tib->tib_nsync); _pthread_unkey(tib); + _pthread_ungarbage(tib); + while ((dtor = tib->tib_atexit)) { STRACE("__cxa_finalize(%t, %p)", dtor->fun, dtor->arg); tib->tib_atexit = dtor->next; diff --git a/libc/testlib/testmain.c b/libc/testlib/testmain.c index bd6093851..e211f8564 100644 --- a/libc/testlib/testmain.c +++ b/libc/testlib/testmain.c @@ -151,23 +151,20 @@ int main(int argc, char *argv[]) { a->teardown(0); } } - if (_weaken(TearDownOnce)) { + if (_weaken(TearDownOnce)) _weaken(TearDownOnce)(); - } // make sure threads are in a good state - if (_weaken(_pthread_decimate)) { - _weaken(_pthread_decimate)(); - } + if (_weaken(_pthread_decimate)) + _weaken(_pthread_decimate)(false); if (_weaken(pthread_orphan_np) && !_weaken(pthread_orphan_np)()) { tinyprint(2, "error: tests ended with threads still active\n", NULL); _Exit(1); } // check for memory leaks - if (!g_testlib_failed) { + if (!g_testlib_failed) CheckForMemoryLeaks(); - } // we're done! int status = MIN(255, g_testlib_failed); diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index bd1fd5d04..4afebc85d 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -74,7 +74,7 @@ struct PosixThread { atomic_int pt_canceled; // 0x04: thread has bad beliefs _Atomic(enum PosixThreadStatus) pt_status; atomic_int ptid; // transitions 0 → tid - atomic_int pt_refs; // negative means free + atomic_int pt_refs; // prevents decimation void *(*pt_start)(void *); // creation callback void *pt_arg; // start's parameter void *pt_rc; // start's return value @@ -103,14 +103,13 @@ int _pthread_setschedparam_freebsd(int, int, const struct sched_param *); int _pthread_tid(struct PosixThread *) libcesque; intptr_t _pthread_syshand(struct PosixThread *) libcesque; long _pthread_cancel_ack(void) libcesque; -void _pthread_decimate(void) libcesque; -void _pthread_free(struct PosixThread *, bool) libcesque; +void _pthread_decimate(bool) libcesque; +void _pthread_free(struct PosixThread *) libcesque; void _pthread_lock(void) libcesque; void _pthread_onfork_child(void) libcesque; void _pthread_onfork_parent(void) libcesque; void _pthread_onfork_prepare(void) libcesque; void _pthread_unlock(void) libcesque; -void _pthread_unref(struct PosixThread *) libcesque; void _pthread_zombify(struct PosixThread *) libcesque; forceinline pureconst struct PosixThread *_pthread_self(void) { @@ -118,7 +117,11 @@ forceinline pureconst struct PosixThread *_pthread_self(void) { } forceinline void _pthread_ref(struct PosixThread *pt) { - atomic_fetch_add_explicit(&pt->pt_refs, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&pt->pt_refs, 1, memory_order_acq_rel); +} + +forceinline void _pthread_unref(struct PosixThread *pt) { + atomic_fetch_sub_explicit(&pt->pt_refs, 1, memory_order_acq_rel); } COSMOPOLITAN_C_END_ diff --git a/libc/thread/pt.internal.h b/libc/thread/pt.internal.h index d054c8bab..c211c273f 100644 --- a/libc/thread/pt.internal.h +++ b/libc/thread/pt.internal.h @@ -8,5 +8,7 @@ #define PT_MASKED 16 #define PT_INCANCEL 32 #define PT_OPENBSD_KLUDGE 64 +#define PT_EXITING 128 +#define PT_OWNSIGALTSTACK 256 #endif /* COSMOPOLITAN_LIBC_THREAD_PT_H_ */ diff --git a/libc/thread/pthread_attr_getguardsize.c b/libc/thread/pthread_attr_getguardsize.c index 3d4f394d3..fd4524efb 100644 --- a/libc/thread/pthread_attr_getguardsize.c +++ b/libc/thread/pthread_attr_getguardsize.c @@ -19,7 +19,7 @@ #include "libc/thread/thread.h" /** - * Returns size of unmapped pages at bottom of stack. + * Returns size of protected region at bottom of thread stack. * * @param guardsize will be set to guard size in bytes * @return 0 on success, or errno on error diff --git a/libc/thread/pthread_attr_getsigaltstack_np.c b/libc/thread/pthread_attr_getsigaltstack_np.c new file mode 100644 index 000000000..c261a57a4 --- /dev/null +++ b/libc/thread/pthread_attr_getsigaltstack_np.c @@ -0,0 +1,34 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/runtime/stack.h" +#include "libc/thread/thread.h" + +/** + * Returns configuration for thread signal stack. + * + * @param stackaddr will be set to signal stack address + * @return 0 on success, or errno on error + * @see pthread_attr_setsigaltstacksize_np() + */ +errno_t pthread_attr_getsigaltstack_np(const pthread_attr_t *attr, + void **stackaddr, size_t *stacksize) { + *stackaddr = attr->__sigaltstackaddr; + *stacksize = attr->__sigaltstacksize; + return 0; +} diff --git a/libc/thread/pthread_attr_getsigaltstacksize_np.c b/libc/thread/pthread_attr_getsigaltstacksize_np.c new file mode 100644 index 000000000..b6115357a --- /dev/null +++ b/libc/thread/pthread_attr_getsigaltstacksize_np.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/runtime/stack.h" +#include "libc/thread/thread.h" + +/** + * Returns size of thread signal stack. + * + * This defaults to zero, which means that cosmo won't allocate a + * managed signal stack for newly created threads. + * + * @param x will be set to stack size in bytes + * @return 0 on success, or errno on error + * @see pthread_attr_setsigaltstacksize_np() + */ +errno_t pthread_attr_getsigaltstacksize_np(const pthread_attr_t *a, + size_t *stacksize) { + *stacksize = a->__sigaltstacksize; + return 0; +} diff --git a/libc/thread/pthread_attr_setguardsize.c b/libc/thread/pthread_attr_setguardsize.c index 2e03607b2..e404ea04f 100644 --- a/libc/thread/pthread_attr_setguardsize.c +++ b/libc/thread/pthread_attr_setguardsize.c @@ -19,18 +19,16 @@ #include "libc/thread/thread.h" /** - * Sets size of unmapped pages at bottom of stack. + * Sets size of protected region at bottom of thread stack. * - * This value will be rounded up to the host microprocessor page size, - * which is usually 4096 or 16384. It's important to write code in such - * a way that that code can't skip over the guard area. GCC has warnings - * like `-Wframe-larger-than=4096 -Walloca-larger-than=4096` which help - * guarantee your code is safe in this regard. It should be assumed the - * guard pages exist beneath the stack pointer, rather than the bottom - * of the stack, since guard pages are also used to grow down commit, - * which can be poked using CheckLargeStackAllocation(). + * Cosmopolitan sets this value to `sysconf(_SC_PAGESIZE)` by default. * - * @param guardsize contains guard size in bytes + * You may set `guardsize` to disable the stack guard feature and gain a + * slight performance advantage by avoiding mprotect() calls. Note that + * it could make your code more prone to silent unreported corruption. + * + * @param guardsize contains guard size in bytes, which is implicitly + * rounded up to `sysconf(_SC_PAGESIZE)`, or zero to disable * @return 0 on success, or errno on error */ errno_t pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) { diff --git a/libc/thread/pthread_attr_setsigaltstack_np.c b/libc/thread/pthread_attr_setsigaltstack_np.c new file mode 100644 index 000000000..91c08f107 --- /dev/null +++ b/libc/thread/pthread_attr_setsigaltstack_np.c @@ -0,0 +1,42 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/dce.h" +#include "libc/errno.h" +#include "libc/limits.h" +#include "libc/runtime/runtime.h" +#include "libc/thread/thread.h" + +/** + * Defines user-owned signal stack for thread. + */ +errno_t pthread_attr_setsigaltstack_np(pthread_attr_t *attr, void *stackaddr, + size_t stacksize) { + if (!stackaddr) { + attr->__sigaltstackaddr = 0; + attr->__sigaltstacksize = 0; + return 0; + } + if (stacksize > INT_MAX) + return EINVAL; + if (stacksize < __get_minsigstksz()) + return EINVAL; + attr->__sigaltstackaddr = stackaddr; + attr->__sigaltstacksize = stacksize; + return 0; +} diff --git a/libc/thread/pthread_attr_setsigaltstacksize_np.c b/libc/thread/pthread_attr_setsigaltstacksize_np.c new file mode 100644 index 000000000..73da4ad30 --- /dev/null +++ b/libc/thread/pthread_attr_setsigaltstacksize_np.c @@ -0,0 +1,78 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et 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/errno.h" +#include "libc/limits.h" +#include "libc/runtime/runtime.h" +#include "libc/thread/thread.h" + +/** + * Defines size of cosmo-owned signal stack for thread. + * + * The sigaltstack() function is useful for writing robust programs that + * can recover from the occasional thread having a stack overflow rather + * than having the entire process crash. To use it normally, sigaltstack + * needs to be called at the start of each thread with a unique piece of + * memory. However this is challenging to do *correctly* without support + * from the POSIX threads runtime, since canceled or crashed threads may + * need to execute on the signal stack during pthread_exit() which would + * prevent a thread-local storage key destructor from free()'ing it. + * + * By default pthread_create() will not install a sigaltstack() on newly + * created threads. If this function is called, on the attributes object + * that gets passed to pthread_create(), then it'll use malloc() to make + * a stack for the thread using the size you specify here. The threading + * runtime will also free that memory safely after complete termination. + * + * pthread_t id; + * pthread_attr_t attr; + * pthread_attr_init(&attr); + * pthread_attr_setguardsize(&attr, getpagesize()); + * pthread_attr_setsigaltstacksize_np(&attr, stacksize); + * pthread_create(&id, &attr, func, 0); + * pthread_attr_destroy(&attr); + * pthread_join(id, 0); + * + * Try using a size of `sysconf(_SC_SIGSTKSZ)`. If you want the smallest + * size possible, then `sysconf(_SC_MINSIGSTKSZ) + 2048` is probably the + * smallest value that can reasonably expected to work with pthread_exit + * + * struct sigaction sa; + * sigemptyset(&sa.sa_mask); + * sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + * sa.sa_sigaction = on_crash_signal; + * sigaction(SIGSEGV, &sa, 0); + * + * Please note that in order for this to work, your handlers for signals + * such as SIGSEGV and SIGBUS need to use SA_ONSTACK in your sa_flags. + * + * @param stacksize contains stack size in bytes, or 0 to disable + * @return 0 on success, or errno on error + * @raise EINVAL if `stacksize` is less than `sysconf(_SC_MINSIGSTKSZ)` + */ +errno_t pthread_attr_setsigaltstacksize_np(pthread_attr_t *a, + size_t stacksize) { + if (stacksize) { + if (stacksize > INT_MAX) + return EINVAL; + if (stacksize < __get_minsigstksz()) + return EINVAL; + } + a->__sigaltstacksize = stacksize; + return 0; +} diff --git a/libc/thread/pthread_attr_setstack.c b/libc/thread/pthread_attr_setstack.c index f82be129f..8bfaed866 100644 --- a/libc/thread/pthread_attr_setstack.c +++ b/libc/thread/pthread_attr_setstack.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" #include "libc/errno.h" +#include "libc/limits.h" #include "libc/thread/thread.h" /** @@ -71,6 +72,8 @@ errno_t pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, attr->__stacksize = 0; return 0; } + if (stacksize > INT_MAX) + return EINVAL; if (stacksize < PTHREAD_STACK_MIN) return EINVAL; attr->__stackaddr = stackaddr; diff --git a/libc/thread/pthread_attr_setstacksize.c b/libc/thread/pthread_attr_setstacksize.c index f2ccb0c99..58e69eb15 100644 --- a/libc/thread/pthread_attr_setstacksize.c +++ b/libc/thread/pthread_attr_setstacksize.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" +#include "libc/limits.h" #include "libc/thread/thread.h" /** @@ -27,6 +28,8 @@ * @raise EINVAL if `stacksize` is less than `PTHREAD_STACK_MIN` */ errno_t pthread_attr_setstacksize(pthread_attr_t *a, size_t stacksize) { + if (stacksize > INT_MAX) + return EINVAL; if (stacksize < PTHREAD_STACK_MIN) return EINVAL; a->__stacksize = stacksize; diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index 8be2420c7..42a3b45a1 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigaltstack.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -67,54 +68,104 @@ __static_yoink("_pthread_onfork_child"); #define MAP_ANON_OPENBSD 0x1000 #define MAP_STACK_OPENBSD 0x4000 -void _pthread_free(struct PosixThread *pt, bool isfork) { +void _pthread_free(struct PosixThread *pt) { + + // thread must be removed from _pthread_list before calling unassert(dll_is_alone(&pt->list) && &pt->list != _pthread_list); + + // do nothing for the one and only magical statical posix thread if (pt->pt_flags & PT_STATIC) return; + + // unmap stack if the cosmo runtime was responsible for mapping it if (pt->pt_flags & PT_OWNSTACK) unassert(!munmap(pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize)); - if (!isfork) { - uint64_t syshand = - atomic_load_explicit(&pt->tib->tib_syshand, memory_order_acquire); - if (syshand) { - if (IsWindows()) - unassert(CloseHandle(syshand)); - else if (IsXnuSilicon()) - __syslib->__pthread_join(syshand, 0); - } + + // free any additional upstream system resources + // our fork implementation wipes this handle in child automatically + uint64_t syshand = + atomic_load_explicit(&pt->tib->tib_syshand, memory_order_acquire); + if (syshand) { + if (IsWindows()) + unassert(CloseHandle(syshand)); // non-inheritable + else if (IsXnuSilicon()) + unassert(!__syslib->__pthread_join(syshand, 0)); } + + // free heap memory associated with thread + if (pt->pt_flags & PT_OWNSIGALTSTACK) + free(pt->pt_attr.__sigaltstackaddr); free(pt->pt_tls); free(pt); } -void _pthread_decimate(void) { - struct Dll *e; +void _pthread_decimate(bool annihilation_only) { struct PosixThread *pt; + struct Dll *e, *e2, *list = 0; enum PosixThreadStatus status; -StartOver: + + // acquire posix threads gil _pthread_lock(); - for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) { + + // swiftly remove every single zombie + // that isn't being held by a killing thread + for (e = dll_last(_pthread_list); e; e = e2) { + e2 = dll_prev(_pthread_list, e); pt = POSIXTHREAD_CONTAINER(e); + if (atomic_load_explicit(&pt->pt_refs, memory_order_acquire) > 0) + continue; // pthread_kill() has a lease on this thread status = atomic_load_explicit(&pt->pt_status, memory_order_acquire); if (status != kPosixThreadZombie) - break; - if (!atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) { - dll_remove(&_pthread_list, e); - _pthread_unlock(); - _pthread_unref(pt); - goto StartOver; - } + break; // zombies only exist at the end of the linked list + if (atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) + continue; // undead thread should that'll stop existing soon + dll_remove(&_pthread_list, e); + dll_make_first(&list, e); } + + // code like pthread_exit() needs to call this in order to know if + // it's appropriate to run exit() handlers however we really don't + // want to have a thread exiting block on a bunch of __maps locks! + // therefore we only take action if we'll destroy all but the self + if (annihilation_only) + if (!(_pthread_list == _pthread_list->prev && + _pthread_list == _pthread_list->next)) { + dll_make_last(&_pthread_list, list); + list = 0; + } + + // release posix threads gil _pthread_unlock(); + + // now free our thread local batch of zombies + // because death is a release and not a punishment + // this is advantaged by not holding locks over munmap + while ((e = dll_first(list))) { + pt = POSIXTHREAD_CONTAINER(e); + dll_remove(&list, e); + _pthread_free(pt); + } } static int PosixThread(void *arg, int tid) { void *rc; struct PosixThread *pt = arg; + + // setup scheduling if (pt->pt_attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) { unassert(_weaken(_pthread_reschedule)); _weaken(_pthread_reschedule)(pt); // yoinked by attribute builder } + + // setup signal stack + if (pt->pt_attr.__sigaltstacksize) { + struct sigaltstack ss; + ss.ss_sp = pt->pt_attr.__sigaltstackaddr; + ss.ss_size = pt->pt_attr.__sigaltstacksize; + ss.ss_flags = 0; + unassert(!sigaltstack(&ss, 0)); + } + // set long jump handler so pthread_exit can bring control back here if (!setjmp(pt->pt_exiter)) { sigdelset(&pt->pt_attr.__sigmask, SIGTHR); @@ -130,41 +181,45 @@ static int PosixThread(void *arg, int tid) { // calling pthread_exit() will either jump back here, or call exit pthread_exit(rc); } + // avoid signal handler being triggered after we trash our own stack __sig_block(); + // return to clone polyfill which clears tid, wakes futex, and exits return 0; } -static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { - // OpenBSD: Only permits RSP to occupy memory that's been explicitly - // defined as stack memory. We need to squeeze the provided interval - // in order to successfully call mmap(), which will return EINVAL if - // these calculations should overflow. - size_t n; - uintptr_t x, y; - int e, rc, pagesz; - pagesz = __pagesize; - n = attr->__stacksize; - x = (uintptr_t)attr->__stackaddr; - y = ROUNDUP(x, pagesz); - n -= y - x; - n = ROUNDDOWN(n, pagesz); - e = errno; - if (__sys_mmap((void *)y, n, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, - -1, 0, 0) == (void *)y) { - attr->__stackaddr = (void *)y; - attr->__stacksize = n; - return 0; - } else { - rc = errno; - errno = e; - if (rc == EOVERFLOW) { - rc = EINVAL; - } - return rc; +static bool TellOpenbsdThisIsStackMemory(void *addr, size_t size) { + return __sys_mmap( + addr, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, -1, + 0, 0) == addr; +} + +// OpenBSD only permits RSP to occupy memory that's been explicitly +// defined as stack memory, i.e. `lo <= %rsp < hi` must be the case +static errno_t FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { + + // get interval + uintptr_t lo = (uintptr_t)attr->__stackaddr; + uintptr_t hi = lo + attr->__stacksize; + + // squeeze interval + lo = (lo + __pagesize - 1) & -__pagesize; + hi = hi & -__pagesize; + + // tell os it's stack memory + errno_t olderr = errno; + if (!TellOpenbsdThisIsStackMemory((void *)lo, hi - lo)) { + errno_t err = errno; + errno = olderr; + return err; } + + // update attributes with usable stack address + attr->__stackaddr = (void *)lo; + attr->__stacksize = hi - lo; + return 0; } static errno_t pthread_create_impl(pthread_t *thread, @@ -204,7 +259,7 @@ static errno_t pthread_create_impl(pthread_t *thread, // assume they know what they're doing as much as possible if (IsOpenbsd()) { if ((rc = FixupCustomStackOnOpenbsd(&pt->pt_attr))) { - _pthread_free(pt, false); + _pthread_free(pt); return rc; } } @@ -214,21 +269,17 @@ static errno_t pthread_create_impl(pthread_t *thread, pt->pt_attr.__guardsize = ROUNDUP(pt->pt_attr.__guardsize, pagesize); pt->pt_attr.__stacksize = pt->pt_attr.__stacksize; if (pt->pt_attr.__guardsize + pagesize > pt->pt_attr.__stacksize) { - _pthread_free(pt, false); + _pthread_free(pt); return EINVAL; } pt->pt_attr.__stackaddr = mmap(0, pt->pt_attr.__stacksize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (pt->pt_attr.__stackaddr != MAP_FAILED) { - if (IsOpenbsd() && - __sys_mmap( - pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, - -1, 0, 0) != pt->pt_attr.__stackaddr) { - notpossible; - } + if (IsOpenbsd()) + if (!TellOpenbsdThisIsStackMemory(pt->pt_attr.__stackaddr, + pt->pt_attr.__stacksize)) + notpossible; if (pt->pt_attr.__guardsize) if (mprotect(pt->pt_attr.__stackaddr, pt->pt_attr.__guardsize, PROT_NONE | PROT_GUARD)) @@ -236,7 +287,7 @@ static errno_t pthread_create_impl(pthread_t *thread, } if (!pt->pt_attr.__stackaddr || pt->pt_attr.__stackaddr == MAP_FAILED) { rc = errno; - _pthread_free(pt, false); + _pthread_free(pt); errno = e; if (rc == EINVAL || rc == EOVERFLOW) { return EINVAL; @@ -247,6 +298,18 @@ static errno_t pthread_create_impl(pthread_t *thread, pt->pt_flags |= PT_OWNSTACK; } + // setup signal stack + if (pt->pt_attr.__sigaltstacksize) { + if (!pt->pt_attr.__sigaltstackaddr) { + if (!(pt->pt_attr.__sigaltstackaddr = + malloc(pt->pt_attr.__sigaltstacksize))) { + _pthread_free(pt); + return errno; + } + pt->pt_flags |= PT_OWNSIGALTSTACK; + } + } + // set initial status pt->tib->tib_pthread = (pthread_t)pt; atomic_store_explicit(&pt->tib->tib_sigmask, -1, memory_order_relaxed); @@ -264,7 +327,7 @@ static errno_t pthread_create_impl(pthread_t *thread, memory_order_relaxed); break; default: - _pthread_free(pt, false); + _pthread_free(pt); return EINVAL; } @@ -284,7 +347,7 @@ static errno_t pthread_create_impl(pthread_t *thread, _pthread_lock(); dll_remove(&_pthread_list, &pt->list); _pthread_unlock(); - _pthread_free(pt, false); + _pthread_free(pt); return rc; } @@ -353,7 +416,7 @@ static const char *DescribeHandle(char buf[12], errno_t err, pthread_t *th) { errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { errno_t err; - _pthread_decimate(); + _pthread_decimate(false); BLOCK_SIGNALS; err = pthread_create_impl(thread, attr, start_routine, arg, _SigMask); ALLOW_SIGNALS; diff --git a/libc/thread/pthread_decimate_np.c b/libc/thread/pthread_decimate_np.c index a6bc591cf..3027dc7fa 100644 --- a/libc/thread/pthread_decimate_np.c +++ b/libc/thread/pthread_decimate_np.c @@ -32,6 +32,6 @@ * @return 0 on success, or errno on error */ int pthread_decimate_np(void) { - _pthread_decimate(); + _pthread_decimate(false); return 0; } diff --git a/libc/thread/pthread_detach.c b/libc/thread/pthread_detach.c index 2a33b4988..2456ec69f 100644 --- a/libc/thread/pthread_detach.c +++ b/libc/thread/pthread_detach.c @@ -40,10 +40,8 @@ static errno_t pthread_detach_impl(struct PosixThread *pt) { if (atomic_compare_exchange_weak_explicit(&pt->pt_status, &status, transition, memory_order_release, memory_order_relaxed)) { - if (transition == kPosixThreadZombie) { + if (transition == kPosixThreadZombie) _pthread_zombify(pt); - } - _pthread_decimate(); return 0; } } diff --git a/libc/thread/pthread_exit.c b/libc/thread/pthread_exit.c index 7e5c941a1..933f041a2 100644 --- a/libc/thread/pthread_exit.c +++ b/libc/thread/pthread_exit.c @@ -69,23 +69,33 @@ * @noreturn */ wontreturn void pthread_exit(void *rc) { + int orphan; struct CosmoTib *tib; struct PosixThread *pt; enum PosixThreadStatus status, transition; + STRACE("pthread_exit(%p)", rc); + + // get posix thread object tib = __get_tls(); pt = (struct PosixThread *)tib->tib_pthread; - pt->pt_flags |= PT_NOCANCEL; - pt->pt_rc = rc; - STRACE("pthread_exit(%p)", rc); + // "The behavior of pthread_exit() is undefined if called from a + // cancellation cleanup handler or destructor function that was + // invoked as a result of either an implicit or explicit call to + // pthread_exit()." ──Quoth POSIX.1-2017 + unassert(!(pt->pt_flags & PT_EXITING)); + + // set state + pt->pt_flags |= PT_NOCANCEL | PT_EXITING; + pt->pt_rc = rc; // free resources __cxa_thread_finalize(); - _pthread_decimate(); // run atexit handlers if orphaned thread - if (pthread_orphan_np()) + _pthread_decimate(true); + if ((orphan = pthread_orphan_np())) if (_weaken(__cxa_finalize)) _weaken(__cxa_finalize)(NULL); @@ -113,8 +123,11 @@ wontreturn void pthread_exit(void *rc) { if (transition == kPosixThreadZombie) _pthread_zombify(pt); - // check if this is the last survivor - if (pthread_orphan_np()) { + // "The process shall exit with an exit status of 0 after the last + // thread has been terminated. The behavior shall be as if the + // implementation called exit() with a zero argument at thread + // termination time." ──Quoth POSIX.1-2017 + if (orphan) { for (const uintptr_t *p = __fini_array_end; p > __fini_array_start;) ((void (*)(void))(*--p))(); _Exit(0); diff --git a/libc/thread/pthread_timedjoin_np.c b/libc/thread/pthread_timedjoin_np.c index f814e038a..9dcc410a0 100644 --- a/libc/thread/pthread_timedjoin_np.c +++ b/libc/thread/pthread_timedjoin_np.c @@ -55,29 +55,38 @@ static const char *DescribeReturnValue(char buf[30], int err, void **value) { * * @return 0 on success, or errno on error * @raise ECANCELED if calling thread was cancelled in masked mode + * @raise EDEADLK if `ctid` refers calling thread's own ctid futex * @raise EBUSY if `abstime` was specified and deadline expired * @cancelationpoint */ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) { - int x, e, rc = 0; - unassert(ctid != &__get_tls()->tib_tid); - // "If the thread calling pthread_join() is canceled, then the target - // thread shall not be detached." ──Quoth POSIX.1-2017 - if (!(rc = pthread_testcancel_np())) { - BEGIN_CANCELATION_POINT; - while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { - e = nsync_futex_wait_(ctid, x, !IsWindows() && !IsXnu(), abstime); - if (e == -ECANCELED) { - rc = ECANCELED; - break; - } else if (e == -ETIMEDOUT) { - rc = EBUSY; - break; + int x, e; + errno_t err = 0; + if (ctid == &__get_tls()->tib_tid) { + // "If an implementation detects that the value specified by the + // thread argument to pthread_join() refers to the calling thread, + // it is recommended that the function should fail and report an + // [EDEADLK] error." ──Quoth POSIX.1-2017 + err = EDEADLK; + } else { + // "If the thread calling pthread_join() is canceled, then the target + // thread shall not be detached." ──Quoth POSIX.1-2017 + if (!(err = pthread_testcancel_np())) { + BEGIN_CANCELATION_POINT; + while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { + e = nsync_futex_wait_(ctid, x, !IsWindows() && !IsXnu(), abstime); + if (e == -ECANCELED) { + err = ECANCELED; + break; + } else if (e == -ETIMEDOUT) { + err = EBUSY; + break; + } } + END_CANCELATION_POINT; } - END_CANCELATION_POINT; } - return rc; + return err; } /** @@ -97,6 +106,7 @@ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) { * when we'll stop waiting; if this is null we will wait forever * @return 0 on success, or errno on error * @raise ECANCELED if calling thread was cancelled in masked mode + * @raise EDEADLK if `thread` refers to calling thread * @raise EBUSY if `abstime` deadline elapsed * @cancelationpoint * @returnserrno @@ -104,26 +114,29 @@ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) { errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr, struct timespec *abstime) { int tid; - errno_t err; + errno_t err = 0; struct PosixThread *pt; enum PosixThreadStatus status; pt = (struct PosixThread *)thread; - tid = _pthread_tid(pt); - unassert(_pthread_tid(pt)); - status = atomic_load_explicit(&pt->pt_status, memory_order_acquire); + // "The behavior is undefined if the value specified by the thread // argument to pthread_join() does not refer to a joinable thread." // ──Quoth POSIX.1-2017 + unassert((tid = _pthread_tid(pt))); + status = atomic_load_explicit(&pt->pt_status, memory_order_acquire); unassert(status == kPosixThreadJoinable || status == kPosixThreadTerminated); + + // "The results of multiple simultaneous calls to pthread_join() + // specifying the same target thread are undefined." + // ──Quoth POSIX.1-2017 if (!(err = _pthread_wait(&pt->tib->tib_tid, abstime))) { - _pthread_lock(); - dll_remove(&_pthread_list, &pt->list); - _pthread_unlock(); - if (value_ptr) { + atomic_store_explicit(&pt->pt_status, kPosixThreadZombie, + memory_order_release); + _pthread_zombify(pt); + if (value_ptr) *value_ptr = pt->pt_rc; - } - _pthread_unref(pt); } + STRACE("pthread_timedjoin_np(%d, %s, %s) → %s", tid, DescribeReturnValue(alloca(30), err, value_ptr), DescribeTimespec(err ? -1 : 0, abstime), DescribeErrno(err)); diff --git a/libc/thread/thread.h b/libc/thread/thread.h index 872fdcc37..af8ecd60c 100644 --- a/libc/thread/thread.h +++ b/libc/thread/thread.h @@ -94,9 +94,11 @@ typedef struct pthread_attr_s { int __schedpolicy; int __contentionscope; int __guardsize; - size_t __stacksize; + int __stacksize; + int __sigaltstacksize; uint64_t __sigmask; void *__stackaddr; + void *__sigaltstackaddr; } pthread_attr_t; struct _pthread_cleanup_buffer { @@ -117,6 +119,8 @@ int pthread_attr_getschedpolicy(const pthread_attr_t *, int *) libcesque paramsn int pthread_attr_getscope(const pthread_attr_t *, int *) libcesque paramsnonnull(); int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull(); int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull(); +int pthread_attr_getsigaltstack_np(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull(); +int pthread_attr_getsigaltstacksize_np(const pthread_attr_t *, size_t *) libcesque paramsnonnull(); int pthread_attr_init(pthread_attr_t *) libcesque paramsnonnull(); int pthread_attr_setdetachstate(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setguardsize(pthread_attr_t *, size_t) libcesque paramsnonnull(); @@ -125,6 +129,8 @@ int pthread_attr_setschedpolicy(pthread_attr_t *, int) libcesque paramsnonnull() int pthread_attr_setscope(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1)); int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull(); +int pthread_attr_setsigaltstack_np(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1)); +int pthread_attr_setsigaltstacksize_np(pthread_attr_t *, size_t); int pthread_barrier_destroy(pthread_barrier_t *) libcesque paramsnonnull(); int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, unsigned) libcesque paramsnonnull((1)); int pthread_barrier_wait(pthread_barrier_t *) libcesque paramsnonnull(); diff --git a/test/libc/calls/stackoverflow5_test.c b/test/libc/calls/stackoverflow5_test.c new file mode 100644 index 000000000..611604448 --- /dev/null +++ b/test/libc/calls/stackoverflow5_test.c @@ -0,0 +1,86 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 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 +#include +#include +#include +#include + +/** + * stack overflow recovery technique #5 + * use the cosmo posix threads extensions + */ + +sig_atomic_t smashed_stack; + +void CrashHandler(int sig) { + smashed_stack = true; + pthread_exit(0); +} + +int StackOverflow(int f(), int n) { + if (n < INT_MAX) { + return f(f, n + 1) - 1; + } else { + return INT_MAX; + } +} + +int (*pStackOverflow)(int (*)(), int) = StackOverflow; + +void *MyPosixThread(void *arg) { + exit(pStackOverflow(pStackOverflow, 0)); + return 0; +} + +int main() { + + // choose the most dangerously small size possible + size_t sigstacksize = sysconf(_SC_MINSIGSTKSZ) + 2048; + + // setup signal handler + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_ONSTACK; + sa.sa_handler = CrashHandler; + if (sigaction(SIGBUS, &sa, 0)) + return 1; + if (sigaction(SIGSEGV, &sa, 0)) + return 2; + + // create thread with signal stack + pthread_t id; + pthread_attr_t attr; + if (pthread_attr_init(&attr)) + return 3; + if (pthread_attr_setguardsize(&attr, getpagesize())) + return 4; + if (pthread_attr_setsigaltstacksize_np(&attr, sigstacksize)) + return 5; + if (pthread_create(&id, &attr, MyPosixThread, 0)) + return 6; + if (pthread_attr_destroy(&attr)) + return 7; + if (pthread_join(id, 0)) + return 8; + if (!smashed_stack) + return 9; + + CheckForMemoryLeaks(); +} diff --git a/test/libc/intrin/cosmo_once_test.c b/test/libc/intrin/cosmo_once_test.c index c0e7d54ee..aaf8cba51 100644 --- a/test/libc/intrin/cosmo_once_test.c +++ b/test/libc/intrin/cosmo_once_test.c @@ -50,12 +50,10 @@ TEST(cosmo_once, test) { pthread_t th[N]; x = y = 0; ASSERT_EQ(0, pthread_barrier_init(&b, 0, N)); - for (i = 0; i < N; ++i) { + for (i = 0; i < N; ++i) ASSERT_EQ(0, pthread_create(th + i, 0, Worker, 0)); - } - for (i = 0; i < N; ++i) { + for (i = 0; i < N; ++i) ASSERT_EQ(0, pthread_join(th[i], 0)); - } ASSERT_EQ(N, atomic_load(&x)); ASSERT_EQ(1, atomic_load(&y)); ASSERT_EQ(0, pthread_barrier_destroy(&b)); diff --git a/test/libc/intrin/mmap_test.c b/test/libc/intrin/mmap_test.c index 7f91daed7..5044d6f96 100644 --- a/test/libc/intrin/mmap_test.c +++ b/test/libc/intrin/mmap_test.c @@ -528,18 +528,21 @@ TEST(mmap, sharedFileMapFork) { int count; void *ptrs[N]; +size_t sizes[N]; void BenchMmapPrivate(void) { void *p; - p = mmap(0, gransz * 10, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, - -1, 0); + p = mmap(0, (sizes[count] = rand() % (pagesz * 500)), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) __builtin_trap(); - ptrs[count++] = p; + ptrs[count] = p; + ++count; } void BenchUnmap(void) { - if (munmap(ptrs[--count], gransz * 10)) + --count; + if (munmap(ptrs[count], sizes[count])) __builtin_trap(); } @@ -557,7 +560,7 @@ void BenchBigMunmap(void) { __builtin_trap(); } -BENCH(mmap, bench) { +TEST(mmap, bench) { EZBENCH2("mmap", donothing, BenchMmapPrivate()); EZBENCH2("munmap", donothing, BenchUnmap()); // EZBENCH2("big mmap", donothing, BenchBigMmap()); diff --git a/test/libc/thread/pthread_cancel_deferred_cond_test.c b/test/libc/thread/pthread_cancel_deferred_cond_test.c new file mode 100644 index 000000000..7bf8e1045 --- /dev/null +++ b/test/libc/thread/pthread_cancel_deferred_cond_test.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +int got_cleanup; +pthread_cond_t cv = PTHREAD_COND_INITIALIZER; +pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER; + +void cleanup(void* arg) { + got_cleanup = 1; +} + +void* worker(void* arg) { + pthread_cleanup_push(cleanup, 0); + if (pthread_mutex_lock(&mu)) + _Exit(11); + pthread_cond_wait(&cv, &mu); + _Exit(12); + pthread_cleanup_pop(0); +} + +int main(int argc, char* argv[]) { + void* rc; + pthread_t th; + if (pthread_create(&th, 0, worker, 0)) + return 2; + if (pthread_cancel(th)) + return 3; + if (pthread_join(th, &rc)) + return 4; + if (rc != PTHREAD_CANCELED) + return 5; + if (!got_cleanup) + return 6; + if (pthread_mutex_trylock(&mu) != EBUSY) + return 7; + if (pthread_mutex_unlock(&mu)) + return 8; +} diff --git a/test/libc/thread/pthread_cancel_masked_cond_test.c b/test/libc/thread/pthread_cancel_masked_cond_test.c new file mode 100644 index 000000000..479ebf48d --- /dev/null +++ b/test/libc/thread/pthread_cancel_masked_cond_test.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int got_cleanup; +pthread_cond_t cv = PTHREAD_COND_INITIALIZER; +pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER; + +void cleanup(void* arg) { + got_cleanup = 1; +} + +void* worker(void* arg) { + if (pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)) + _Exit(10); + pthread_cleanup_push(cleanup, 0); + if (pthread_mutex_lock(&mu)) + _Exit(11); + if (pthread_cond_wait(&cv, &mu) != ECANCELED) + _Exit(12); + if (pthread_mutex_trylock(&mu) != EBUSY) + _Exit(13); + if (pthread_mutex_unlock(&mu)) + _Exit(14); + pthread_cleanup_pop(0); + return (void*)123; +} + +int main(int argc, char* argv[]) { + void* rc; + pthread_t th; + if (pthread_create(&th, 0, worker, 0)) + return 2; + if (pthread_cancel(th)) + return 3; + if (pthread_join(th, &rc)) + return 4; + if (rc != (void*)123) + return 5; + if (got_cleanup) + return 6; +} diff --git a/test/libc/thread/pthread_cancel_masked_read_test.c b/test/libc/thread/pthread_cancel_masked_read_test.c new file mode 100644 index 000000000..1608df770 --- /dev/null +++ b/test/libc/thread/pthread_cancel_masked_read_test.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +int pfds[2]; +int got_cleanup; + +void cleanup(void* arg) { + got_cleanup = 1; +} + +void* worker(void* arg) { + char buf[8]; + if (pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)) + _Exit(10); + pthread_cleanup_push(cleanup, 0); + if (read(pfds[0], buf, sizeof(buf)) != -1) + _Exit(11); + if (errno != ECANCELED) + _Exit(12); + pthread_cleanup_pop(0); + return (void*)123; +} + +int main(int argc, char* argv[]) { + void* rc; + pthread_t th; + if (pipe(pfds)) + return 1; + if (pthread_create(&th, 0, worker, 0)) + return 2; + if (pthread_cancel(th)) + return 3; + if (pthread_join(th, &rc)) + return 4; + if (rc != (void*)123) + return 5; + if (got_cleanup) + return 7; +} diff --git a/test/libc/thread/pthread_create_inherit_mask_test.c b/test/libc/thread/pthread_create_inherit_mask_test.c new file mode 100644 index 000000000..58a0460f7 --- /dev/null +++ b/test/libc/thread/pthread_create_inherit_mask_test.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +sigset_t parent_mask; +sigset_t child_mask; + +void* worker(void* arg) { + if (pthread_sigmask(SIG_SETMASK, 0, &child_mask)) + _Exit(1); + return 0; +} + +int main(int argc, char* argv[]) { + pthread_t th; + sigemptyset(&parent_mask); + sigaddset(&parent_mask, SIGSYS); + sigaddset(&parent_mask, SIGUSR2); + sigaddset(&parent_mask, SIGWINCH); + if (pthread_sigmask(SIG_SETMASK, &parent_mask, 0)) + return 1; + if (pthread_create(&th, 0, worker, 0)) + return 2; + if (pthread_join(th, 0)) + return 3; + for (int i = 1; i <= _NSIG; ++i) + if (sigismember(&parent_mask, i) != sigismember(&child_mask, i)) + return 4; +} diff --git a/test/libc/thread/pthread_create_test.c b/test/libc/thread/pthread_create_test.c index 697b8f1f0..d977dd0dc 100644 --- a/test/libc/thread/pthread_create_test.c +++ b/test/libc/thread/pthread_create_test.c @@ -34,6 +34,7 @@ #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" #include "libc/runtime/sysconf.h" +#include "libc/stdio/rand.h" #include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sched.h" @@ -279,11 +280,10 @@ static void CreateDetached(void) { ASSERT_EQ(0, pthread_attr_destroy(&attr)); } -BENCH(pthread_create, bench) { +TEST(pthread_create, bench) { EZBENCH2("CreateJoin", donothing, CreateJoin()); EZBENCH2("CreateDetach", donothing, CreateDetach()); EZBENCH2("CreateDetached", donothing, CreateDetached()); - while (!pthread_orphan_np()) { - _pthread_decimate(); - } + while (!pthread_orphan_np()) + pthread_decimate_np(); } diff --git a/test/libc/thread/pthread_detach_test.c b/test/libc/thread/pthread_detach_test.c index f491a2da8..d5f7d80a3 100644 --- a/test/libc/thread/pthread_detach_test.c +++ b/test/libc/thread/pthread_detach_test.c @@ -50,9 +50,8 @@ TEST(pthread_detach, testCreateReturn) { pthread_t id; ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0)); ASSERT_EQ(0, pthread_detach(id)); - while (!pthread_orphan_np()) { - _pthread_decimate(); - } + while (!pthread_orphan_np()) + pthread_decimate_np(); } TEST(pthread_detach, testDetachUponCreation) { @@ -62,7 +61,6 @@ TEST(pthread_detach, testDetachUponCreation) { ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); ASSERT_EQ(0, pthread_create(&th, &attr, Increment, 0)); ASSERT_EQ(0, pthread_attr_destroy(&attr)); - while (!pthread_orphan_np()) { - _pthread_decimate(); - } + while (!pthread_orphan_np()) + pthread_decimate_np(); }