fix(synccompactor): preserve budget for full expansion fallback - #1111
fix(synccompactor): preserve budget for full expansion fallback#1111manojacs wants to merge 3 commits into
Conversation
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
| if err != nil { | ||
| return nil, fmt.Errorf("incremental expansion: open base graph store: %w", err) | ||
| } | ||
| closeCtx, cancelClose := context.WithTimeout(context.WithoutCancel(ctx), dotc1z.FinalizeTimeout()) |
There was a problem hiding this comment.
🟡 Suggestion: closeCtx is created here at open time but only used after LatestFinishedSyncOfAnyType and GraphFromStore (both O(artifact) reads), so its remaining budget at each store.Close(closeCtx) is FinalizeTimeout() - (time spent reading) and can already be expired. BATON_C1Z_FINALIZE_TIMEOUT is operator-tunable in whole seconds, so a low setting makes the close fail on an expired context — masking the real outcome behind "close base graph store" and leaking exactly the extracted temp data this change set out to protect. restoreEndedSync and finishIncrementalExpansion build their detached context immediately before use; doing the same here (a small closeBaseStore(ctx, store) helper, or one context.WithTimeout(context.WithoutCancel(ctx), ...) per call site) keeps the full finalize budget available at close.
General PR Review: fix(synccompactor): preserve budget for full expansion fallbackBlocking Issues: 0 | Suggestions: 3 | Threads Resolved: 0 Review SummaryScanned the full PR diff ( Risk triage (per Security IssuesNone found. Correctness IssuesNone found. Suggestions
Prompt for AI agents |
Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
| // Base extraction is part of speculative classification. It used to run on | ||
| // the unbounded parent context and could consume the entire fallback | ||
| // budget before discovering that the base had no reusable graph. | ||
| baseGraph, loadErr := c.loadIncrementalBaseGraph(classificationCtx) |
There was a problem hiding this comment.
🟡 Suggestion: On the Pebble engine — the only engine that reaches this path — classificationCtx does not actually bound base extraction. dotc1z.NewStore → pebbleDriver.OpenStore → unpackExistingPebbleC1Z → formatv3.ExtractEnvelopePayload take no context.Context (pkg/dotc1z/pebble_store.go:69, pkg/dotc1z/format/v3/indexed.go:681), so the O(artifact) decompress named in the comment above runs to completion regardless of the deadline; the cap only starts biting at pebble.Open(ctx, …). For a whale base the run budget can still be fully consumed before fallback — the exact failure this change targets. Consider threading ctx into the unpack path, and adding a test where extraction (not post-ResumeSync work) blows the classification budget.
| case errors.Is(err, context.DeadlineExceeded): | ||
| logIncrementalOutcome(ctx, "fell_back", "classification_timeout", append(fields, zap.Error(err))...) |
There was a problem hiding this comment.
🟡 Suggestion: This arm also catches deadline errors that came from walkCtx (== runCtx), not the classification cap — ExpandChanges(walkCtx, …) (line 888) and runIncrementalInvariants(walkCtx, …) (lines 866/905) both return their ctx error through here. A run-duration expiry during the committed walk is then logged as classification_timeout alongside incremental_classification_budget_percent, pointing operators at the 25% cap for a failure the cap did not cause. Distinguish the two, e.g. classificationCtx.Err() != nil && runCtx.Err() == nil for classification_timeout, and emit a separate run_duration_timeout reason otherwise.
| if percent := c.incrementalClassificationBudgetPercent; percent < 0 || percent >= 100 { | ||
| return nil, nil, fmt.Errorf("incremental classification budget percent must be between 1 and 99, got %d", percent) | ||
| } |
There was a problem hiding this comment.
🟡 Suggestion: percent < 0 lets an explicit WithIncrementalClassificationBudgetPercent(0) through, and resolvedIncrementalClassificationBudgetPercent then silently returns 25 — contradicting both this error message and the option's doc comment ("Values must be in the range 1-99"). A caller computing the percent (e.g. budgetMs*100/totalMs rounding to 0) gets the default instead of the validation error the API promises. Either track whether the option was applied (pointer or set bool) and reject an explicit 0, or document that 0 selects the default in WithIncrementalClassificationBudgetPercent.
Problem
Incremental grant expansion is speculative until the compactor has loaded a reusable base graph and classified the merged expandable rules. Previously, base loading,
PendingExpansionscanning, and restore work could consume the run budget before the remaining-duration check.A decline could therefore follow this sequence:
This addresses item 2 from the review of #1013.
Changes
WithIncrementalClassificationBudgetPercent.PendingExpansionenumeration, source-entitlement reads, and changed-entitlement classification to that bounded context.dotc1z.FinalizeTimeout().classification_timeoutseparately from real base-graph or incremental errors.not_attempted/run_duration_exhaustedwhen no budget remains before base I/O.The resulting flow is:
Tests
Added coverage for:
ResumeSync, emitting the dedicated reason, followed by detached restore and successful full expansion;Validation:
All pass locally.
Related work
#1105 removes the redundant base reopen on the normal Pebble fold path. This change remains responsible for budget ordering and for bounding classification on fallback/rebuild paths.