feat(tty): serialize terminal output writes with a lock#31
Merged
Conversation
Add an internal `lock` package wrapping a binary semaphore (Semaphore(1)) and route Tty::write / Tty::write_string through it, so a single write lands on the terminal atomically even when multiple tasks write concurrently. with_lock pairs acquire/release via defer, releasing on both the normal and failure paths. Upgrade moonbitlang/async 0.19.1 -> 0.19.4 for the semaphore API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The win32 console white-box test builds a Tty record literal directly, which must include the new write_lock field. Fixes the windows-latest CI check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Semaphore API the write lock needs already exists in 0.19.1 with an identical signature, so the bump to 0.19.4 was unnecessary. 0.19.4 also changed Windows-affecting internals and regressed the Tty write path on windows-latest (Tty::open test timed out). Staying on 0.19.1 keeps the lock feature while matching the version the tests module already pins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a4e78605bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Add docs/plans/2026-06-15-output-write-lock.md with goal, target files, public API changes, invariants, acceptance criteria, validation, and the public API audit notes required when .mbti files change, and add the TTY-23 row to the execution board. Addresses the plan-discipline review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
Output writes on
Ttyare now serialized through an internal lock, so a singleTty::write/Tty::write_stringcall lands on the terminal atomically even when multiple tasks write concurrently. Previously a large write could span multiplewrite_oncesyscalls with suspension points in between, letting another writer interleave its bytes and garble escape sequences / frames.Changes
moonbit-community/tty/internal/lockwrapping a binary semaphore (Semaphore(1)):Lock::new/acquire/try_acquire/releasewith_lock(action)pairs acquire/release viadefer, releasing on both the normal and failure paths (deferis registered only afteracquiresucceeds, so a cancelled acquire never releases a lock it doesn't hold).Ttygains awrite_lock : @lock.Lockfield (both Windows and non-Windows variants);write/write_stringroute throughwrite_lock.with_lock(...).moonbitlang/async0.19.1→0.19.4for the semaphore API.Scope / notes
write/write_string(all ofstyle.mbt/decstbm.mbtcommand methods and thequery_*helpers route here), so all output-stream writes are covered.size.mbt/state.mbtonly touchself.output.fd()for ioctls (tcgetattr/tcsetattr/window size), which are independent atomic syscalls and intentionally not locked.writes (e.g.enable_mouse) is a sequence of atomic writes, not one atomic unit — another writer can still interleave between them. Frame-level atomicity requires assembling one buffer perwrite.Validation
moon check/moon test(165 passed) /moon fmt/moon infoall clean.🤖 Generated with Claude Code