Skip to content

Drop _VeraExit(130) Ctrl-C workaround; centralize SIGINT handling on wasmtime 45 (#599)#709

Merged
aallan merged 2 commits into
mainfrom
fix/599-drop-keyboardinterrupt-workaround
May 29, 2026
Merged

Drop _VeraExit(130) Ctrl-C workaround; centralize SIGINT handling on wasmtime 45 (#599)#709
aallan merged 2 commits into
mainfrom
fix/599-drop-keyboardinterrupt-workaround

Conversation

@aallan

@aallan aallan commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

Removes the _VeraExit(130) Ctrl-C workaround that #599 bookmarked, now that the upstream fix has shipped on PyPI.

Key finding (the issue's premise was incomplete). #599 framed step 2 as "remove the guard and let KeyboardInterrupt propagate naturally." I verified the actual post-upgrade behavior first, and it turns out the upstream fix (except Exceptionexcept BaseException, [bytecodealliance/wasmtime-py#337]) prevents the SIGABRT but does not by itself produce the clean exit-130 UX — it re-raises a bare KeyboardInterrupt, which would escape execute() entirely (its except clauses catch Exception, and KeyboardInterrupt is a BaseException). So this is a relocation, not a deletion.

What changed

Dependency wasmtime>=44.0.0>=45.0.0 (+ uv.lock). 45.0.0 (2026-05-26) is the first PyPI release containing the trampoline fix; 44.0.0 was tagged 2026-04-20, before the 2026-05-07 merge, so it never had it.
Centralized handler One except KeyboardInterrupt at the func(store, ...) call site in execute(), mapping a Ctrl-C in any host import to exit_code=130 with stdout/stderr/state preserved (modeled on the existing IO.exit / _VeraExit handler).
Removed The four per-host-import launder guards — one in host_sleep, three across host_read_char (Unix non-TTY, Unix TTY cbreak, Windows getwch). Terminal restore on the Unix-TTY path stays correct: it lives in a finally, so it runs before the interrupt propagates.

One source of truth replaces four duplicated workarounds.

Why 45.0.0, not 44.0.0

The bookmark text said "current pin 43.0.0" — but the pin had already drifted to 44.0.0. That was a false floor: 44.0.0 predates the fix. Confirmed three ways (git ancestry ahead_by, release dates, and the installed 44.0.0 still carrying except Exception as e:). 45.0.0 has wheels for every supported platform (macosx_11_0_arm64, macosx_10_13_x86_64, manylinux*_x86_64, win_amd64; all py3-none, requires_python>=3.9).

Verification

  • Real SIGINT to a running vera run of an IO.sleep program → exit code 130, pre-interrupt stdout preserved, no SIGABRT. This is the exact macOS malloc abort during wasmtime cleanup after Ctrl-C-in-host-import #595 repro (Conway's Life Ctrl-C), now clean.
  • pytest tests/ -m "not stress"3930 passed, 15 skipped on wasmtime 45.0.0. The upgrade touches all execution (the broader-surface change the issue flagged); zero regressions.
  • mypy vera/ clean; all 25 pre-commit hooks green; doc counts + site assets consistent.

Tests (TestHostSleepKeyboardInterrupt)

  • Structural assertion flipped: the four raise _VeraExit(130) guards are gone and execute() carries the centralized exit-130 handler.
  • IO.sleep end-to-end test kept — it passes identically before and after the relocation, which is exactly what proves the guard could be removed without changing UX.
  • New IO.read_char end-to-end test (non-TTY branch) — proves the removed read_char guards still exit cleanly via the central handler.

Release prep (in this PR, per workflow)

Version bump to v0.0.160 across all tracked sites, CHANGELOG cut, HISTORY row, doc counts, KNOWN_ISSUES.md runtime-workaround row removed (table now empty → "No runtime workarounds currently in place").

Closes #599
Closes #595

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Version bumped to 0.0.160
    • Minimum wasmtime dependency raised to 45.0.0
  • Bug Fixes

    • Centralised Ctrl+C/KeyboardInterrupt handling so user interrupts yield a consistent exit code 130 and preserve prior output
    • Removed per-import Ctrl+C workarounds now redundant with upstream fix
  • Documentation

    • Release notes, history, README, roadmap, known issues and testing docs updated for v0.0.160 and test count bump

Review Change Stack

Closes #599 and #595.

wasmtime-py 45.0.0 (released 2026-05-26) is the first PyPI release whose
host-import trampoline catches BaseException rather than Exception
(bytecodealliance/wasmtime-py#337, the fix Vera filed via #336). That
makes a raw KeyboardInterrupt raised inside a host import unwind the wasm
call safely and re-raise in Python at the call site, instead of escaping
into Rust with an undefined ABI return value and aborting with a libmalloc
SIGABRT (#595).

Empirically determined the post-upgrade behavior before changing anything:
the upstream fix prevents the crash but does NOT by itself produce the
clean exit-130 UX — it re-raises a bare KeyboardInterrupt, which would
otherwise escape execute() (whose except clauses only catch Exception, not
BaseException). So this is a *relocation*, not a deletion:

- Bump pyproject.toml wasmtime floor 44.0.0 -> 45.0.0 (+ uv.lock).
- Add ONE `except KeyboardInterrupt` handler at the func(store, ...) call
  site in execute(), mapping a Ctrl-C in any host import to exit_code=130
  with captured stdout/stderr/state preserved (modeled on the existing
  IO.exit / _VeraExit handler).
- Remove the four per-host-import launder guards: one in host_sleep, three
  across host_read_char (Unix non-TTY, Unix TTY cbreak, Windows getwch).
  The Unix-TTY terminal restore stays correct — it lives in a `finally`,
  so the terminal is restored before the interrupt propagates.

One source of truth replaces four duplicated workarounds.

Verification:
- Real SIGINT to a running `vera run` of an IO.sleep program: exit code
  130, pre-interrupt stdout preserved, no SIGABRT (the exact #595 repro).
- pytest tests/ -m "not stress": 3930 passed, 15 skipped (full suite green
  on wasmtime 45.0.0 — the broader-surface upgrade the issue flagged).
- mypy vera/ clean; doc counts + site assets consistent.

Tests (tests/test_runtime_traps.py::TestHostSleepKeyboardInterrupt):
- Replaced the structural "host_sleep has the guard" assertion with a
  structural assertion that the four guards are gone AND execute() carries
  the centralized exit-130 handler.
- Kept the IO.sleep end-to-end test (passes identically before and after
  the relocation — it is the behavioral contract that justified removal).
- Added an IO.read_char end-to-end test (non-TTY branch) proving the
  removed read_char guards still exit cleanly via the central handler.

KNOWN_ISSUES.md: removed the host_sleep runtime-workaround row (table now
empty -> "No runtime workarounds currently in place").

Co-Authored-By: Claude <noreply@anthropic.invalid>
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cf7fcf9c-7145-4244-b5ab-798344d3f949

📥 Commits

Reviewing files that changed from the base of the PR and between 2513803 and 5ca6652.

📒 Files selected for processing (2)
  • HISTORY.md
  • tests/test_runtime_traps.py

📝 Walkthrough

Walkthrough

Release v0.0.160 centralises Ctrl-C handling into a single execute() KeyboardInterrupt handler (exit code 130) enabled by requiring wasmtime >=45.0.0; per-host-import guards were removed, Unix TTY restore logic was refactored, tests were added/updated, and docs/metadata were bumped.

Changes

KeyboardInterrupt centralisation refactoring

Layer / File(s) Summary
Release metadata and dependency floor
pyproject.toml, vera/__init__.py
Version bumped to 0.0.160; wasmtime dependency floor raised from >=44.0.0 to >=45.0.0 with updated inline commentary.
Core runtime refactoring: host imports and execute() handler
vera/codegen/api.py
Host imports (host_sleep, host_read_char) stop laundering KeyboardInterrupt into _VeraExit(130) and allow it to propagate. Unix TTY restore logic is moved into finally blocks to ensure terminal state is restored before propagation. execute() gains a top-level except KeyboardInterrupt that returns ExecuteResult(exit_code=130) while preserving stdout/stderr/state and host store sizes.
Test suite: structural and behavioural validation
tests/test_runtime_traps.py
Structural test now verifies absence of per-import _VeraExit(130) guards and presence of centralized execute() handler; end-to-end tests assert exit_code == 130 and stdout preservation for IO.sleep and IO.read_char interruption scenarios.
Documentation and release notes
CHANGELOG.md, HISTORY.md, KNOWN_ISSUES.md, README.md, ROADMAP.md, TESTING.md
Added v0.0.160 entry documenting the wasmtime floor bump and removal of per-import workarounds; updated runtime-workarounds note, project totals, test count (3,960 → 3,961), and test file metadata.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • aallan/vera#600: Related work around the host_sleep KeyboardInterrupt guard; contextually connected to this removal and documentation updates.
  • aallan/vera#690: Related to IO.read_char semantics; earlier PR introduced read_char and this PR adjusts its interrupt propagation and tests.

Suggested labels

compiler, tests, docs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely summarises the main change: removal of per-import Ctrl-C workaround guards and centralisation of KeyboardInterrupt handling in execute() to work with wasmtime 45, matching the core refactoring across vera/codegen/api.py and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/599-drop-keyboardinterrupt-workaround

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.84%. Comparing base (7c30465) to head (5ca6652).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #709      +/-   ##
==========================================
+ Coverage   90.80%   90.84%   +0.03%     
==========================================
  Files          60       60              
  Lines       24091    24086       -5     
  Branches      292      292              
==========================================
+ Hits        21875    21880       +5     
+ Misses       2209     2199      -10     
  Partials        7        7              
Flag Coverage Δ
javascript 61.40% <ø> (ø)
python 94.60% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@HISTORY.md`:
- Line 348: The file contains inconsistent counts for active-development days:
update the earlier summary occurrence that currently reads "59 active
development days" so it matches the later line which reads "Total: **810+
commits, 160 tagged releases, 60 active development days.**"; search for the
text "59 active development days" (or the summary paragraph near the top) and
change the numeric value to "60" to ensure both summaries are identical.

In `@tests/test_runtime_traps.py`:
- Around line 2195-2198: The test reads the API source using
Path(...).read_text() without specifying encoding, which can cause
cross-platform differences; update the read of api.py by calling read_text with
encoding="utf-8" (the variable is api_src in tests/test_runtime_traps.py) so the
file is opened using explicit UTF-8 encoding for portable test behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 597d5dbe-9b17-4691-9111-b8e46dce146f

📥 Commits

Reviewing files that changed from the base of the PR and between 7c30465 and 2513803.

⛔ Files ignored due to path filters (6)
  • docs/index.html is excluded by !docs/**
  • docs/index.md is excluded by !docs/**
  • docs/llms-full.txt is excluded by !docs/**
  • docs/llms.txt is excluded by !docs/**
  • docs/sitemap.xml is excluded by !docs/**
  • uv.lock is excluded by !**/*.lock, !uv.lock
📒 Files selected for processing (10)
  • CHANGELOG.md
  • HISTORY.md
  • KNOWN_ISSUES.md
  • README.md
  • ROADMAP.md
  • TESTING.md
  • pyproject.toml
  • tests/test_runtime_traps.py
  • vera/__init__.py
  • vera/codegen/api.py

Comment thread HISTORY.md
Comment thread tests/test_runtime_traps.py
- HISTORY.md: the top-of-file summary still read '59 active development
  days' (my earlier replace_all only matched the 'Total:' footer phrasing,
  not the 'across N active development days' intro). Now 60, consistent
  with the footer.
- tests/test_runtime_traps.py: the structural test's api.py read_text()
  now passes encoding='utf-8', matching the adjacent test at line 2043 and
  the project's cross-platform fixture convention (CLAUDE.md). The other
  read_text() at line 1826 is pre-existing and outside this PR's diff;
  left untouched to keep the change minimal.

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan aallan merged commit 9f5158f into main May 29, 2026
28 checks passed
@aallan aallan deleted the fix/599-drop-keyboardinterrupt-workaround branch May 29, 2026 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant