Conversation
This was referenced Aug 30, 2026
shibukawa
added a commit
to shibukawa/tinygodriver
that referenced
this pull request
Sep 2, 2026
…on the TinyGo path TinyGo's sync.RWMutex (through at least 0.42) deadlocks whenever a reader arrives while a writer is waiting for the existing readers to drain. Lock() subtracts rwMutexMaxReaders and waits for the last RUnlock to bring the count back to exactly that; RLock() adds 1 and then waits for the count to turn positive, keeping its +1 while it waits. A reader that arrives mid-wait is therefore counted as a holder that never leaves: the writer is never woken and the reader waits for the writer's Unlock. Standard Go snapshots the readers a writer must wait for in a separate counter. Upstream fix: tinygo-org/tinygo#5630, open at time of writing. This is what `tinygo test ./websocket` had been hanging on, roughly one run in three: a stack sample showed 15 threads in RWMutex.RLock and one in RWMutex.Lock, all reached through netdev.Device.mu, which every Send and Recv read-locks while Socket, Accept and Close write-lock it. A four-step interleaving reproduces it deterministically; 16 readers and a writer around a map hung 13 of 13 runs; the same netdev echo probe went from 11 of 40 hung to 0 of 40 with a plain mutex. internal/syncx.RWMutex is sync.RWMutex on standard Go and a plain sync.Mutex behind the same method set on tinygo and force_tinygo_logic. Every RWMutex a tinygo build could reach now uses it: netdev's socket table and the darwin and windows TLS session tables, httpmux, the dynamodb and datastore field caches, the s3 region, fasthttp's HostClient map (as vendor.py patches, PATCHES.md section 8) and the five registries in the mysql fork (PETITWEB comments, README). Each critical section is a map lookup, so serializing readers costs nothing measurable. Three tests pin it down. TestRWMutexHandsOverToWaitingWriter runs the four-step interleaving against the shim on every build. TestUpstreamRWMutexStillDeadlocks (tinygo only) runs it against sync.RWMutex and expects the deadlock, so it fails the day a TinyGo release ships the fix, which is the signal to retire the shim. TestNoStdRWMutexOnTinyGoPath asks `go list -tags tinygo` for the files of every package on darwin, linux and windows and parses them for the selector, so neither first-party code nor a re-vendored fork can drift back. Verified: go test and go test -tags force_tinygo_logic across the module; tinygo test for syncx, netdev, httpmux, httprevproxy, fasthttp and fasthttpwebsocket; `tinygo test ./websocket` 20 times under a watchdog with 0 hangs, against 5 of 16 before; the examples that touch the swapped packages build, as does a linux/arm64 cross-link; mingw vets the windows session table. TestLargeMessage's occasional EPIPE, seen on the untouched tree as well, is a separate flake and is not changed by this. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
shibukawa
added a commit
to shibukawa/tinygodriver
that referenced
this pull request
Sep 2, 2026
TinyGo's sync.RWMutex deadlocks whenever a reader arrives while a writer is waiting; netdev.Device.mu is read on every Send and Recv and written on every Socket, Accept and Close, which is how tinygo test ./websocket hung one run in three. internal/syncx.RWMutex is the standard type on standard Go and a plain mutex on TinyGo, every reachable RWMutex now uses it, a policy test keeps it that way, and a tinygo-only test fails the day a release ships the upstream fix (tinygo-org/tinygo#5630). 0 hangs in 20 runs, against 5 in 16. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RWMutex counts the readers that hold the lock and the readers that queue behind a waiting writer in one number, and both sides wait on a predicate over that number. Two interleavings stop the program permanently. A writer waits until the count shows no readers at all. A reader that arrives during that wait joins the same count, so the last holder of the lock no longer sees the condition that wakes the writer. A reader that Unlock releases reads the count again instead of an acquire. A writer that arrives in between changes the base of the count, so the reader goes back to sleep after its wakeup is spent, while that writer waits for it. Use the split that the standard library uses. A writer records how many readers it finds and waits only for those, so later readers cannot starve it. Counting semaphores hand the lock over, so a released waiter holds the lock and does not test a value again that a third party can change back. task.Semaphore cannot do this, because one Post does nothing when there are several waiters, so the file gets a small futex semaphore that can. Ordinary code reaches this. syscall.ForkLock is an RWMutex, os.Pipe read-locks it and os.StartProcess write-locks it, so a program that starts processes and makes pipes at the same time can stop. The two new tests fail on the current code and pass with this change.
yohimik
force-pushed
the
upstream-pr/sync-rwmutex
branch
from
September 2, 2026 08:49
7001593 to
6965ab2
Compare
Author
|
Rebased on dev after the 0.42.0 release. The problem is present in v0.42.0 as |
yohimik
added a commit
to yohimik/tinygo
that referenced
this pull request
Sep 5, 2026
Use posix_spawn on hosted Linux and Darwin. Map process files, apply the working directory and process group, and clear the child signal mask. Use wait4 for process status and support Kill and Signal. Mark Darwin pipes close-on-exec under ForkLock. Darwin also needs the fcntl wrapper in PR tinygo-org#5612 and the libSystem symbols in PR tinygo-org#5636. Concurrent spawn and pipe creation need the RWMutex fix in PR tinygo-org#5630. Keep the process stubs on other targets and add process regression tests.
Member
|
@yohimik this PR is just too much text for me to review. Please follow the latest AGENTS.md guidelines. Also, what is the specific problem that you had caused you to work on this? Probably opening an issue first would be helpful. Thanks! |
This was referenced Sep 17, 2026
yohimik
added a commit
to yohimik/tinygo
that referenced
this pull request
Sep 17, 2026
Use posix_spawn on hosted Linux and Darwin. Map process files, apply the working directory and process group, and clear the child signal mask. Use wait4 for process status and support Kill and Signal. Mark Darwin pipes close-on-exec under ForkLock. Darwin also needs the fcntl wrapper in PR tinygo-org#5612 and the libSystem symbols in PR tinygo-org#5636. Concurrent spawn and pipe creation need the RWMutex fix in PR tinygo-org#5630. Keep the process stubs on other targets and add process regression tests.
This was referenced Sep 17, 2026
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.
Superseded by #5697. The problem is tracked in #5692.
Closed on 17 September 2026 with a request for an issue first. The replacement uses the same patch, rebased on 18 September onto
devat93940cb6. Its description contains the scope, dependencies, and test status.