Skip to content

Commit

Permalink
Implement support for POSIX thread cancellations
Browse files Browse the repository at this point in the history
This change makes some miracle modifications to the System Five system
call support, which lets us have safe, correct, and atomic handling of
thread cancellations. It all turned out to be cheaper than anticipated
because it wasn't necessary to modify the system call veneers. We were
able to encode the cancellability of each system call into the magnums
found in libc/sysv/syscalls.sh. Since cancellations are so waq, we are
also supporting a lovely Musl Libc mask feature for raising ECANCELED.
  • Loading branch information
jart committed Nov 4, 2022
1 parent 37d40e0 commit 2278327
Show file tree
Hide file tree
Showing 145 changed files with 715 additions and 265 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

SHELL = build/bootstrap/cocmd.com
HOSTS ?= freebsd openbsd netbsd rhel7 rhel5 xnu win10 win10:31336
MAKEFLAGS += -j --no-builtin-rules
MAKEFLAGS += --no-builtin-rules

.SUFFIXES:
.DELETE_ON_ERROR:
Expand Down
1 change: 1 addition & 0 deletions libc/calls/__sig2.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ textwindows int __sig_raise(int sig, int si_code) {
if (1 <= sig && sig <= 64) {
if (!__sig_is_masked(sig)) {
++__sig_count;
// TODO(jart): ucontext_t support
__sig_handle(false, sig, si_code, 0);
return 0;
} else {
Expand Down
4 changes: 2 additions & 2 deletions libc/calls/clock_nanosleep-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ textwindows int sys_clock_nanosleep_nt(int clock, int flags,
for (;;) {
if (sys_clock_gettime_nt(clock, &now)) return -1;
if (_timespec_gte(now, abs)) return 0;
if (_check_interrupts(false, g_fds.p)) return eintr();
if (_check_interrupts(false, g_fds.p)) return -1;
SleepEx(MIN(__SIG_POLLING_INTERVAL_MS,
_timespec_tomillis(_timespec_sub(abs, now))),
false);
Expand All @@ -47,7 +47,7 @@ textwindows int sys_clock_nanosleep_nt(int clock, int flags,
if (_timespec_gte(now, abs)) return 0;
if (_check_interrupts(false, g_fds.p)) {
if (rem) *rem = _timespec_sub(abs, now);
return eintr();
return -1;
}
SleepEx(MIN(__SIG_POLLING_INTERVAL_MS,
_timespec_tomillis(_timespec_sub(abs, now))),
Expand Down
1 change: 1 addition & 0 deletions libc/calls/clock_nanosleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
* @raise EINVAL if `flags` has an unrecognized value
* @raise EINVAL if `req->tv_nsec ∉ [0,1000000000)`
* @raise ENOSYS on bare metal
* @cancellationpoint
* @returnserrno
* @norestart
*/
Expand Down
6 changes: 3 additions & 3 deletions libc/calls/fcntl-sysv.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"

int sys_fcntl(int fd, int cmd, uintptr_t arg) {
int sys_fcntl(int fd, int cmd, uintptr_t arg, int impl(int, int, ...)) {
int e, rc;
bool islock;
if ((islock = cmd == F_GETLK || //
Expand All @@ -40,12 +40,12 @@ int sys_fcntl(int fd, int cmd, uintptr_t arg) {
cosmo2flock(arg);
}
e = errno;
rc = __sys_fcntl(fd, cmd, arg);
rc = impl(fd, cmd, arg);
if (islock) {
flock2cosmo(arg);
} else if (rc == -1 && cmd == F_DUPFD_CLOEXEC && errno == EINVAL) {
errno = e;
rc = __fixupnewfd(__sys_fcntl(fd, F_DUPFD, arg), O_CLOEXEC);
rc = __fixupnewfd(impl(fd, F_DUPFD, arg), O_CLOEXEC);
}
return rc;
}
7 changes: 6 additions & 1 deletion libc/calls/fcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
* @raise EDEADLK if `cmd` was `F_SETLKW` and waiting would deadlock
* @raise EMFILE if `cmd` is `F_DUPFD` or `F_DUPFD_CLOEXEC` and
* `RLIMIT_NOFILE` would be exceeded
* @cancellationpoint when `cmd` is `F_SETLKW`
* @asyncsignalsafe
* @restartable
*/
Expand All @@ -111,7 +112,11 @@ int fcntl(int fd, int cmd, ...) {
if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) {
rc = _weaken(__zipos_fcntl)(fd, cmd, arg);
} else if (!IsWindows()) {
rc = sys_fcntl(fd, cmd, arg);
if (cmd == F_SETLKW) {
rc = sys_fcntl(fd, cmd, arg, __sys_fcntl_cp);
} else {
rc = sys_fcntl(fd, cmd, arg, __sys_fcntl);
}
} else {
rc = sys_fcntl_nt(fd, cmd, arg);
}
Expand Down
1 change: 1 addition & 0 deletions libc/calls/fdatasync.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* @return 0 on success, or -1 w/ errno
* @see sync(), fsync(), sync_file_range()
* @see __nosync to secretly disable
* @cancellationpoint
* @asyncsignalsafe
*/
int fdatasync(int fd) {
Expand Down
3 changes: 2 additions & 1 deletion libc/calls/flock.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"

/**
* Acquires lock on file.
Expand All @@ -30,6 +30,7 @@
* @param op can have LOCK_{SH,EX,NB,UN} for shared, exclusive,
* non-blocking, and unlocking
* @return 0 on success, or -1 w/ errno
* @cancellationpoint
* @restartable
*/
int flock(int fd, int op) {
Expand Down
3 changes: 2 additions & 1 deletion libc/calls/fstatfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/struct/statfs-meta.internal.h"
#include "libc/calls/struct/statfs.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/runtime/stack.h"
#include "libc/sysv/errfuns.h"

/**
* Returns information about filesystem.
* @return 0 on success, or -1 w/ errno
* @cancellationpoint
*/
int fstatfs(int fd, struct statfs *sf) {
int rc;
Expand Down
1 change: 1 addition & 0 deletions libc/calls/fsync.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* @return 0 on success, or -1 w/ errno
* @see fdatasync(), sync_file_range()
* @see __nosync to secretly disable
* @cancellationpoint
* @asyncsignalsafe
*/
int fsync(int fd) {
Expand Down
2 changes: 1 addition & 1 deletion libc/calls/ftruncate.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
* @raise EINVAL if `fd` is a non-file, e.g. pipe, socket
* @raise EINVAL if `fd` wasn't opened in a writeable mode
* @raise ENOSYS on bare metal
* @see truncate()
* @cancellationpoint
* @asyncsignalsafe
* @threadsafe
*/
Expand Down
2 changes: 1 addition & 1 deletion libc/calls/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ forceinline size_t _clampio(size_t size) {
}

int sys_close_nt(struct Fd *, int) hidden;
bool _check_interrupts(bool, struct Fd *) hidden;
int _check_interrupts(bool, struct Fd *) hidden;
int sys_openat_metal(int, const char *, int, unsigned);

COSMOPOLITAN_C_END_
Expand Down
17 changes: 13 additions & 4 deletions libc/calls/interrupts-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,33 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/lockcmpxchgp.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"

textwindows bool _check_interrupts(bool restartable, struct Fd *fd) {
bool res;
textwindows int _check_interrupts(bool restartable, struct Fd *fd) {
int rc;
if (_weaken(pthread_testcancel_np) &&
(rc = _weaken(pthread_testcancel_np)())) {
errno = rc;
return -1;
}
if (_weaken(_check_sigalrm)) _weaken(_check_sigalrm)();
if (!__tls_enabled || !(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) {
if (_weaken(_check_sigchld)) _weaken(_check_sigchld)();
if (fd && _weaken(_check_sigwinch)) _weaken(_check_sigwinch)(fd);
}
res = _weaken(__sig_check) && _weaken(__sig_check)(restartable);
return res;
if (_weaken(__sig_check) && _weaken(__sig_check)(restartable)) return eintr();
return 0;
}
1 change: 1 addition & 0 deletions libc/calls/nanosleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* @raise EFAULT if `req` is NULL or `req` / `rem` is a bad pointer
* @raise ENOSYS on bare metal
* @see clock_nanosleep()
* @cancellationpoint
* @norestart
*/
int nanosleep(const struct timespec *req, struct timespec *rem) {
Expand Down
2 changes: 2 additions & 0 deletions libc/calls/ntcontext2linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "libc/nt/struct/context.h"
#include "libc/str/str.h"

// TODO(jart): uc_sigmask support

privileged void _ntcontext2linux(ucontext_t *ctx, const struct NtContext *cr) {
if (!cr) return;
ctx->uc_mcontext.eflags = cr->EFlags;
Expand Down
1 change: 1 addition & 0 deletions libc/calls/open.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* @param file specifies filesystem path to open
* @return file descriptor, or -1 w/ errno
* @see openat() for further documentation
* @cancellationpoint
* @asyncsignalsafe
* @restartable
* @threadsafe
Expand Down
1 change: 1 addition & 0 deletions libc/calls/openat.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
* @raise ELOOP if `flags` had `O_NOFOLLOW` and `file` is a symbolic link
* @raise ELOOP if a loop was detected resolving components of `file`
* @raise EISDIR if writing is requested and `file` names a directory
* @cancellationpoint
* @asyncsignalsafe
* @restartable
* @threadsafe
Expand Down
5 changes: 3 additions & 2 deletions libc/calls/pause-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/errors.h"
#include "libc/nt/synchronization.h"
#include "libc/sysv/errfuns.h"
Expand All @@ -31,7 +32,7 @@ textwindows int sys_pause_nt(void) {
for (;;) {

if (_check_interrupts(false, g_fds.p)) {
return eintr();
return -1;
}

if (SleepEx(__SIG_POLLING_INTERVAL_MS, true) == kNtWaitIoCompletion) {
Expand Down
3 changes: 2 additions & 1 deletion libc/calls/pause.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/calls.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sock/internal.h"

/**
Expand All @@ -37,6 +37,7 @@
* However this has a tinier footprint and better logging.
*
* @return -1 w/ errno set to EINTR
* @cancellationpoint
* @see sigsuspend()
* @norestart
*/
Expand Down
7 changes: 2 additions & 5 deletions libc/calls/poll-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/errno.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
Expand Down Expand Up @@ -64,8 +63,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
if (sigmask) {
__sig_mask(SIG_SETMASK, sigmask, &oldmask);
}
if (_check_interrupts(false, g_fds.p)) {
rc = eintr();
if ((rc = _check_interrupts(false, g_fds.p))) {
goto ReturnPath;
}

Expand Down Expand Up @@ -190,8 +188,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
}
// otherwise loop limitlessly for timeout to elapse while
// checking for signal delivery interrupts, along the way
if (_check_interrupts(false, g_fds.p)) {
rc = eintr();
if ((rc = _check_interrupts(false, g_fds.p))) {
goto ReturnPath;
}
}
Expand Down
1 change: 1 addition & 0 deletions libc/calls/poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
* @return fds[𝑖].revents is always zero initializaed and then will
* be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something
* was determined about the file descriptor
* @cancellationpoint
* @asyncsignalsafe
* @threadsafe
* @norestart
Expand Down
1 change: 1 addition & 0 deletions libc/calls/ppoll.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
*
* @param timeout if null will block indefinitely
* @param sigmask may be null in which case no mask change happens
* @cancellationpoint
* @asyncsignalsafe
* @threadsafe
* @norestart
Expand Down
1 change: 1 addition & 0 deletions libc/calls/pread.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
* @raise EIO if a complicated i/o error happened
* @raise EINTR if signal was delivered instead
* @see pwrite(), write()
* @cancellationpoint
* @asyncsignalsafe
* @threadsafe
* @vforksafe
Expand Down
1 change: 1 addition & 0 deletions libc/calls/preadv.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ static ssize_t Preadv(int fd, struct iovec *iov, int iovlen, int64_t off) {
* Reads with maximum generality.
*
* @return number of bytes actually read, or -1 w/ errno
* @cancellationpoint
* @asyncsignalsafe
* @vforksafe
*/
Expand Down
1 change: 1 addition & 0 deletions libc/calls/pwrite.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
* @return [1..size] bytes on success, or -1 w/ errno; noting zero is
* impossible unless size was passed as zero to do an error check
* @see pread(), write()
* @cancellationpoint
* @asyncsignalsafe
* @threadsafe
* @vforksafe
Expand Down
1 change: 1 addition & 0 deletions libc/calls/pwritev.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ static ssize_t Pwritev(int fd, const struct iovec *iov, int iovlen,
* call using pwrite().
*
* @return number of bytes actually sent, or -1 w/ errno
* @cancellationpoint
* @asyncsignalsafe
* @vforksafe
*/
Expand Down
4 changes: 2 additions & 2 deletions libc/calls/read-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
}
if (_check_interrupts(true, g_fds.p)) {
POLLTRACE("sys_read_nt interrupted");
return eintr();
return -1;
}
}
POLLTRACE("sys_read_nt ready to read");
Expand Down Expand Up @@ -94,7 +94,7 @@ textwindows ssize_t sys_read_nt(struct Fd *fd, const struct iovec *iov,
uint32_t size;
size_t i, total;
if (opt_offset < -1) return einval();
if (_check_interrupts(true, fd)) return eintr();
if (_check_interrupts(true, fd)) return -1;
while (iovlen && !iov[0].iov_len) iov++, iovlen--;
if (iovlen) {
for (total = i = 0; i < iovlen; ++i) {
Expand Down
1 change: 1 addition & 0 deletions libc/calls/read.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
* or `SO_RCVTIMEO` is in play and the time interval elapsed
* @raise ENOBUFS is specified by POSIX
* @raise ENXIO is specified by POSIX
* @cancellationpoint
* @asyncsignalsafe
* @restartable
* @vforksafe
Expand Down
1 change: 1 addition & 0 deletions libc/calls/readv.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* performance boost in the case of a single small iovec.
*
* @return number of bytes actually read, or -1 w/ errno
* @cancellationpoint
* @restartable
*/
ssize_t readv(int fd, const struct iovec *iov, int iovlen) {
Expand Down
9 changes: 2 additions & 7 deletions libc/calls/sigblockall.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,10 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/struct/sigset.h"
#include "libc/str/str.h"

/**
* Blocks all signals without strace logging.
*
* @param neu is new signal mask for process
* @return old signal mask
*/
sigset_t _sigblockall(void) {
sigset_t ss;
sigfillset(&ss);
memset(&ss, -1, sizeof(ss));
return _sigsetmask(ss);
}
Loading

0 comments on commit 2278327

Please sign in to comment.