Stop flaky atomic instance batch depending on unindexed modules#5449
Merged
Conversation
atomic-endpoints-test "can write multiple instances that depend on each other" writes place.gts + country.gts in one /_atomic batch, then writes Country/Place instances that adopt from them in a second /_atomic batch. /_atomic returns once writes are durable, not once they are indexed, and _batchWriteUnlocked with waitForIndex:false deliberately skips waiting for a prior write's pending index. So the second batch's fileSerialization can look up the Place definition while the first batch's modules are still indexing. That on-demand definition lookup then races the in-flight module index — the generation bump discards the prerender result and the lookup returns undefined — so the batch fails with FilterRefersToNonexistentType and a 500. Under CI load the module index lags into that window; locally it usually wins, so the test only flakes in CI. The earlier module-before-instance ordering fix only serializes modules and instances within a single batch; this test splits them across two batches, so that fix does not apply. Fix: the module batch is setup, so post it with ?waitForIndex=true. The modules are indexed before the instance batch runs, removing the race. This is the documented way for a caller writing modules then dependent instances across separate batches to stay correct. Diagnostics kept alongside the fix so a recurrence is debuggable without a rerun: - the atomic 500 handler now logs e.cause; FilterRefersToNonexistentType carries which module/definition was missing (or that a concurrent invalidation discarded the lookup) in its cause, not its message - the instance-batch assertion prints the response body on non-201 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses a CI flake in the realm-server atomic write tests caused by issuing two separate /_atomic batches where the second batch depends on modules from the first before those modules are indexed. It also improves server-side diagnostics so future atomic-batch 500s preserve the most actionable underlying error detail.
Changes:
- Make the module-setup
/_atomicrequest inatomic-endpoints-test.tsopt intowaitForIndex=trueso the subsequent instance batch can’t race module indexing. - Improve atomic-write 500 logging to include
e.cause(message + stack when it’s anError) so root causes likeFilterRefersToNonexistentTypeErroraren’t swallowed. - Enhance the test assertion to print the response body when the instance batch doesn’t return
201.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/runtime-common/realm.ts | Logs e.cause details when an atomic write fails, improving debuggability of intermittent 500s. |
| packages/realm-server/tests/atomic-endpoints-test.ts | Uses /_atomic?waitForIndex=true for module setup and prints response body on failure to eliminate and diagnose the flaky race. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
backspace
approved these changes
Jul 9, 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.
What was flaky
atomic-endpoints-test.ts > ... > writes > can write multiple instances that depend on each otherintermittently fails in CI with a 500 andFilterRefersToNonexistentTypeErrorfor thePlacetype (failing run).Root cause
The test does two separate
/_atomicwrites: firstplace.gts+country.gts(modules), thenCountry/Placeinstances that adopt from them./_atomicreturns once writes are durable, not indexed, and_batchWriteUnlockedrun withwaitForIndex: falsedeliberately skips waiting for a prior write's pending index (so consecutive atomic writes stay responsive). So the second batch'sfileSerializationcan look up thePlacedefinition while the first batch's modules are still indexing. That on-demand definition lookup then races the in-flight module index: the generation bump discards the prerender result and the lookup returnsundefined, so the whole batch rolls back with a 500. Under CI load the module index lags into that window; locally it usually wins, so it only flakes in CI.The existing module-before-instance ordering handles modules and instances within a single batch. This test splits them across two batches, so that path never applies.
Fix
The first batch is setup for the second, so it posts with
?waitForIndex=true. The modules are indexed before the instance batch runs, which removes the race. This is the documented way for a caller writing modules and then dependent instances across separate batches to stay correct.Diagnostics kept alongside the fix
So a recurrence is debuggable without a rerun:
e.cause.FilterRefersToNonexistentTypeErrorcarries the actionable detail (which module/definition was missing, or that a concurrent invalidation discarded the lookup) in itscause, not its message.