Implement FUTEX_WAIT_BITSET and FUTEX_WAKE_BITSET - #2775
Conversation
glibc's pthread_cond_timedwait and pthread_barrier_wait issue
FUTEX_WAIT_BITSET with FUTEX_CLOCK_REALTIME (op 265). sys_futex only
stripped FUTEX_PRIVATE_FLAG before its switch, so the clock bit stayed
on, nothing matched, and the call returned ENOSYS. glibc turns that into
The futex facility returned an unexpected error code.
and aborts, which takes out any program using a barrier or an absolute
condition-variable timeout.
Three parts:
* FUTEX_CLOCK_REALTIME joins FUTEX_PRIVATE_FLAG in FUTEX_CMD_MASK_. It
is a modifier on the command, not part of it.
* FUTEX_WAIT_BITSET/FUTEX_WAKE_BITSET carry a mask. A waiter records
the mask it waited with and only wakes if it overlaps the waker's.
Plain FUTEX_WAIT/FUTEX_WAKE pass FUTEX_BITSET_MATCH_ANY, so their
behaviour is unchanged. A zero mask is EINVAL, as on Linux.
* FUTEX_WAIT's timeout is relative but FUTEX_WAIT_BITSET's is absolute,
against CLOCK_REALTIME when the flag is set and CLOCK_MONOTONIC
otherwise, so it is converted before being handed to wait_for. An
already-expired deadline becomes a zero timeout rather than a
negative one.
Verified against real Linux with the same static i386 binary:
before after / Linux
pthread_barrier_wait aborts (3/3) completes
pthread_cond_timedwait, times out aborts ETIMEDOUT at
0.304s (Linux
0.303s)
pthread_cond_timedwait, signalled aborts wakes on the
signal, not the
deadline
plain pthread_cond_wait works works
The last row is the regression check on the untouched FUTEX_WAIT path.
Also re-ran the pty, sigurg, fstatat and threaded COW tests I had to hand
plus an Alpine shell smoke: unchanged.
|
This not a review but just FYI that we primary aim for Alpine compatibility. Nothing wrong with sending in glibc support and we are happy to merge it once it's been looked at but we will probably do this with reduced priority |
|
That is a fair call, and I checked rather than assumed — musl does not need any of this. From musl's sources:
and if (__clock_gettime(clk, &to)) return EINVAL;
to.tv_sec = at->tv_sec - to.tv_sec;
if ((to.tv_nsec = at->tv_nsec - to.tv_nsec) < 0) { ... }So musl uses only It is still a real wall for glibc guests, which is how I ran into it — a glibc binary using a barrier or an absolute condvar timeout aborts outright rather than degrading. Reduced priority is completely reasonable; leaving it here in case it is useful later. (One incidental thing from reading that code, no action implied: musl prefers |
Any program that uses a pthread barrier or an absolute condition-variable timeout dies on master with:
glibc's
pthread_cond_timedwaitandpthread_barrier_waitissueFUTEX_WAIT_BITSET | FUTEX_CLOCK_REALTIME, which is op 265.sys_futexonly stripsFUTEX_PRIVATE_FLAGbefore its switch, so the clock bit survives, nothing matches, and the call falls through toENOSYS— which glibc treats as fatal and aborts on.I found this while trying to write a threaded stress test against master for an unrelated PR, and it turned out to be blocking any threaded test at all.
The change
Three parts, all in
kernel/futex.c:FUTEX_CLOCK_REALTIMEjoinsFUTEX_CMD_MASK_. LikeFUTEX_PRIVATE_FLAGit is a modifier on the command, not part of it.FUTEX_WAIT_BITSET/FUTEX_WAKE_BITSETcarry a mask. A waiter records the mask it waited with and only wakes if it overlaps the waker's. PlainFUTEX_WAIT/FUTEX_WAKEpassFUTEX_BITSET_MATCH_ANY, so their behaviour is unchanged. A zero mask isEINVAL, as on Linux.FUTEX_WAIT's timeout is relative;FUTEX_WAIT_BITSET's is absolute, againstCLOCK_REALTIMEwhen the flag is set andCLOCK_MONOTONICotherwise. It is converted before being handed towait_for, and an already-expired deadline becomes a zero timeout rather than a negative one.Testing
Same static i386 binary on real Linux (x86 Ubuntu) and on iSH:
pthread_barrier_waitpthread_cond_timedwaitthat times outETIMEDOUTafter 0.304sETIMEDOUTafter 0.303spthread_cond_timedwaitthat gets signalledpthread_cond_waitThe last row is the regression check on the untouched
FUTEX_WAITpath, and the timing rows check the absolute→relative conversion in both directions — a sign error there would show up as an immediate return or a hang rather than a wrong answer.Also re-ran the pty, SIGURG,
fstatatand threaded COW-fault tests I had to hand, plus an Alpine shell smoke with background jobs: unchanged.Not included
The other unimplemented ops (
FUTEX_CMP_REQUEUE,FUTEX_WAKE_OP, the PI family) are left alone. They are separate work and nothing I ran needed them; this is deliberately scoped to the one that makes glibc abort.🤖 Generated with Claude Code