Finding #10: bulk post-sync completion snapshot (vendored blob rebuild) - #22
Merged
Conversation
…4e → 2f345306) Sync completion made an O(n) series of per-row `unison_bridge_ri_get_details` bridge calls on the main thread (freeze on large reconciliations), and row progress walked all nodes O(n) per update (quadratic). Vendor: new patch 0005 changes `external syncComplete` to carry the final post-sync `stateItem array` (`!theState`, after do_unisonSynchronize finalizes every row's whatHappened). Blob rebuilt at the same base 91421d0 + OCaml 5.5.0; REQUIRED_PATCHES + vendor/README provenance (checksum/size/patch accounting) updated. Only `uimacbridge.ml` changed vs the prior blob inputs. C `syncComplete(value state)`: resolves accessors, marshals ONE complete snapshot (final progress + details + bytes) while the array is CAMLparam-rooted, using the exn-safe wrappers; any accessor raise / OOM aborts before publishing and delivers `ok=false` (no partial rows, never "no failures"). Kept separate from g_ri_roots (read-only completion result, not scan-state). Coordinator: `SyncResults .available/.unavailable` → `.presentSyncResults(s, snapshot)` / `.presentSyncUnavailable(s, reason)`, both bound to the exact pending (SessionID, OperationID), releasing the lease once. An unavailable snapshot does NOT enter restartRequired — the engine is quiescent post-commit. Swift: `finalizeSyncUI(snapshot:)` applies the cached snapshot once on the main actor via the pure `SyncCompletionModel` (identical details-based failure attribution, zero bridge calls); a count mismatch routes to `finalizeSyncUnavailable` (clear terminal message; only Rescan/Profiles/Quit). The completion-time per-row getter loop is deleted. New O(1) `rowToNode` index, rebuilt atomically with items/tree in replaceItems/beginScanning, replaces the O(n) `leafNode(forRow:)` walk. Tests: SyncCompletionModel (row kinds, count mismatch, skipped≠failure, large 5000-row set, row-index builder, and ZERO per-row getter calls proven by a new Debug-only C counter `unison_bridge_test_ri_get_details_count` — absent from Release, verified via nm); coordinator available/unavailable/abandoned/ non-interactive-close/duplicate-reject. Full suite 561, Debug + Release green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review corrections on #22 (blob unchanged at 2f345306): 1. Results-unavailable action gate. Added `resultsUnavailable` to ReconcileActionGate; it now blocks EVERY engine-reaching action at both validation and method boundaries — Direction/Revert, Ignore (its phase-independent path), Diff incl. canDiff, Details, Go/Sync — while allowing Rescan, Profiles/close, Quit. Fixes the leak where `.done` still permitted `.details` (→ ri_get_details) and `.ignore`. 2. Presentation. `finalizeSyncUnavailable` no longer renders the green success check: `completionEmphasis(resultsUnavailable:)` returns an orange warning symbol + a dedicated accessibility label. CompletionEmphasis gained an accessibilityLabel used for the status icon. 3. Real bridge-boundary tests. Factored the C marshalling into a shared `deliver_sync_snapshot`; a Debug seam `unison_bridge_test_run_sync_snapshot` runs the REAL accessor→strdup→handler path over the just-scanned rooted rows on the OCaml thread. New tests: real boundary (ok=true, exact row count, populated details); accessor-raise at row 1 and allocation failure at row 1 (each → exactly one unavailable completion, zero partial rows, runtime still usable); a direct wrong-OperationID coordinator rejection; a gate matrix; and the presentation descriptor. Debug seams are absent from Release (nm-verified). 4. ABI hardening. `syncComplete` validates `Is_block`/tag-0 before `Wosize_val` and bounds `size_t`→int; the Swift trampoline rejects the malformed `ok=true, count>0, rows==NULL` shape as unavailable rather than empty success. Full suite 569, Debug + Release green; blob and patches untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/guard tests Verification-only (no production behavior change; blob unchanged at 2f345306): 1. Real end-to-end sync test (test_h_realSync): drives the actual public path unison_bridge_synchronize() → vendored unisonSynchronize → do_unisonSynchronize → syncComplete !theState (patch 0005) → C marshaller → Swift handler, over a local IntegrationFixture with conflict-free one-sided files. Asserts dispatch == UNISON_BRIDGE_OK, exactly one ok=true completion, snapshot count == scanned rows, plausible terminal fields (a "done" row + populated details), and that the one-sided files were actually propagated between the two roots. Uses the real OCaml caller, not the test seam. 2. Accessor-failure now covers all three fields (progress/details/bytes) at row 1 — each: exactly one ok=false, zero partial rows, and a subsequent clean snapshot. 3. Direct Swift malformed-success guard test: extracted an internal UnisonBridge.convertSyncCompletion(ok:count:rows:) helper (the trampoline now calls it) and assert ok=true+count>0+null-rows and negative count both map to unavailable, while ok=true+count 0+null-rows is a genuine empty success. 4. Wrong-OperationID test strengthened: after the bogus completion, the real sync phase/lease is asserted still pending (.syncing(s, op)); the genuine completion then lands and transitions to .ready. Full suite 571, Debug + Release green; Debug test symbols absent from Release (nm); blob and patch set unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
Author
Verification pass (final) —
|
…apshot # Conflicts: # TODO.md # Tests/BridgeTests.swift
The 'Patches applied' row and checksum note already list patch 0005; the Reproducibility line's parenthetical still said (0002+0003+0004). Corrected to (0002+0003+0004+0005) so the provenance is internally consistent. No blob or patch change (blob stays 2f345306). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
leafNode(forRow:) already does an O(1) rowToNode lookup (the index is rebuilt synchronously with items/tree). The doc still described it as an O(items) tree walk and proposed future caching. Corrected to state the O(1) index lookup. Comment only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bcourbage
marked this pull request as ready for review
July 20, 2026 21:03
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.
Draft — for review, do not merge. Finding #10 (fast, safe sync completion), integrated on top of the cumulative
main(PRs #16–#21) at968f4ae. Includes the authorized vendored-blob bumpa57f5c4e → 2f345306and OCaml patch0005.Problem
finalizeSyncUImade O(n) per-rowunison_bridge_ri_get_detailsbridge calls on the main thread at sync completion (a completion freeze on large syncs); per-row progress used an O(n) node walk.Fix
patches/0005—syncCompletecarries the final post-syncstateItem array(!theStateafterdo_unisonSynchronize). No new OCaml allocation; C reads final values via the already-registered accessors while the array is GC-rooted.deliver_sync_snapshot) — resolve accessors and marshal ONE complete snapshot (progress + details + bytes) with the exn-safe wrappers; any accessor raise / OOM / bad shape aborts before publishing →ok=false, never partial, never "no failures". ABI guards:Is_block/tag-0 beforeWosize_val,size_t→int bound; the Swift trampoline rejectsok+count>0+null-rows.SyncResults .available/.unavailable→ effects.presentSyncResults(SessionID, snapshot)/.presentSyncUnavailable(SessionID, reason:), bound to the exact pending(SessionID, OperationID), lease released once, and NEITHER enteringrestartRequired(engine is quiescent post-commit).finalizeSyncUI(snapshot:)applies once on main via the pureSyncCompletionModel(identical details-based attribution, zero bridge calls); a count mismatch routes tofinalizeSyncUnavailable. The per-row getter loop is gone; an O(1)rowToNodeindex is rebuilt atomically with items/tree.ReconcileActionGatestate blocks every engine-reaching action (Direction/Revert, Ignore incl. its phase-independent path, Diff incl.canDiff, Details, Go/Sync) at both validation and method boundaries; only Rescan/Profiles/Quit remain.finalizeSyncUnavailableshows a dedicated orange warning (not the green success check).Cumulative integration (PRs #16–#21 preserved)
Merged current green
mainvia a normal merge. The two conflicts were resolved preserving both sides:Tests/BridgeTests.swift: kept PR Finding #10: bulk post-sync completion snapshot (vendored blob rebuild) #22's four sync-snapshot tests AND PR L1: app-global diff-request broker (owner + drain) + genuine run_show_diffs fault test #21's real diff-fault test (test_r); the co-located append had matched the trailing brace as common — added the one missing method brace.TODO.md: consolidated to one entry per finding (In-process close→reopen leaves the embedded Unison engine unable to reopen (stall / corrupted protocol) #8/Finding #2: Revert to Recommendation is a real engine inverse (unisonRiRevert) + first-class changedFromDefault #12, feat(shutdown): reap wedged ssh transport children on exit — exact-PID registry (issue #6) #9, docs(todo): correct patch accounting + track post-#6 follow-ups #10, Bridge safety: GC rooting, exception containment, transactional publication, and reconciliation action-gating #11, Finding #7: lossless profile-directive preservation on save (source / include? / source? / raw) #13, L1) with no duplicate or historical layers.AppDelegate and ReconcileWindowController auto-merged cleanly and retain both PR L1: app-global diff-request broker (owner + drain) + genuine run_show_diffs fault test #21's app-global diff broker (owner routing + drain contract) and PR Finding #10: bulk post-sync completion snapshot (vendored blob rebuild) #22's post-sync snapshot / results-unavailable gate / exact
(SessionID, OperationID)ownership / row-index / presentation.Validation (at merge
2ab70c7; final head0aef318adds a README-only fix)make build(Debug) +make build CONFIG=Releasesucceed.test_h_realSync_propagatesFiles_andDeliversSnapshot): the REAL public path —unison_bridge_synchronize()→do_unisonSynchronize→syncComplete !theState(patch 0005's real caller, not the Debug seam) → C marshaller → Swift handler — over conflict-free one-sided files: oneok=truesnapshot, count == scanned, plausible fields, files actually propagated on disk. ✅test_r_runShowDiffs_realBridgeRaise_thenEngineUsable): realunison_bridge_run_show_diffsforced throughbridge_call2_exn's raise path → false return, no callback published, broker cleared, subsequent real diff succeeds. ✅nm): theUNISON_DEBUG_HOOKS-gated seams (run_sync_snapshot,fail_snapshot_accessor_at,fail_strdup_at,force_next_callbacks_raise, …) are absent from the Release binary; they are present/functional in the Debug test host (the accessor-raise and alloc-failure tests exercise them and pass).unison_bridge_test_statusis an ungated, always-compiled synthetic-status helper (pre-existing), not one of the gated seams.Vendor provenance
Base
91421d0, OCaml 5.5.0, patches0002,0003,0004,0005(no0006). Blob SHA-2562f345306314305d8e921fea587d913b628b86872553bc3776047ef58fe1dfc89.vendor/README.mdprovenance updated for internal consistency (the Reproducibility line now lists the full0002+0003+0004+0005set).Limitations
finalizeSyncUI/finalizeSyncUnavailable) is not driven by an AppKit UI harness — the gate, coordinator,SyncCompletionModel, index builder, and the real C marshaller (Debug seam + the realunison_bridge_synchronizepath) are all tested.