Put more thought into i/o polyfills

wait4() is now solid enough to run `make -j100` on Windows. You can now
use MSG_DONTWAIT on Windows. There was a handle leak in accept() that's
been fixed. Our WIN32 overlapped i/o code has been simplified. Priority
class now inherits into subprocesses, so the verynice command will work
and the signal mask will now be inherited by execve() and posix_spawn()
This commit is contained in:
Justine Tunney 2023-11-06 16:38:44 -08:00
parent 736fdb757a
commit e961385e55
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
52 changed files with 679 additions and 487 deletions

View file

@ -16,139 +16,191 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/cosmo.h"
#include "libc/errno.h"
#include "libc/fmt/wintime.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/nt/accounting.h"
#include "libc/nt/process.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/events.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/filetime.h"
#include "libc/nt/struct/processmemorycounters.h"
#include "libc/nt/synchronization.h"
#include "libc/proc/proc.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/w.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
static textwindows struct timespec GetNextDeadline(struct timespec deadline) {
if (timespec_iszero(deadline)) deadline = timespec_real();
struct timespec delay = timespec_frommillis(__SIG_PROC_INTERVAL_MS);
return timespec_add(deadline, delay);
}
static textwindows int ReapZombie(struct Proc *pr, int *wstatus,
struct rusage *opt_out_rusage) {
static textwindows int __proc_reap(struct Proc *pr, int *wstatus,
struct rusage *opt_out_rusage) {
if (wstatus) {
*wstatus = pr->wstatus;
}
if (opt_out_rusage) {
*opt_out_rusage = pr->ru;
}
if (!pr->waiters) {
dll_remove(&__proc.zombies, &pr->elem);
dll_remove(&__proc.zombies, &pr->elem);
if (dll_is_empty(__proc.zombies)) {
ResetEvent(__proc.haszombies);
}
if (pr->waiters) {
pr->status = PROC_UNDEAD;
dll_make_first(&__proc.undead, &pr->elem);
} else {
dll_make_first(&__proc.free, &pr->elem);
CloseHandle(pr->handle);
}
return pr->pid;
}
static textwindows int CheckZombies(int pid, int *wstatus,
static textwindows int __proc_check(int pid, int *wstatus,
struct rusage *opt_out_rusage) {
struct Dll *e;
for (e = dll_first(__proc.zombies); e; e = dll_next(__proc.zombies, e)) {
struct Proc *pr = PROC_CONTAINER(e);
if (pid == -1 && pr->waiters) {
continue; // this zombie has been claimed
}
if (pid == -1 || pid == pr->pid) {
return ReapZombie(pr, wstatus, opt_out_rusage);
return __proc_reap(pr, wstatus, opt_out_rusage);
}
}
return 0;
}
static textwindows void UnwindWaiterCount(void *arg) {
int *waiters = arg;
--*waiters;
}
static textwindows int __proc_wait(int pid, int *wstatus, int options,
struct rusage *rusage, sigset_t waitmask) {
for (;;) {
static textwindows int WaitForProcess(int pid, int *wstatus, int options,
struct rusage *rusage,
uint64_t waitmask) {
uint64_t m;
int rc, *wv;
nsync_cv *cv;
struct Dll *e;
struct Proc *pr;
struct timespec deadline = timespec_zero;
// check list of processes that've already exited
if ((rc = CheckZombies(pid, wstatus, rusage))) {
return rc;
}
// find the mark
pr = 0;
if (pid == -1) {
if (dll_is_empty(__proc.list)) {
return echild();
// check for signals and cancelation
int sig, handler_was_called;
if (_check_cancel() == -1) {
return -1;
}
cv = &__proc.onexit;
wv = &__proc.waiters;
} else {
for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) {
if (pid == PROC_CONTAINER(e)->pid) {
pr = PROC_CONTAINER(e);
if ((sig = __sig_get(waitmask))) {
handler_was_called = __sig_relay(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) {
return -1; // ECANCELED because SIGTHR was just handled
}
if (handler_was_called & SIG_HANDLED_NO_RESTART) {
return eintr(); // a non-SA_RESTART handler was called
}
}
if (pr) {
unassert(!pr->iszombie);
cv = &pr->onexit;
wv = &pr->waiters;
} else {
// check for zombie to harvest
__proc_lock();
CheckForZombies:
int rc = __proc_check(pid, wstatus, rusage);
if (rc || (options & WNOHANG)) {
__proc_unlock();
return rc;
}
// there's no zombies left
// check if there's any living processes
if (dll_is_empty(__proc.list)) {
__proc_unlock();
return echild();
}
}
// wait for status change
if (options & WNOHANG) return 0;
WaitMore:
deadline = GetNextDeadline(deadline);
SpuriousWakeup:
++*wv;
pthread_cleanup_push(UnwindWaiterCount, wv);
m = __sig_begin(waitmask);
if ((rc = _check_signal(true)) != -1) {
rc = nsync_cv_wait_with_deadline(cv, &__proc.lock, deadline, 0);
// get appropriate wait object
// register ourself as waiting
struct Proc *pr = 0;
uintptr_t hWaitObject;
if (pid == -1) {
// wait for any status change
hWaitObject = __proc.haszombies;
++__proc.waiters;
} else {
// wait on specific child
for (struct Dll *e = dll_first(__proc.list); e;
e = dll_next(__proc.list, e)) {
pr = PROC_CONTAINER(e);
if (pid == pr->pid) break;
}
if (pr) {
// by making the waiter count non-zero, the proc daemon stops
// being obligated to monitor this process. this means we may
// need to assume responsibility later on for zombifying this
++pr->waiters;
hWaitObject = pr->handle;
} else {
__proc_unlock();
return echild();
}
}
__proc_unlock();
// perform blocking operation
uint32_t wi;
uintptr_t sem;
struct PosixThread *pt = _pthread_self();
pt->pt_blkmask = waitmask;
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM,
memory_order_release);
wi = WaitForMultipleObjects(2, (intptr_t[2]){hWaitObject, sem}, 0, -1u);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
CloseHandle(sem);
// log warning if handle unexpectedly closed
if (wi & kNtWaitAbandoned) {
wi &= ~kNtWaitAbandoned;
STRACE("wait4 abandoned %u", wi);
}
// check for wait() style wakeup
__proc_lock();
if (!wi && !pr) {
--__proc.waiters;
goto CheckForZombies;
}
// check if killed or win32 error
if (wi) {
if (pr && --pr->waiters && pr->status == PROC_UNDEAD) {
__proc_free(pr);
}
__proc_unlock();
if (wi == 1) {
// __sig_cancel() woke our semaphore
continue;
} else {
// neither posix or win32 define i/o error conditions for
// generic wait. failure should only be due to api misuse
return einval();
}
}
// handle process exit notification
--pr->waiters;
if (pr->status == PROC_ALIVE) {
__proc_harvest(pr, true);
}
switch (pr->status) {
case PROC_ALIVE:
// exit caused by execve() reparenting
__proc_unlock();
break;
case PROC_ZOMBIE:
// exit happened and we're the first to know
rc = __proc_reap(pr, wstatus, rusage);
__proc_unlock();
return rc;
case PROC_UNDEAD:
// exit happened but another thread waited first
if (!pr->waiters) {
__proc_free(pr);
}
__proc_unlock();
return echild();
default:
__builtin_unreachable();
}
}
__sig_finish(m);
pthread_cleanup_pop(true);
if (rc == -1) return -1;
if (rc == ETIMEDOUT) goto WaitMore;
if (rc == ECANCELED) return ecanceled();
if (pr && pr->iszombie) return ReapZombie(pr, wstatus, rusage);
unassert(!rc); // i have to follow my dreams however crazy they seem
if (!pr && (rc = CheckZombies(pid, wstatus, rusage))) return rc;
goto SpuriousWakeup;
}
textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
struct rusage *opt_out_rusage) {
int rc;
uint64_t m;
// no support for WCONTINUED and WUNTRACED yet
if (options & ~WNOHANG) return einval();
// XXX: NT doesn't really have process groups. For instance the
@ -156,12 +208,9 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
// just does an "ignore ctrl-c" internally.
if (pid == 0) pid = -1;
if (pid < -1) pid = -pid;
m = __sig_block();
__proc_lock();
pthread_cleanup_push((void *)__proc_unlock, 0);
rc = WaitForProcess(pid, opt_out_wstatus, options, opt_out_rusage,
m | 1ull << (SIGCHLD - 1));
pthread_cleanup_pop(true);
sigset_t m = __sig_block();
int rc = __proc_wait(pid, opt_out_wstatus, options, opt_out_rusage,
m | 1ull << (SIGCHLD - 1));
__sig_unblock(m);
return rc;
}