Repository navigation
Retry once when the batch signer returns a partial map - #310
Merged
Merged
Conversation
BatchSignedUrlLookup.lookup collapsed two distinct causes of "this uid is not in the scope's map" into one silent per-uid fallback: 1. Data drift: the file was never in this scope. The right answer is to fall back and warn. 2. A lagged index: the file IS in the scope on the server, but the batch endpoint's map has not caught up with a just-completed bulk upload. Falling back per uid here defeats the batch design -- a 28,000-member series that took the fallback would issue 28,000 getFileDetails calls where the batch would have taken one. TEST_USER_1/prod on run 229 of test-cloud-api showed the second case after #308 first let a series manifest reach the server: the batch answered signer_calls=1 with a 2-entry map for a 4-member series. The two missing uids fell back per uid. Retrying the same scope 37 minutes later on run_attempt=2 returned the full 4-entry map -- so the endpoint does eventually catch up, just not by the time waitForAllBulkUploads reported done. Fix: when a cached scope answers with a URL but a subsequent uid in that scope misses, drop the entry, sleep for DEFAULT_PARTIAL_MAP_ RETRY_SECONDS (500 ms), and re-fetch once. If the second map covers the uid, take it and every subsequent uid in the scope resolves from the refreshed cache; if not, warn and fall back as before. Bounded to one retry per scope, so a genuine data-drift miss still costs one extra signer call for the whole scope, not one per uid. Tracked in a new Stats.partial_map_retries counter so tests can distinguish the retry path from a plain data-drift miss. The live-cloud round-trip test's `signer_calls == 1` assertion is relaxed to `signer_calls == 1 + partial_map_retries`, keeping the "one batch call per series' worth of members" contract while allowing the retry. Closes #309. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao
This was referenced Sep 15, 2026
stevevanhooser
pushed a commit
that referenced
this pull request
Sep 15, 2026
NDI-matlab moved the file since 5804cd80 with one commit that has
already been ported on the Python side:
9120c002 Retry once when the batch signer returns a partial map
(VH-Lab/NDI-matlab#991)
#310 landed the corresponding Python
retry on 2026-09-15 (src/ndi/cloud/batch_signed_url.py:
_partial_map_retry_seconds / STATS.partial_map_retries). The Python
side is caught up; nothing else to port here.
Purely a bookkeeping bump to unblock this PR's CI. Included on this
branch because the drift check gates every push, not because it is
part of the mfdaq change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DiZHVBoGVBfHRYCxvoAWmo
stevevanhooser
pushed a commit
that referenced
this pull request
Sep 16, 2026
Picks up: - Sample-index-base as a third indexing rule (WDS/NDI-python#314) - mfdaq ingested-clamp-past-edges companion (#312) - Bridge hash bumps for mfdaq_reader (770c1eb3) and the accurate decision_log for batchSignedUrlLookup 9120c002. Conflict: src/ndi/cloud/ndi_matlab_python_bridge.yaml. Both this branch and main added a decision_log paragraph for the 9120c002 hash bump, but main's is the correct one: the Python side landed the partial-map retry FIRST (#310 with _partial_map_retry_seconds and STATS.partial_map_retries), so this is a hash-only bump. My earlier note incorrectly said the Python side had no equivalent. Took main's version and dropped mine. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N67xH9BejMzZp4GNAq8m7w
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.
Closes #309.
The gap
BatchSignedUrlLookup.lookuptreated "the map does not name this uid" as one thing. It is two:getFileDetailscalls where the batch would have taken one.Run 229 of
test-cloud-api.ymlon TEST_USER_1 / prod, on the merge commit that fixed #306 (#308), showed case 2: the batch answeredsigner_calls=1with a 2-entry map for a 4-member series; the two missing uids each took a per-uid round trip. Retrying the same scope 37 minutes later onrun_attempt: 2returned the full 4-entry map, so the endpoint DOES eventually catch up — just not by the timewaitForAllBulkUploadsreports done.The fix
When a cached scope answers with a URL but a subsequent uid in the same scope misses, drop the cache entry, sleep
DEFAULT_PARTIAL_MAP_RETRY_SECONDS(500 ms), and re-fetch once. If the second map covers the uid, take it and every subsequent uid in that scope resolves from the refreshed cache. If not, warn and fall back exactly as today.Bounded to one retry per scope, tracked in a new
Stats.partial_map_retriescounter — so a genuine data-drift miss still costs one extra signer call for the whole scope, not one per uid, and a run's own stats say whether the retry path fired.A test seam (
sleep=andpartial_map_retry_seconds=) lets offline tests exercise the retry without paying the wall-clock cost.Tests
Four new tests in
TestBatchLookupPartialMapRetry:test_a_lagged_scope_settles_on_the_retry— first map has 2 of 4 uids, second has all 4. All four members resolve;signer_calls=2,partial_map_retries=1,uid_misses=0.test_the_retry_pauses_before_re_fetching— the 500 ms sleep fires exactly once, on the retry path.test_a_scope_is_only_retried_once— a scope whose map stays partial produces at most one retry regardless of how many misses follow.test_a_retry_that_returns_no_map_at_all_still_falls_back— retry raises → per-uid fallback, no double-count of the miss.Existing test
test_a_uid_absent_from_the_batch_map_falls_backupdated to acknowledge the retry path:partial_map_retries=1,signer_calls=2, same fallback outcome. Retry sleep disabled in that test for speed.Live-cloud round-trip test's assertion
signer_calls == 1relaxed tosigner_calls == 1 + partial_map_retries, with a separatepartial_map_retries <= 1cap. The "one batch call per series' worth of members" contract survives; the retry is admitted.Not changed
filehandler.download_file_from_cloud— no change; it consults the same lookup._fetch_scopecode path, populating the cache the same way.Files
src/ndi/cloud/batch_signed_url.py— retry logic +partial_map_retriesstat.tests/test_cloud_batch_signed_url.py— new test class, one existing test refreshed.tests/test_cloud_file_series_round_trip.py— assertion admits one retry.Offline tests pass (22 batch, 47 in the wider batch+series suite). Bridge check passes. The 7 pre-existing keyring failures in
test_cloud_profile_secret_field_names.pyreproduce onmain; unrelated.🤖 Generated with Claude Code
https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao
Generated by Claude Code