Repository navigation
Conversation
With -scheduler=cores, an interrupt handler that wakes a goroutine calls scheduleTask, which takes schedulerLock. The lock did not mask interrupts, so a handler on core 0 could spin on a lock held by the code it interrupted. Add schedulerSpinLock. Lock disables interrupts and Unlock restores the state saved for the core. schedulerUnlockAndWait keeps interrupts off from the unlock until waiti, so a wake is not lost.
Contributor
|
I tested this on an ESP32-S3-WROOM-1 devkit with
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With
-scheduler=coreson the ESP32-S3, an interrupt handler that wakes a goroutine can hang the core.scheduleTasktakesschedulerLock. On the S3 that lock is a plain spinlock and taking it does not mask interrupts. If the interrupt lands while the code it interrupted on the same core holds the lock, the handler spins on a lock it can never get. The interrupted code cannot run until the handler returns.Peripheral interrupts run on core 0 only, so core 0 is where this happens. A handler that sends on a channel with a parked receiver is enough to trigger it.
This PR adds
schedulerSpinLock.Lockdisables interrupts andUnlockrestores the previous state, saved per core. A handler on the same core now runs afterUnlock, so itsscheduleTaskgets the lock.schedulerUnlockAndWaitneeds one change. It relies on interrupts staying off between the unlock andwaitiso a wake is not lost. It now releases the lock without restoring interrupts and takes it again with the saved state.This is the same approach as #6069 on riscv-qemu. The S3 has two cores, so the deadlock that #6069 fixes on three or more harts does not apply here.
How this was found
I wrote Spin models of the cores scheduler while debugging a hang on riscv-qemu (#6047). The model of the rp2 scheduler had a variant where an interrupt handler wakes a goroutine through
scheduleTask. It deadlocked. I first treated that as hypothetical, because I found no runtime or driver handler that does it. A user callback can, for example a GPIO callback that sends on a channel.I then wrote a reproducer for the S3 and ran it in esp-emu (see #6055). It stalls on the first interrupt.
To check the cause, I added a temporary bounded spin in
scheduleTaskNoWakefor interrupt context. The handler stopped withscheduleTask in an interrupt spun on schedulerLockin 3 of 3 runs. I reverted it. A build where core 0 loops without takingschedulerLockdoes not stall.Testing
All runs are on esp-emu 0.51.0 with
-scheduler=cores -serial=uart, using a compiler built from dev.The reproducer has core 1 raise a software interrupt (
FROM_CPU_2) at varying points. The handler on core 0 sends on a channel to a parked receiver. Core 0 loops onruntime.Gosched(). Core 1 reports the result.schedulerLockt/timers.go, mixed and allocgc stress programsWith #6060 applied, the timers stress program passes with and without this PR. The channel stress program hangs with and without it. That hang is the one #6106 fixes. With #6060 and #6106 both applied, both stress programs pass with and without this PR, and the reproducer still stalls without this PR and passes with it.
Each result is one run per build. The emulator is deterministic.
Reproducer
main.goBuild and run
Not done
schedulerLockis a hardware spinlock that does not mask interrupts. I modelled the fix there and found no new deadlock. I have not tested it on a Pico.