Attribute reader shutdown failures to checks that read content

This commit is contained in:
2026-10-09 03:07:13 +00:00
parent 6eec022bb0
commit 7d92c477c2
4 changed files with 44 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
# Performance Implementation Remediation # Performance Implementation Remediation
**Status:** Remediation in progress; R1 complete. **Status:** Remediation in progress; R1–R2 complete.
**Review baseline:** df064bd, reviewed on 2026-10-09 against the **Review baseline:** df064bd, reviewed on 2026-10-09 against the
[performance target](performance.md) and [performance target](performance.md) and
@@ -90,6 +90,12 @@ tracking remains internal implementation bookkeeping.
later selected check that has no applicable files. Verify the earlier reader later selected check that has no applicable files. Verify the earlier reader
user receives the cleanup cause and the child exit error remains discoverable. user receives the cleanup cause and the child exit error remains discoverable.
**Completion:** Complete. Internal blob-read bookkeeping now identifies the last
check that actually used the reader. The extended close-failure fixture preserves
Go-format attribution when Markdown has no files, including the child exit code
and stderr. The regression failed before the fix and passes afterward; validation
race tests and compiled public API and dependency checks passed.
## R3: Qualify Output Allocation Measurements By Their Workload ## R3: Qualify Output Allocation Measurements By Their Workload
**Priority:** Low. **Priority:** Low.

View File

@@ -19,6 +19,7 @@ type CandidateTree struct {
byPath map[string]int byPath map[string]int
directories map[string]struct{} directories map[string]struct{}
blobReader *git.BlobReader blobReader *git.BlobReader
blobReads uint64
symlinkTargets map[string]string symlinkTargets map[string]string
closed bool closed bool
closeErr error closeErr error
@@ -113,6 +114,7 @@ func (tree *CandidateTree) readBlob(ctx context.Context, entry git.TreeEntry) ([
} }
tree.blobReader = git.NewBlobReader(operationContext, tree.repositoryDir) tree.blobReader = git.NewBlobReader(operationContext, tree.repositoryDir)
} }
tree.blobReads++
contents, err := tree.blobReader.Read(ctx, entry.ObjectID) contents, err := tree.blobReader.Read(ctx, entry.ObjectID)
if err != nil { if err != nil {
return nil, fmt.Errorf("read committed path %q: %w", entry.Path, err) return nil, fmt.Errorf("read committed path %q: %w", entry.Path, err)

View File

@@ -28,17 +28,29 @@ done
printf 'batch process failed during cleanup\n' >&2 printf 'batch process failed during cleanup\n' >&2
exit 23`) exit 23`)
err := Run(context.Background(), lifecycleRequest(repository, commit)) for _, testCase := range []struct {
var failure *Failure name string
if !errors.As(err, &failure) || failure.Check != GoFormat { markdown *MarkdownSettings
t.Fatalf("Run() error = %v, want cleanup failure attributed to GoFormat", err) }{
} {name: "formatting only"},
var exitErr *exec.ExitError {name: "later Markdown check without files", markdown: &MarkdownSettings{}},
if !errors.As(failure.Cause, &exitErr) || exitErr.ExitCode() != 23 { } {
t.Fatalf("cleanup cause = %v, want Git child exit code 23", failure.Cause) t.Run(testCase.name, func(t *testing.T) {
} request := lifecycleRequest(repository, commit)
if !strings.Contains(failure.Cause.Error(), "batch process failed during cleanup") { request.Settings.Markdown = testCase.markdown
t.Fatalf("cleanup cause = %v, want captured Git stderr", failure.Cause) err := Run(context.Background(), request)
var failure *Failure
if !errors.As(err, &failure) || failure.Check != GoFormat {
t.Fatalf("Run() error = %v, want cleanup failure attributed to GoFormat", err)
}
var exitErr *exec.ExitError
if !errors.As(failure.Cause, &exitErr) || exitErr.ExitCode() != 23 {
t.Fatalf("cleanup cause = %v, want Git child exit code 23", failure.Cause)
}
if !strings.Contains(failure.Cause.Error(), "batch process failed during cleanup") {
t.Fatalf("cleanup cause = %v, want captured Git stderr", failure.Cause)
}
})
} }
} }

View File

@@ -146,10 +146,13 @@ func Run(ctx context.Context, request Request) (result error) {
} }
}() }()
if request.Settings.GoFormat { if request.Settings.GoFormat {
lastContentCheck = GoFormat readsBefore := tree.blobReads
if err := checkGoFormat(ctx, tree); err != nil { if err := checkGoFormat(ctx, tree); err != nil {
return err return err
} }
if tree.blobReads != readsBefore {
lastContentCheck = GoFormat
}
} }
if request.Settings.ModuleDistribution.WorkspaceFiles { if request.Settings.ModuleDistribution.WorkspaceFiles {
if err := checkWorkspaceFiles(tree); err != nil { if err := checkWorkspaceFiles(tree); err != nil {
@@ -157,10 +160,13 @@ func Run(ctx context.Context, request Request) (result error) {
} }
} }
if request.Settings.ModuleDistribution.LocalReplacements { if request.Settings.ModuleDistribution.LocalReplacements {
lastContentCheck = LocalReplacements readsBefore := tree.blobReads
if err := checkLocalReplacements(ctx, tree); err != nil { if err := checkLocalReplacements(ctx, tree); err != nil {
return err return err
} }
if tree.blobReads != readsBefore {
lastContentCheck = LocalReplacements
}
} }
if request.Settings.FileSize != nil { if request.Settings.FileSize != nil {
if err := checkFileSize(tree, request.Settings.FileSize); err != nil { if err := checkFileSize(tree, request.Settings.FileSize); err != nil {
@@ -168,10 +174,13 @@ func Run(ctx context.Context, request Request) (result error) {
} }
} }
if request.Settings.Markdown != nil { if request.Settings.Markdown != nil {
lastContentCheck = MarkdownLinks readsBefore := tree.blobReads
if err := checkMarkdownLinks(ctx, tree, request.Settings.Markdown.EnforceCheckoutBoundary, request.Output); err != nil { if err := checkMarkdownLinks(ctx, tree, request.Settings.Markdown.EnforceCheckoutBoundary, request.Output); err != nil {
return err return err
} }
if tree.blobReads != readsBefore {
lastContentCheck = MarkdownLinks
}
} }
} }
return nil return nil