mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-06 03:08:31 +00:00
Make improvements
- We now serialize the file descriptor table when spawning / executing processes on Windows. This means you can now inherit more stuff than just standard i/o. It's needed by bash, which duplicates the console to file descriptor #255. We also now do a better job serializing the environment variables, so you're less likely to encounter E2BIG when using your bash shell. We also no longer coerce environ to uppercase - execve() on Windows now remotely controls its parent process to make them spawn a replacement for itself. Then it'll be able to terminate immediately once the spawn succeeds, without having to linger around for the lifetime as a shell process for proxying the exit code. When process worker thread running in the parent sees the child die, it's given a handle to the new child, to replace it in the process table. - execve() and posix_spawn() on Windows will now provide CreateProcess an explicit handle list. This allows us to remove handle locks which enables better fork/spawn concurrency, with seriously correct thread safety. Other codebases like Go use the same technique. On the other hand fork() still favors the conventional WIN32 inheritence approach which can be a little bit messy, but is *controlled* by guaranteeing perfectly clean slates at both the spawning and execution boundaries - sigset_t is now 64 bits. Having it be 128 bits was a mistake because there's no reason to use that and it's only supported by FreeBSD. By using the system word size, signal mask manipulation on Windows goes very fast. Furthermore @asyncsignalsafe funcs have been rewritten on Windows to take advantage of signal masking, now that it's much more pleasant to use. - All the overlapped i/o code on Windows has been rewritten for pretty good signal and cancelation safety. We're now able to ensure overlap data structures are cleaned up so long as you don't longjmp() out of out of a signal handler that interrupted an i/o operation. Latencies are also improved thanks to the removal of lots of "busy wait" code. Waits should be optimal for everything except poll(), which shall be the last and final demon we slay in the win32 i/o horror show. - getrusage() on Windows is now able to report RUSAGE_CHILDREN as well as RUSAGE_SELF, thanks to aggregation in the process manager thread.
This commit is contained in:
parent
af7cb3c82f
commit
791f79fcb3
382 changed files with 4008 additions and 4511 deletions
|
@ -17,30 +17,23 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/atomic.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/struct/sigaction.h"
|
||||
#include "libc/calls/struct/siginfo.h"
|
||||
#include "libc/calls/struct/sigset.h"
|
||||
#include "libc/calls/struct/ucontext-freebsd.internal.h"
|
||||
#include "libc/calls/struct/ucontext.internal.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/calls/syscall_support-sysv.internal.h"
|
||||
#include "libc/calls/ucontext.h"
|
||||
#include "libc/cosmo.h"
|
||||
#include "libc/dce.h"
|
||||
#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/enum/context.h"
|
||||
#include "libc/nt/enum/threadaccess.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/context.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/runtime/syslib.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/sa.h"
|
||||
#include "libc/sysv/consts/sicode.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
|
@ -58,7 +51,10 @@ long _pthread_cancel_ack(void) {
|
|||
(pt->pt_flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
pt->pt_flags |= PT_NOCANCEL | PT_OPENBSD_KLUDGE;
|
||||
pt->pt_flags |= PT_NOCANCEL;
|
||||
if (IsOpenbsd()) {
|
||||
pt->pt_flags |= PT_OPENBSD_KLUDGE;
|
||||
}
|
||||
return ecanceled();
|
||||
}
|
||||
|
||||
|
@ -70,7 +66,7 @@ static void _pthread_cancel_sig(int sig, siginfo_t *si, void *arg) {
|
|||
if (!__tls_enabled) return;
|
||||
if (!(pt = _pthread_self())) return;
|
||||
if (pt->pt_flags & PT_NOCANCEL) return;
|
||||
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return;
|
||||
if (!atomic_load_explicit(&pt->pt_canceled, memory_order_acquire)) return;
|
||||
|
||||
// in asynchronous mode we'll just the exit asynchronously
|
||||
if (pt->pt_flags & PT_ASYNC) {
|
||||
|
@ -84,61 +80,38 @@ static void _pthread_cancel_sig(int sig, siginfo_t *si, void *arg) {
|
|||
|
||||
// check for race condition between pre-check and syscall
|
||||
// rewrite the thread's execution state to acknowledge it
|
||||
if (systemfive_cancellable <= (char *)ctx->uc_mcontext.PC &&
|
||||
(char *)ctx->uc_mcontext.PC < systemfive_cancellable_end) {
|
||||
ctx->uc_mcontext.PC = (intptr_t)systemfive_cancel;
|
||||
return;
|
||||
// sadly windows isn't able to be sophisticated like this
|
||||
if (!IsWindows()) {
|
||||
if (systemfive_cancellable <= (char *)ctx->uc_mcontext.PC &&
|
||||
(char *)ctx->uc_mcontext.PC < systemfive_cancellable_end) {
|
||||
ctx->uc_mcontext.PC = (intptr_t)systemfive_cancel;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// punts cancellation to start of next cancellation point
|
||||
// punts cancelation to start of next cancellation point
|
||||
// we ensure sigthr is a pending signal in case unblocked
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
static void _pthread_cancel_listen(void) {
|
||||
struct sigaction sa;
|
||||
if (!IsWindows()) {
|
||||
sa.sa_sigaction = _pthread_cancel_sig;
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
memset(&sa.sa_mask, -1, sizeof(sa.sa_mask));
|
||||
npassert(!sigaction(SIGTHR, &sa, 0));
|
||||
}
|
||||
struct sigaction sa = {
|
||||
.sa_mask = -1,
|
||||
.sa_flags = SA_SIGINFO,
|
||||
.sa_sigaction = _pthread_cancel_sig,
|
||||
};
|
||||
sigaction(SIGTHR, &sa, 0);
|
||||
}
|
||||
|
||||
static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) {
|
||||
uint32_t old_suspend_count;
|
||||
if (!(pt->pt_flags & PT_NOCANCEL)) {
|
||||
if ((pt->pt_flags & PT_ASYNC) &&
|
||||
(old_suspend_count = SuspendThread(hThread)) != -1u) {
|
||||
if (!old_suspend_count) {
|
||||
struct NtContext cpu;
|
||||
cpu.ContextFlags = kNtContextControl | kNtContextInteger;
|
||||
if (GetThreadContext(hThread, &cpu)) {
|
||||
cpu.Rip = (uintptr_t)pthread_exit;
|
||||
cpu.Rdi = (uintptr_t)PTHREAD_CANCELED;
|
||||
cpu.Rsp &= -16;
|
||||
*(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip;
|
||||
unassert(SetThreadContext(hThread, &cpu));
|
||||
}
|
||||
}
|
||||
ResumeThread(hThread);
|
||||
}
|
||||
pt->abort_errno = ECANCELED;
|
||||
__sig_cancel(pt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
||||
static errno_t _pthread_cancel_single(struct PosixThread *pt) {
|
||||
|
||||
// install our special signal handler
|
||||
static bool once;
|
||||
if (!once) {
|
||||
_pthread_cancel_listen();
|
||||
once = true;
|
||||
}
|
||||
static atomic_uint once;
|
||||
cosmo_once(&once, _pthread_cancel_listen);
|
||||
|
||||
// check if thread is already dead
|
||||
switch (atomic_load_explicit(&pt->status, memory_order_acquire)) {
|
||||
// we don't care about any further esrch checks upstream
|
||||
switch (atomic_load_explicit(&pt->pt_status, memory_order_acquire)) {
|
||||
case kPosixThreadZombie:
|
||||
case kPosixThreadTerminated:
|
||||
return ESRCH;
|
||||
|
@ -146,12 +119,11 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
break;
|
||||
}
|
||||
|
||||
// flip the bit indicating that this thread is cancelled
|
||||
atomic_store_explicit(&pt->cancelled, 1, memory_order_release);
|
||||
// erase this thread from the book of life
|
||||
atomic_store_explicit(&pt->pt_canceled, 1, memory_order_release);
|
||||
|
||||
// does this thread want to cancel itself?
|
||||
// does this thread want to cancel itself? just exit
|
||||
if (pt == _pthread_self()) {
|
||||
unassert(!(pt->pt_flags & PT_NOCANCEL));
|
||||
if (!(pt->pt_flags & (PT_NOCANCEL | PT_MASKED)) &&
|
||||
(pt->pt_flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
|
@ -159,24 +131,29 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// send the cancelation signal
|
||||
errno_t err;
|
||||
if (IsWindows()) {
|
||||
pthread_cancel_nt(pt, _pthread_syshand(pt));
|
||||
err = 0;
|
||||
} else if (IsXnuSilicon()) {
|
||||
err = __syslib->__pthread_kill(_pthread_syshand(pt), SIGTHR);
|
||||
} else {
|
||||
int e = errno;
|
||||
if (!sys_tkill(_pthread_tid(pt), SIGTHR, pt->tib)) {
|
||||
err = pthread_kill((pthread_t)pt, SIGTHR);
|
||||
if (err == ESRCH) err = 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
static errno_t _pthread_cancel_everyone(void) {
|
||||
errno_t err;
|
||||
struct Dll *e;
|
||||
struct PosixThread *other;
|
||||
err = ESRCH;
|
||||
_pthread_lock();
|
||||
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
|
||||
other = POSIXTHREAD_CONTAINER(e);
|
||||
if (other != _pthread_self() &&
|
||||
atomic_load_explicit(&other->pt_status, memory_order_acquire) <
|
||||
kPosixThreadTerminated) {
|
||||
_pthread_cancel_single(other);
|
||||
err = 0;
|
||||
} else {
|
||||
err = errno;
|
||||
errno = e;
|
||||
}
|
||||
}
|
||||
if (err == ESRCH) {
|
||||
err = 0; // we already reported this
|
||||
}
|
||||
_pthread_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -185,18 +162,18 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
*
|
||||
* When a thread is cancelled, it'll interrupt blocking i/o calls,
|
||||
* invoke any cleanup handlers that were pushed on the thread's stack
|
||||
* before the cancellation occurred, in addition to destructing pthread
|
||||
* before the cancelation occurred, in addition to destructing pthread
|
||||
* keys, before finally, the thread shall abruptly exit.
|
||||
*
|
||||
* By default, pthread_cancel() can only take effect when a thread
|
||||
* reaches a cancellation point. Such functions are documented with
|
||||
* `@cancellationpoint`. They check the cancellation state before the
|
||||
* reaches a cancelation point. Such functions are documented with
|
||||
* `@cancelationpoint`. They check the cancellation state before the
|
||||
* underlying system call is issued. If the system call is issued and
|
||||
* blocks, then pthread_cancel() will interrupt the operation in which
|
||||
* case the syscall wrapper will check the cancelled state a second
|
||||
* time, only if the raw system call returned EINTR.
|
||||
*
|
||||
* The following system calls are implemented as cancellation points.
|
||||
* The following system calls are implemented as cancelation points.
|
||||
*
|
||||
* - `accept4`
|
||||
* - `accept`
|
||||
|
@ -245,7 +222,7 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* - `write`
|
||||
* - `writev`
|
||||
*
|
||||
* The following library calls are implemented as cancellation points.
|
||||
* The following library calls are implemented as cancelation points.
|
||||
*
|
||||
* - `fopen`
|
||||
* - `gzopen`, `gzread`, `gzwrite`, etc.
|
||||
|
@ -269,8 +246,8 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* - `usleep`
|
||||
*
|
||||
* Other userspace libraries provided by Cosmopolitan Libc that call the
|
||||
* cancellation points above will block cancellations while running. The
|
||||
* following are examples of functions that *aren't* cancellation points
|
||||
* cancelation points above will block cancellations while running. The
|
||||
* following are examples of functions that *aren't* cancelation points
|
||||
*
|
||||
* - `INFOF()`, `WARNF()`, etc.
|
||||
* - `getentropy`
|
||||
|
@ -290,14 +267,14 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* - `timespec_sleep`
|
||||
* - `touch`
|
||||
*
|
||||
* The way to block cancellations temporarily is:
|
||||
* The way to block cancelations temporarily is:
|
||||
*
|
||||
* int cs;
|
||||
* pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
||||
* // ...
|
||||
* pthread_setcancelstate(cs, 0);
|
||||
*
|
||||
* In order to support cancellations all your code needs to be rewritten
|
||||
* In order to support cancelations all your code needs to be rewritten
|
||||
* so that when resources such as file descriptors are managed they must
|
||||
* have a cleanup crew pushed to the stack. For example even malloc() is
|
||||
* technically unsafe w.r.t. leaks without doing something like this:
|
||||
|
@ -308,12 +285,12 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* pthread_cleanup_pop(1);
|
||||
*
|
||||
* Consider using Cosmopolitan Libc's garbage collector since it will be
|
||||
* executed when a thread exits due to a cancellation.
|
||||
* executed when a thread exits due to a cancelation.
|
||||
*
|
||||
* void *p = _gc(malloc(123));
|
||||
* read(0, p, 123);
|
||||
*
|
||||
* It's possible to put a thread in asynchronous cancellation mode with
|
||||
* It's possible to put a thread in asynchronous cancelation mode with
|
||||
*
|
||||
* pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
|
||||
* for (;;) donothing;
|
||||
|
@ -321,15 +298,15 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* In which case a thread may be cancelled at any assembly opcode. This
|
||||
* is useful for immediately halting threads that consume cpu and don't
|
||||
* use any system calls. It shouldn't be used on threads that will call
|
||||
* cancellation points since in that case asynchronous mode could cause
|
||||
* cancelation points since in that case asynchronous mode could cause
|
||||
* resource leaks to happen, in such a way that can't be worked around.
|
||||
*
|
||||
* If none of the above options seem savory to you, then a third way is
|
||||
* offered for doing cancellations. Cosmopolitan Libc supports the Musl
|
||||
* offered for doing cancelations. Cosmopolitan Libc supports the Musl
|
||||
* Libc `PTHREAD_CANCEL_MASKED` non-POSIX extension. Any thread may pass
|
||||
* this setting to pthread_setcancelstate(), in which case threads won't
|
||||
* be abruptly destroyed upon cancellation and have their stack unwound;
|
||||
* instead, cancellation points will simply raise an `ECANCELED` error,
|
||||
* be abruptly destroyed upon cancelation and have their stack unwound;
|
||||
* instead, cancelation points will simply raise an `ECANCELED` error,
|
||||
* which can be more safely and intuitively handled for many use cases.
|
||||
* For example:
|
||||
*
|
||||
|
@ -341,8 +318,8 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* pthread_exit(0);
|
||||
* }
|
||||
*
|
||||
* Shows how the masked cancellations paradigm can be safely used. Note
|
||||
* that it's so important that cancellation point error return codes be
|
||||
* Shows how the masked cancelations paradigm can be safely used. Note
|
||||
* that it's so important that cancelation point error return codes be
|
||||
* checked. Code such as the following:
|
||||
*
|
||||
* pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0);
|
||||
|
@ -354,10 +331,10 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
* pthread_exit(0); // XXX: not run if write() was cancelled
|
||||
* }
|
||||
*
|
||||
* Isn't safe to use in masked mode. That's because if a cancellation
|
||||
* occurs during the write() operation then cancellations are blocked
|
||||
* Isn't safe to use in masked mode. That's because if a cancelation
|
||||
* occurs during the write() operation then cancelations are blocked
|
||||
* while running read(). MASKED MODE DOESN'T HAVE SECOND CHANCES. You
|
||||
* must rigorously check the results of each cancellation point call.
|
||||
* must rigorously check the results of each cancelation point call.
|
||||
*
|
||||
* Unit tests should be able to safely ignore the return value, or at
|
||||
* the very least be programmed to consider ESRCH a successful status
|
||||
|
@ -368,30 +345,18 @@ static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
|||
*/
|
||||
errno_t pthread_cancel(pthread_t thread) {
|
||||
errno_t err;
|
||||
struct Dll *e;
|
||||
struct PosixThread *arg, *other;
|
||||
struct PosixThread *arg;
|
||||
if ((arg = (struct PosixThread *)thread)) {
|
||||
err = _pthread_cancel_impl(arg);
|
||||
err = _pthread_cancel_single(arg);
|
||||
} else {
|
||||
err = ESRCH;
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
|
||||
other = POSIXTHREAD_CONTAINER(e);
|
||||
if (other != _pthread_self() &&
|
||||
atomic_load_explicit(&other->status, memory_order_acquire) <
|
||||
kPosixThreadTerminated) {
|
||||
_pthread_cancel_impl(other);
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
err = _pthread_cancel_everyone();
|
||||
}
|
||||
STRACE("pthread_cancel(%d) → %s", _pthread_tid(arg), DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates cancellation point in calling thread.
|
||||
* Creates cancelation point in calling thread.
|
||||
*
|
||||
* This function can be used to force `PTHREAD_CANCEL_DEFERRED` threads
|
||||
* to cancel without needing to invoke an interruptible system call. If
|
||||
|
@ -407,25 +372,25 @@ void pthread_testcancel(void) {
|
|||
if (!(pt = _pthread_self())) return;
|
||||
if (pt->pt_flags & PT_NOCANCEL) return;
|
||||
if ((!(pt->pt_flags & PT_MASKED) || (pt->pt_flags & PT_ASYNC)) &&
|
||||
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
|
||||
atomic_load_explicit(&pt->pt_canceled, memory_order_acquire)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates cancellation point in calling thread.
|
||||
* Creates cancelation point in calling thread.
|
||||
*
|
||||
* This function can be used to force `PTHREAD_CANCEL_DEFERRED` threads
|
||||
* to cancel without needing to invoke an interruptible system call. If
|
||||
* the calling thread is in the `PTHREAD_CANCEL_DISABLE` then this will
|
||||
* do nothing. If the calling thread hasn't yet been cancelled, this'll
|
||||
* do nothing. If the calling thread uses `PTHREAD_CANCEL_MASKED`, then
|
||||
* this function returns `ECANCELED` if a cancellation occurred, rather
|
||||
* this function returns `ECANCELED` if a cancelation occurred, rather
|
||||
* than the normal behavior which is to destroy and cleanup the thread.
|
||||
* Any `ECANCELED` result must not be ignored, because the thread shall
|
||||
* have cancellations disabled once it occurs.
|
||||
* have cancelations disabled once it occurs.
|
||||
*
|
||||
* @return 0 if not cancelled or cancellation is blocked or `ECANCELED`
|
||||
* @return 0 if not cancelled or cancelation is blocked or `ECANCELED`
|
||||
* in masked mode when the calling thread has been cancelled
|
||||
*/
|
||||
errno_t pthread_testcancel_np(void) {
|
||||
|
@ -433,7 +398,7 @@ errno_t pthread_testcancel_np(void) {
|
|||
if (!__tls_enabled) return 0;
|
||||
if (!(pt = _pthread_self())) return 0;
|
||||
if (pt->pt_flags & PT_NOCANCEL) return 0;
|
||||
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return 0;
|
||||
if (!atomic_load_explicit(&pt->pt_canceled, memory_order_acquire)) return 0;
|
||||
if (!(pt->pt_flags & PT_MASKED) || (pt->pt_flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue