Skip to content

refactor(engine): one replay loop for every native host - #83

Merged
colinhacks merged 2 commits into
mainfrom
rust-replay
Oct 23, 2026
Merged

colinhacks merged 2 commits into
mainfrom
rust-replay

Conversation

@colinhacks

Copy link
Copy Markdown
Owner

A host that links the engine natively has to drive its replay itself: call a round, answer every describe and catalog query it names, call again until the analysis comes back. The oracle did that three times, once per database, each with its own loop over the JSON entry point. This puts the loop in the core crate.

What changes

  • engine/core/src/replay.rs
    • Answerer (a client that blocks) and AsyncAnswerer (one whose answers are futures) each answer a describe and a catalog query. A database's refusal, such as an unknown column, is the inner Err, an answer the engine reads as it does today. The host's own failure, such as a lost connection, is the outer one, and ends the replay.
    • run and run_async run each round on the engine's stack, as api::call runs one, and give up after 64 rounds.
    • AnswerCache is the loader's cache in Rust. Needs are named by what they ask, so a later statement reuses an answer: a whole-database answer for 60 s, any other for 5 s, and a failure never.
  • The oracle: its Postgres (tokio-postgres), SQLite (rusqlite) and MySQL (mysql_async) hosts are Answerers now. They build the same JSON as before and decode it into each database's typed describe. The JSON helpers they used are gone.
  • The three engine.wasm artifacts are rebuilt, since their fingerprints hash core's sources. The wasm entry points don't use the new module.

Verified

  • The new module's tests: a toy database checks that every need is answered, that answers are reused by scope, that a refusal is an answer and never cached, that a lost host ends the replay, and that the async loop matches the blocking one.
  • The oracle on Postgres 18 and MySQL 8.4 with --strict-coverage: 116 cases, 0 failed, so every analysis through the new loop matches its expected.txt.
  • cargo build --workspace --all-targets with no warnings, the engine's tests, the artifacts' fingerprints, and the loader's typecheck and tests (226, none skipped).

Copilot AI balanced review requested due to automatic review settings October 23, 2026 20:13
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Oct 23, 2026 11:31pm UTC

Request Review

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 was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues. Two rough edges inline: the round limit no longer matches the loader's, and the Postgres host still reports a lost connection as a database refusal.

Reviewed changes

I reviewed the new core replay module, the three oracle hosts ported onto it, and the rebuilt artifacts.

  • engine/core/src/replay.rs: Answerer and AsyncAnswerer separate a host failure (outer Err) from a database refusal (inner Err). run and run_async call Db::unbundle and then Db::round on the engine's stack, as api::call does. AnswerCache ports the loader's cache, with the same 60 s and 5 s TTLs and failures never kept.
  • Oracle hosts: Postgres, SQLite and MySQL now each implement one of the traits. The JSON request and response loops, engine() and engine_call() are gone, and every statement gets a fresh AnswerCache.
  • Artifacts: all three engine.wasm files were rebuilt, since core's fingerprint changed.

I checked that extension::unbundle is idempotent (it uses or_insert), so keeping the unbundled answers in answers from one round to the next is safe.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread engine/core/src/replay.rs Resolved
Comment thread oracle/src/main.rs Resolved
@colinhacks
colinhacks force-pushed the rust-replay branch 4 times, most recently from caecb2e to d95f950 Compare October 23, 2026 23:26
A host that links the engine natively wrote its own loop around each
round and its own answers. replay::run and run_async run the loop over
an Answerer, on the engine's stack, with an answer cache like the
loader's. The oracle's three hosts now answer through it.
A describe that never reached the Postgres server now ends the replay as the host's failure, as the MySQL host's does, rather than reading as a refusal.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found. Both points from the previous review are fixed.

Reviewed changes

Since the previous Pullfrog review, the branch was rebased onto 3a14b0a and gained d95f950, which I reviewed here.

  • Round limit: replay::ROUNDS is now 16, the same bound as the replay loop in src/engine-core.ts. Its doc comment names that file, so the oracle no longer passes statements the loader would give up on.
  • Postgres host failures: pg_error now returns Option<DbError> and gives None for an error that never reached the server. PgAnswers::describe turns that None into the outer Err, so a lost connection ends the replay. The MySQL host already did this. Server refusals still carry the same message, code and position as before.
  • Artifacts: all three engine.wasm files were rebuilt, and tests/fingerprint.test.ts passes for each of them at d95f950.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

@colinhacks
colinhacks merged commit af5310d into main Oct 23, 2026
6 checks passed
@colinhacks
colinhacks deleted the rust-replay branch October 23, 2026 23:38
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.

2 participants