Repository navigation
InstanceSyncPushTest DELETE-push test times out on main and unrelated branches — the change event is dropped at 5.5s, the wait gives up at 30s #1616
Description
MeshWeaver.InstanceSync.Test.InstanceSyncPushTest.A_pending_content_change_pushes_a_DELETE_when_the_local_node_is_being_deleted fails on main and on unrelated branches, so every PR it lands on has to spend a fresh investigation proving it is not theirs. Second-largest bucket in the last-30-failed-runs sweep on #1384.
Occurrences (2026-08-14 alone)
| run | branch |
|---|---|
| 31793987399 | main |
| 31823380161 | main |
| 31824306190 | fix/clients-sdk |
| 31834879042 | fix/1444-install-attribution |
Two of the four are main, and neither of the two branch runs touches InstanceSync's path — which is what makes this an attribution tax rather than a signal.
The failure
System.TimeoutException : The operation has timed out.
at InstanceSyncTestBase.WaitForRemote(Func`2 predicate, Nullable`1 timeout) InstanceSyncTestBase.cs:98
at InstanceSyncPushTest.A_pending_content_change_pushes_a_DELETE_when_the_local_node_is_being_deleted() InstanceSyncPushTest.cs:136
The test's own output is the interesting part — the give-up is at 30 s, but the thing it is waiting for was dropped at 5.5 s:
[19:59:48.387] === TEST START ===
[19:59:48.827] [Warning] InstanceSyncWorker theta/_Sync/partner: remote unreachable
(Connection refused (fake remote is unreachable)) — accumulating; next probe in 00:00:00.1
[19:59:48.934] [Warning] InstanceSyncWorker … next probe in 00:00:00.2
[19:59:53.902] [Warning] MeshWeaver.Data.DataExtensions
Dropping DataChangedEvent for stream FPJp7O20i0exSl4PyXP-Qw on hub cache/J9hKUio…:
no synchronization hub found on this hub or any parent — the target stream is gone
(disposed circuit, released read stream, or never-created sync hub).
Sender: theta/_…
[20:00:18.995] === TEST FAILED: The operation has timed out.
So the push does happen; its change event is delivered to a stream whose sync hub is gone, and the remaining 25 seconds are spent waiting for something already discarded. 🚨 A larger timeout buys nothing here and would make the drop invisible — the bound is not the defect.
Where to look
DataExtensions's "no synchronization hub found on this hub or any parent" is the one line that names a cause. It fires on the cache hub for a stream the sender (theta/_…) still believes in — i.e. the two sides disagree about whether the stream is alive. Establish which of the three cases in its own message this is (disposed circuit / released read stream / never-created sync hub) before anything else; they have different fixes.- Timing. The drop lands ~5 s in, right after the worker's unreachable-probe backoff has run twice (0.1 s, 0.2 s). Whether the drop is caused by that backoff path releasing the stream, or merely coincides with it, is the first thing to settle.
WaitForRemote(InstanceSyncTestBase.cs:98) is the observer. It was rewritten in feat(#1508): the hub's lifecycle becomes a SOURCE, not a property to sample #1598 and reverted in revert: restore WaitForRemote's poll — my #1508 test-infra half reddened main #1605 — the revert restored the original, so the current failures are on the ORIGINAL observer. Do not re-attempt that rewrite as a fix; it made a second ordering trap and did not address this.
Not to be fixed by
Raising the 30 s bound, a retry, or a Task.Delay. Per AGENTS.md the question is why the sync hub is gone while the sender still has the stream — the drop is the evidence, and it is already logged.
How to measure a fix
flake-repro.yml in rate mode against test/MeshWeaver.InstanceSync.Test with
FullyQualifiedName~A_pending_content_change_pushes_a_DELETE_when_the_local_node_is_being_deleted,
≥100 iterations, A/B main vs the fix branch. It does not reproduce locally on an unloaded Mac (26/26 clean), so a local pass proves nothing.
Related: #1384 (the population this belongs to), #1599 (the largest bucket).
Activity
rbuergi commentedon Aug 14, 2026
An isolated loop does not reproduce it — the instrument is a full shard.
flake-repro.ymlinratemode, 40 iterations each, same filter, run on both arms:main(31836446824)So the failing condition is not in the test alone. It needs the shard around it — the other projects, the load, the contention — which matches where it actually fails: shard 0 of the full run, after
MeshWeaver.Hosting.Orleans.Test(160 tests, 163 s) andMeshWeaver.Autocomplete.Testhave just run in the same job.Two consequences worth writing down before anyone chases this:
flake-reproalone will read clean whether it works or not. Until the repro moves into the loop, the only instrument is the shard, and the only honest measure is occurrences across real runs.DOTNET_PROCESSOR_COUNT=4on the 2-vCPU runner has reproduced this class before. Getting it to fail on demand is what turns everything after it into measurement instead of guessing.Same shape as #890, where a filtered loop stayed green and only a full shard reproduced.
rbuergi commentedon Aug 14, 2026
Folding in #1617 (same test, filed independently 4 minutes later)
Two findings from there that this issue did not have, both worth keeping:
The timing-margin explanation is refuted, with numbers.
InstanceSyncTestBaseoverrides every interval to be tight —DrainDebounce = 50 ms,PullInterval = 200 ms,RetryInitial = 100 ms,RetryMax = 400 ms— so the 30 s wait covers roughly 150 pull attempts and 75 retry probes. The drain did not arrive late; it never produced a DELETE at all. That kills "widen the bound" on arithmetic rather than on principle.A concrete hypothesis for the drain path (stated there as unverified, and repeated here as such):
That fits everything observed here, including the
Dropping DataChangedEvent … the target stream is goneat 5.5 s, and it is load-sensitive in the right way: it needs an in-flight attempt straddling the reconnect flip, which is exactly what a loaded shard supplies and an idle loop does not.…and it answers that issue's suggested next step in the negative
It does not — measured before it was suggested: 0/40 on
mainand 0/40 on the branch that had just failed (31836446824, 31836444718). The isolated loop runs one process per iteration with none of the shard's contention, so it cannot reach this. Making it reproduce is the first piece of work;DOTNET_PROCESSOR_COUNT=4inside a full shard is the lever most likely to get there.So the order is: instrument the drain to prove or kill the stale-UPDATE hypothesis (does the drain push an UPDATE where a DELETE was expected?), rather than loop it and read a 0/N as evidence of anything.
Closing #1617 as a duplicate of this one; nothing from it is lost.
rbuergi commentedon Aug 17, 2026
Rate measurement on
main— 8 failures in 100 iterations (8%)Measured today with
flake-repro.ymlinratemode, 100 iterations, onmain(not on a feature branch), so the number is independent of any PR:Run: https://github.com/Systemorph/MeshWeaver/actions/runs/32024085067 (
test/MeshWeaver.InstanceSync.Test, filterFullyQualifiedName~InstanceSyncPushTest.A_pending_content_change_pushes_a_DELETE_when_the_local_node_is_being_deleted,loglevel=Debug; the failing iterations' trx + blame dumps are on the run's artifacts).Two things this pins down:
mainright now, at roughly 1-in-12. That is frequent enough that any PR touching anything has a material chance of catching it — it red-flagged a docs-only PR (docs: which routes ride a module, and which stay in the host #1731, 26 lines of markdown in one embedded.md) an hour ago, which is what prompted the measurement.Posting the number rather than a rerun, per the repo's "measure before attributing" rule — and because a
ratemeasurement is the only form in which a future before/after comparison of a fix will mean anything.Merge pull request #1743 from Systemorph/fix/1616-instancesync-delete…