runtime/cores: add secondary core startup barrier - #5737
deadprogram merged 3 commits into
Conversation
a408a14 to
fd2894f
Compare
|
Thanks for the fix and the clear reproducers @rdon-key! The following is edited from an automated review:
|
|
Thank you for the review. Item 1: Items 2 and 3: |
f34afc1 to
ba5ee6d
Compare
|
This PR has been rebased, and |
deadprogram
left a comment
There was a problem hiding this comment.
Thanks for the improvement @rdon-key and for handling all the feedback items. Now squash/merging.
This fixes a startup race with
scheduler=coresthat was identified during the review of the RP2040 XIP / flash-safe changes in #5411.startSecondaryCores()can return after a secondary core/hart has started, but beforesecondaryCoresStartedis set. During that window, the secondary core can enter the scheduler and run Go code while the runtime still considers the system single-core.This is especially problematic for the GC. If GC starts on a secondary core while
secondaryCoresStartedis still false, the single-core path can use the secondary core's system stack pointer together with the primary core'sstackTop, resulting in an invalid stack range.This change adds a startup barrier so secondary cores cannot enter the scheduler until the primary core has completed
startSecondaryCores()and marked the multicore runtime as ready.Changes
secondaryCoresStartedtoatomic.Uint32startSecondaryCores()returnswaitForSecondaryCoresReady()The intended invariant is:
Reproducer
RP2040 / Pico
The race can be reproduced naturally, without calling runtime internals, by queueing a goroutine during
init()and checking whether core 1 runs it beforesecondaryCoresStartedbecomes true. A repeating timer alarm interrupt busy-waits in its handler until then, widening the race window.Note: the reproducer reads the flag through
//go:externas aboolwith a single-byte volatile load. Since the target is little-endian, this still works after the variable becomesatomic.Uint32, so the same reproducer runs unchanged on bothdevand this change.Details
On
dev:With this change:
RISC-V QEMU
This reproducer is intentionally artificial: it calls
startSecondaryCores()directly frominit()via//go:linknameand exits before the runtime would setsecondaryCoresStarted. It only checks whether a secondary hart can reach the scheduler before the flag is set. With this change, the hart stays blocked at the barrier for the whole test.Details
On
dev:With this change:
Testing
Tested with
-scheduler=cores.dev. The reason is unclear, but RP2350 shares the same RP2 startup path as RP2040, so the same race window exists. With this change, the worker observedsecondaryCoresStarted=true.