Skip to content

v0.1.1: threading wait(timeout) deadlock fix#268

Merged
tamnd merged 1 commit into
mainfrom
v0.1.1-threading-timeout
Apr 29, 2026
Merged

v0.1.1: threading wait(timeout) deadlock fix#268
tamnd merged 1 commit into
mainfrom
v0.1.1-threading-timeout

Conversation

@tamnd

@tamnd tamnd commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replaces sync.Cond with chCond (channel-backed notifier with timeout) inside vm/stdlib_threading.go. Pre-v0.1.1 Condition.wait(timeout=…), Event.wait(timeout=…), Semaphore.acquire(timeout=…), Barrier.wait(timeout=…) hung on sync.Cond.Wait and tripped Go's deadlock detector.
  • Wires timeout through every primitive that takes one; Condition.notify(n) now wakes up to n waiters instead of always Signal-one.
  • Exposes threading.BrokenBarrierError as a real subclass of RuntimeError on the threading module.

First release of the v0.1.x cycle (roadmap at notes/Spec/1500/1541_goipy_v01x_roadmap.md, V1). Spec at notes/Spec/1500/1542_goipy_v0101_condition_timeout.md.

Test plan

  • go build ./...
  • go test ./vm/ -run TestFixtures — all 347 fixtures pass
  • Fixture 347 output matches CPython 3.14 byte-for-byte
  • /tmp/probe_v01x_thr2.py no longer deadlocks; Condition.wait(timeout=0.05) returns False after ~50ms as expected
  • Existing threading-using fixtures (94-99, 132, 134, 136, 137) unaffected

Pre-v0.1.1, every threading.Condition.wait(timeout=...),
Event.wait(timeout), and Semaphore.acquire(timeout) call hung on
sync.Cond.Wait, which has no timeout. The Go deadlock detector
killed the process with "all goroutines are asleep - deadlock!".

This release replaces sync.Cond with chCond, a channel-backed
notifier. Each Wait() registers a fresh channel; the FIFO of
waiters is woken by Notify(n) or Broadcast(); timeout via select
+ time.After.

What's wired:
- Event.wait(timeout=None)
- Condition.wait(timeout=None) — returns True if notified, False on timeout
- Condition.wait_for(predicate, timeout=None) — returns last predicate result
- Condition.notify(n) — wakes up to n waiters (was always Signal-one)
- Semaphore.acquire(blocking, timeout)
- Barrier.wait(timeout) — on timeout, breaks the barrier

threading.BrokenBarrierError (subclass of RuntimeError) is now
exposed as a real class on the threading module instead of a bare
RuntimeError with a "BrokenBarrierError" message.

First release of the v0.1.x cycle. Spec at
notes/Spec/1500/1542_goipy_v0101_condition_timeout.md; fixture
347 covers all four primitives across timeout-expired and
notified-before-timeout paths.

Closes V1 from notes/Spec/1500/1541_goipy_v01x_roadmap.md.
@tamnd tamnd merged commit 3b93209 into main Apr 29, 2026
1 check passed
@tamnd tamnd deleted the v0.1.1-threading-timeout branch April 29, 2026 08:59
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.

1 participant