Skip to content

Commit e617dae

Browse files
committed
ipv6: drop spurious revert of merged signal/pthread changes
This branch predated the merges of the tgkill/rt_sigtimedwait (osv_sigtimedwait) and pthread_mutex_timedlock work; its diff vs master had begun to revert them (and delete tst-signal-fills.cc). Restore libc/signal.cc, libc/pthread.cc, linux.cc and tst-signal-fills.cc to master and re-add tst-signal-fills.so to the test list, so this branch touches only the IPv6 forward-port.
1 parent eaa48fb commit e617dae

5 files changed

Lines changed: 231 additions & 17 deletions

File tree

libc/pthread.cc

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -452,19 +452,62 @@ int pthread_mutex_trylock(pthread_mutex_t *m)
452452
return 0;
453453
}
454454

455+
// Try to lock @m, spinning-with-backoff until the absolute deadline given by
456+
// @abs_timeout on clock @clock. OSv's lockfree mutex has no native timed lock,
457+
// so we poll try_lock() with a short sleep between attempts. Returns 0 on
458+
// success, EINVAL for an invalid @abs_timeout, or ETIMEDOUT if the deadline
459+
// passes first.
460+
//
461+
// Note: this is a poll+backoff, not a native timed acquire. That is fine for
462+
// the rare contended timedlock; if it ever needs to be tight, add a timed wait
463+
// to the lockfree mutex itself.
464+
static int mutex_timedlock_until(pthread_mutex_t *m, clockid_t clock,
465+
const struct timespec *abs_timeout)
466+
{
467+
// Validate the timeout up front so an invalid @abs_timeout fails with
468+
// EINVAL without acquiring the mutex, as POSIX requires, even on the
469+
// uncontended fast path.
470+
if (!abs_timeout || abs_timeout->tv_nsec < 0 ||
471+
abs_timeout->tv_nsec >= 1000000000) {
472+
return EINVAL;
473+
}
474+
if (from_libc(m)->try_lock()) {
475+
return 0; // uncontended fast path
476+
}
477+
478+
auto deadline_ns = std::chrono::seconds(abs_timeout->tv_sec) +
479+
std::chrono::nanoseconds(abs_timeout->tv_nsec);
480+
auto now = [clock]() -> std::chrono::nanoseconds {
481+
if (clock == CLOCK_MONOTONIC) {
482+
return osv::clock::uptime::now().time_since_epoch();
483+
}
484+
return osv::clock::wall::now().time_since_epoch();
485+
};
486+
487+
while (now() < deadline_ns) {
488+
if (from_libc(m)->try_lock()) {
489+
return 0;
490+
}
491+
sched::thread::sleep(std::chrono::microseconds(50));
492+
}
493+
// One last attempt right at the deadline.
494+
return from_libc(m)->try_lock() ? 0 : ETIMEDOUT;
495+
}
496+
455497
int pthread_mutex_timedlock(pthread_mutex_t *m,
456498
const struct timespec *abs_timeout)
457499
{
458-
WARN_STUBBED();
459-
return EINVAL;
500+
return mutex_timedlock_until(m, CLOCK_REALTIME, abs_timeout);
460501
}
461502

462503
int pthread_mutex_clocklock(pthread_mutex_t *m,
463504
clockid_t clock,
464505
const struct timespec *abs_timeout)
465506
{
466-
WARN_STUBBED();
467-
return EINVAL;
507+
if (clock != CLOCK_REALTIME && clock != CLOCK_MONOTONIC) {
508+
return EINVAL;
509+
}
510+
return mutex_timedlock_until(m, clock, abs_timeout);
468511
}
469512

470513
int pthread_mutex_unlock(pthread_mutex_t *m)

libc/signal.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,54 @@ int sigwait(const sigset_t *set, int *sig)
323323
return 0;
324324
}
325325

326+
// Timed variant used by rt_sigtimedwait(2). Waits for one of the signals in
327+
// @set to be delivered, or for the relative @timeout to elapse. A {0,0}
328+
// timeout polls (returns immediately with EAGAIN if nothing matching is
329+
// pending). Returns the signal number, or -1 with errno set (EAGAIN on
330+
// timeout, EINVAL on a malformed timeout). Called from linux.cc; kept here
331+
// because it needs the signal-waiter internals.
332+
extern "C" OSV_LIBC_API
333+
int osv_sigtimedwait(const sigset_t *set, siginfo_t *si,
334+
const struct timespec *timeout)
335+
{
336+
// Validate the timeout as Linux does: a malformed timespec must fail with
337+
// EINVAL rather than arm a bogus (or negative) timer.
338+
if (!timeout || timeout->tv_sec < 0 ||
339+
timeout->tv_nsec < 0 || timeout->tv_nsec >= 1000000000) {
340+
errno = EINVAL;
341+
return -1;
342+
}
343+
sched::timer tmr(*sched::thread::current());
344+
tmr.set(osv::clock::uptime::now() +
345+
std::chrono::seconds(timeout->tv_sec) +
346+
std::chrono::nanoseconds(timeout->tv_nsec));
347+
348+
// Only accept a delivered signal that is a member of @set (rt_sigtimedwait
349+
// waits for a specific set); a signal outside the set is not what the
350+
// caller asked for, so keep waiting for it (or the timeout).
351+
int signo = 0;
352+
sched::thread::wait_until([&] {
353+
int pending = thread_pending_signal;
354+
if (pending != 0 && (!set || sigismember(set, pending))) {
355+
signo = pending;
356+
return true;
357+
}
358+
return tmr.expired();
359+
});
360+
361+
if (signo == 0) {
362+
// Timed out (or polled with {0,0}) with no matching signal.
363+
errno = EAGAIN;
364+
return -1;
365+
}
366+
thread_pending_signal = 0;
367+
if (si) {
368+
memset(si, 0, sizeof(*si));
369+
si->si_signo = signo;
370+
}
371+
return signo;
372+
}
373+
326374
// Partially-Linux-compatible support for kill(2).
327375
// Note that this is different from our generate_signal() - the latter is only
328376
// suitable for delivering SIGFPE and SIGSEGV to the same thread that called

linux.cc

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#include <osv/syscalls_config.h>
6868

6969
extern "C" int eventfd2(unsigned int, int);
70+
extern "C" int osv_sigtimedwait(const sigset_t *, siginfo_t *, const struct timespec *);
7071

7172
extern "C" OSV_LIBC_API long gettid()
7273
{
@@ -395,12 +396,15 @@ int rt_sigprocmask(int how, sigset_t * nset, sigset_t * oset, size_t sigsetsize)
395396

396397
int rt_sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout, size_t sigsetsize)
397398
{
398-
if (!timeout || (!timeout->tv_sec && !timeout->tv_nsec)) {
399+
if (!timeout) {
400+
// No timeout: block indefinitely for a matching signal.
399401
return sigwaitinfo(set, info);
400-
} else {
401-
errno = ENOSYS;
402-
return -1;
403402
}
403+
// A timeout of {0,0} is a non-blocking poll (return EAGAIN if nothing is
404+
// already pending); a positive timeout blocks up to that long. Both are
405+
// handled by osv_sigtimedwait, which returns immediately when the deadline
406+
// is already in the past.
407+
return osv_sigtimedwait(set, info, timeout);
404408
}
405409

406410
#if CONF_syscall_sys_exit
@@ -656,17 +660,39 @@ static int pselect6(int nfds, fd_set *readfds, fd_set *writefds,
656660
#if CONF_syscall_tgkill
657661
static int tgkill(int tgid, int tid, int sig)
658662
{
663+
// On Linux tid<=0 and tgid<=0 are malformed arguments -> EINVAL (ESRCH is
664+
// reserved for well-formed IDs that simply do not exist).
665+
if (tid <= 0 || tgid <= 0) {
666+
errno = EINVAL;
667+
return -1;
668+
}
669+
// OSv is a single process, so the only valid thread-group id is our own.
670+
if (tgid != getpid()) {
671+
errno = ESRCH;
672+
return -1;
673+
}
674+
// The self case is the common one (raise()/abort()); deliver directly.
675+
if (tid == gettid()) {
676+
return kill(getpid(), sig);
677+
}
678+
// Targeting another thread: verify the thread exists, then deliver via the
679+
// process-wide signal path. OSv delivers signals process-wide rather than
680+
// to one specific thread, so the signal is handled by the process (a waiter
681+
// or a fresh handler thread) rather than strictly by `tid`; this is the
682+
// same approximation kill() already makes, and is far better than the
683+
// previous ENOSYS for a valid live tid.
659684
//
660-
// Given OSv supports sigle process only, we only support this syscall
661-
// when thread group id is self (getpid()) or -1 (see https://linux.die.net/man/2/tgkill)
662-
// AND tid points to the current thread (caller)
663-
// Ideally we would want to delegate to pthread_kill() but there is no
664-
// easy way to map tgid to pthread_t so we directly delegate to kill().
665-
if ((tgid == -1 || tgid == getpid()) && (tid == gettid())) {
666-
return kill(tgid, sig);
685+
// Use with_thread_by_id() rather than find_by_id(): it holds the thread
686+
// map lock for the duration, so the thread cannot be freed out from under
687+
// the existence check (find_by_id returns a pointer with no such guarantee).
688+
bool exists = false;
689+
sched::with_thread_by_id((unsigned)tid, [&exists] (sched::thread *t) {
690+
exists = (t != nullptr);
691+
});
692+
if (exists) {
693+
return kill(getpid(), sig);
667694
}
668-
669-
errno = ENOSYS;
695+
errno = ESRCH;
670696
return -1;
671697
}
672698
#endif

modules/tests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-bsd-evh.so \
128128
tst-fpu.so tst-preempt.so tst-tracepoint.so tst-hub.so \
129129
misc-console.so misc-leak.so misc-readbench.so misc-mmap-anon-perf.so \
130130
tst-mmap-file.so misc-mmap-big-file.so tst-mmap.so tst-huge.so \
131+
tst-signal-fills.so \
131132
tst-prctl.so \
132133
tst-mmap-file-cow.so \
133134
tst-elf-permissions.so misc-mutex.so misc-sockets.so tst-condvar.so \

tests/tst-signal-fills.cc

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (C) 2026 Greg Burd
3+
*
4+
* This work is open source software, licensed under the terms of the
5+
* BSD license as described in the LICENSE file in the top-level directory.
6+
*/
7+
8+
// Exercises the relaxed tgkill(2) and the rt_sigtimedwait(2) timeout path.
9+
// Built and run on the OSv test image.
10+
11+
#include <sys/syscall.h>
12+
#include <signal.h>
13+
#include <unistd.h>
14+
#include <time.h>
15+
#include <errno.h>
16+
#include <pthread.h>
17+
18+
#include <cassert>
19+
#include <atomic>
20+
#include <iostream>
21+
22+
static std::atomic<int> g_caught{0};
23+
static void handler(int) { g_caught.fetch_add(1, std::memory_order_relaxed); }
24+
25+
static long gettid_() { return syscall(SYS_gettid); }
26+
27+
int main()
28+
{
29+
std::cerr << "Running signal-fills tests\n";
30+
31+
// ---- tgkill ----
32+
struct sigaction sa {};
33+
sa.sa_handler = handler;
34+
sigaction(SIGUSR1, &sa, nullptr);
35+
36+
// tgkill to our own tgid/tid delivers the signal.
37+
g_caught = 0;
38+
assert(syscall(SYS_tgkill, getpid(), gettid_(), SIGUSR1) == 0);
39+
for (int i = 0; i < 100 && g_caught == 0; i++) usleep(1000);
40+
assert(g_caught >= 1);
41+
42+
// tgkill with a wrong tgid -> ESRCH.
43+
errno = 0;
44+
assert(syscall(SYS_tgkill, getpid() + 12345, gettid_(), SIGUSR1) == -1 && errno == ESRCH);
45+
46+
// tgkill to a non-existent tid -> ESRCH (not a crash / not silently OK).
47+
errno = 0;
48+
assert(syscall(SYS_tgkill, getpid(), 0x7fffff, SIGUSR1) == -1 && errno == ESRCH);
49+
50+
// tgkill targeting another *live* thread is accepted and the signal is
51+
// delivered process-wide (OSv's delivery model).
52+
pthread_t th;
53+
struct thread_args {
54+
std::atomic<long> tid{0};
55+
std::atomic<bool> stop{false};
56+
} targs;
57+
// targs lives on this stack frame until pthread_join() below, so the child
58+
// can safely reference it; no heap allocation to leak.
59+
int rc = pthread_create(&th, nullptr, [](void *p) -> void * {
60+
auto *a = static_cast<thread_args *>(p);
61+
a->tid.store(syscall(SYS_gettid));
62+
while (!a->stop.load()) usleep(1000);
63+
return nullptr;
64+
}, &targs);
65+
assert(rc == 0);
66+
while (targs.tid == 0) usleep(1000);
67+
g_caught = 0;
68+
assert(syscall(SYS_tgkill, getpid(), targs.tid.load(), SIGUSR1) == 0);
69+
for (int i = 0; i < 100 && g_caught == 0; i++) usleep(1000);
70+
assert(g_caught >= 1);
71+
targs.stop = true;
72+
pthread_join(th, nullptr);
73+
74+
// ---- rt_sigtimedwait timeout path ----
75+
// Block SIGUSR2, then rt_sigtimedwait with a short timeout and no pending
76+
// signal -> times out with EAGAIN (previously returned ENOSYS).
77+
sigset_t set;
78+
sigemptyset(&set);
79+
sigaddset(&set, SIGUSR2);
80+
sigprocmask(SIG_BLOCK, &set, nullptr);
81+
82+
struct timespec ts { 0, 150 * 1000 * 1000 }; // 150 ms
83+
siginfo_t si;
84+
struct timespec before, after;
85+
clock_gettime(CLOCK_MONOTONIC, &before);
86+
errno = 0;
87+
int r = syscall(SYS_rt_sigtimedwait, &set, &si, &ts, sizeof(sigset_t));
88+
clock_gettime(CLOCK_MONOTONIC, &after);
89+
assert(r == -1 && errno == EAGAIN);
90+
long long ms = (after.tv_sec - before.tv_sec) * 1000 +
91+
(after.tv_nsec - before.tv_nsec) / 1000000;
92+
assert(ms >= 130); // actually waited ~150 ms, did not return ENOSYS instantly
93+
94+
std::cerr << "signal-fills tests PASSED\n";
95+
return 0;
96+
}

0 commit comments

Comments
 (0)