Repository navigation
Normalize Channel timeout deadlines (fix lock spin in Channel:pop/push) - #1025
stefanmielke wants to merge 1 commit into
Conversation
lovrChannelPush/lovrChannelPop built the cnd_timedwait deadline without carrying tv_nsec overflow into tv_sec. When start.tv_nsec + fraction * 1e9 reached 1e9, cnd_timedwait failed immediately (EINVAL) while still holding the channel lock, and the loop retried until the clock rolled into the next second, blocking other threads on that channel for up to ~1 second.
|
Was this PR description written with AI? It's hard to follow. Can you explain the bug you were running into more clearly? |
|
yes for both. what I was trying to do was record things that were happening on the game in the background (to not lock the game on any disk writes) using channels. so the reader sleeps until there's something to write to the disk (using channel:pop), while the writer writes when it's needed. when setting the alarm, LOVR adds the wait to the current clock time, split into seconds and nanoseconds, but never carries the overflow from nanoseconds into seconds. The wait then fails instantly, but it keeps the mailbox locked and retries in a tight loop until the clock rolls over to the next second. so while the writer thread spins with the mailbox locked, the game can’t post anything to it, freezing until the next second (when the lock is released). the tests are there to show it happening. |
|
Pushed a fix to stable/dev! |
What
lovrChannelPushandlovrChannelPopbuild thecnd_timedwaitdeadline asstart.tv_nsec + fraction * 1e9without carrying the overflow intotv_sec. When that sum reaches 1e9 the deadline is invalid:cnd_timedwaitreturns an error immediately without releasing the mutex, and the wait loop retries while holdingchannel->lockuntil the wall clock rolls into the next second. Every other thread using that channel blocks for up to ~1 s. Whole-second timeouts are affected too, because the remaining timeout after a wakeup with no message is fractional.This change carries the overflow into
tv_secin both functions.start.tv_nsec < 1e9andfraction * 1e9 < 1e9, so one subtraction is enough.Testing
Linux x86-64 (glibc 2.39), Release builds of
devand of this branch with identical settings. The repro (3 small Lua files, below) has the main thread push N messages, one every 3 ms, while waiter threads pop from the same channel, and times each push.lovr . fraction(1 waiter,pop(0.75), 1000 pushes)lovr . whole 2 100(2 waiters,pop(1), 100 pushes)dev(bf04f88)An
LD_PRELOADwrapper aroundcnd_timedwaitcounted 32,815,094 invalid deadlines (tv_nsec >= 1e9, each returning an error) in a 300-pushfractionrun ondev, and 0 on this branch.lovr test --headlesspasses (65 passes, 0 errors).Repro (3 files; run as
lovr . fractionorlovr . whole 2 100)conf.luamain.luaconsumer.lua