Skip to content

runtime: yield the thread in Gosched on the threads scheduler - #5764

Merged
deadprogram merged 1 commit into
tinygo-org:devfrom
davecheney:davecheney-threads-gosched-yield
Sep 29, 2026
Merged

deadprogram merged 1 commit into
tinygo-org:devfrom
davecheney:davecheney-threads-gosched-yield

Conversation

@davecheney

@davecheney davecheney commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Gosched was a no-op on the threads scheduler. Add task.Yield, which calls sched_yield, and use it from Gosched to yield the current thread to the OS scheduler. Each goroutine has its own OS thread. The call lives in internal/task next to the other thread hooks, the same way the cooperative and cores schedulers call task.Pause.

This differs from upstream Go's Gosched, which hands off to the Go scheduler through gosched_m. Upstream uses osyield in spin loops, including locking and stop-the-world code, not to implement Gosched.

This reduces an iter test flake on Linux. stableNumGoroutine in iter/pull_test.go uses GOMAXPROCS(1) and Gosched to let the previous subtest's tRunner goroutine finish exiting. TinyGo ignores GOMAXPROCS, and with a no-op Gosched that goroutine could still be counted, making the baseline one too high. sched_yield is only a scheduling hint. It does not enforce GOMAXPROCS(1) or guarantee that the goroutine has exited, so the race can still occur.

The original Linux stress test under full CPU load (8 busy loops on 8 cores) saw TestPull and TestPull2 fail 7 of 300 runs before this change and 0 of 300 after. This is evidence of improvement, not a guarantee that the flake is eliminated.

signal_waitUntilIdle in runtime_unix.go also yields on each polling pass with the threads scheduler. Remove its outdated TODO about Gosched leaving it in a busy loop.

@davecheney
davecheney marked this pull request as ready for review September 27, 2026 04:42
@davecheney
davecheney force-pushed the davecheney-threads-gosched-yield branch from c70b116 to b27da65 Compare September 27, 2026 05:12
@jakebailey
jakebailey requested a balanced review from Copilot September 27, 2026 05:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation is scoped correctly to supported threaded targets and matches the intended scheduler behavior.

Review effort: Balanced
Findings: None

What changed in this PR

Adds OS-thread yielding to runtime.Gosched for the threads scheduler.

Changes:

  • Adds task.Yield backed by POSIX sched_yield.
  • Uses it from the threads scheduler’s Gosched.
File Description
src/​runtime/​scheduler_threads.go Yields the current OS thread in Gosched.
src/​internal/​task/​task_threads.go Adds the thread-yield wrapper and external declaration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/runtime/scheduler_threads.go
@deadprogram

Copy link
Copy Markdown
Member

OK I looked into a bit more @davecheney

The following is edited from an automated review:

  1. The PR description says this matches upstream Go. Upstream Gosched actually hands off to the Go scheduler (gosched_m) and does not call osyield. Upstream uses osyield in spin loops such as lock and stop-the-world code. Perhaps reword that part of the description a little?

  2. The TODO in signal_waitUntilIdle in src/runtime/runtime_unix.go says the loop "becomes a busy loop when using threads". With this change it yields on each pass. You should probably update or remove that TODO in this PR.

  3. The root cause seems to be that GOMAXPROCS(1) is ignored on the threads scheduler, so sched_yield makes the race much less likely but cannot fully rule it out. Might be worth a short note in the description so nobody is surprised if the flake ever comes back?

I think this is indeed better than what we have now. Count me in favor.

@davecheney

Copy link
Copy Markdown
Contributor Author

OK I looked into a bit more @davecheney

The following is edited from an automated review:

  1. The PR description says this matches upstream Go. Upstream Gosched actually hands off to the Go scheduler (gosched_m) and does not call osyield. Upstream uses osyield in spin loops such as lock and stop-the-world code. Perhaps reword that part of the description a little?
  2. The TODO in signal_waitUntilIdle in src/runtime/runtime_unix.go says the loop "becomes a busy loop when using threads". With this change it yields on each pass. You should probably update or remove that TODO in this PR.
  3. The root cause seems to be that GOMAXPROCS(1) is ignored on the threads scheduler, so sched_yield makes the race much less likely but cannot fully rule it out. Might be worth a short note in the description so nobody is surprised if the flake ever comes back?

I think this is indeed better than what we have now. Count me in favor.

thanks for the review; i'll take another swing at this tomorrow.

Gosched was a no-op on the threads scheduler. Add task.Yield, which
calls sched_yield, and use it from Gosched to ask the OS scheduler to
let other threads run. Each goroutine has its own OS thread.

This differs from upstream Go's Gosched, which hands off to the Go
scheduler through gosched_m. Upstream uses osyield in spin loops,
including locking and stop-the-world code, not to implement Gosched.

This reduces an iter test flake on Linux. stableNumGoroutine in
iter/pull_test.go uses GOMAXPROCS(1) and Gosched to let the previous
subtest's tRunner goroutine finish exiting. TinyGo ignores GOMAXPROCS,
so sched_yield cannot guarantee that goroutine has exited or rule out
the race. The original Linux stress test under full CPU load saw
TestPull and TestPull2 fail 7 of 300 runs before this change and 0 of
300 after.

Remove the outdated signal_waitUntilIdle TODO. Its polling loop now
yields on each pass with the threads scheduler.
@davecheney
davecheney force-pushed the davecheney-threads-gosched-yield branch from b27da65 to cd6f742 Compare September 29, 2026 08:54
@davecheney

Copy link
Copy Markdown
Contributor Author

@deadprogram PTAL

@deadprogram deadprogram left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the best option we have @davecheney thanks for working on it.

@deadprogram
deadprogram merged commit 1849ad6 into tinygo-org:dev Sep 29, 2026
33 checks passed
@davecheney
davecheney deleted the davecheney-threads-gosched-yield branch September 29, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants