Skip to content

refactor(batch): atomic ID counter, ItemError, ctx-aware cancellation - #63

Closed
MasterOfBinary wants to merge 6 commits into
masterfrom
refactor/cancellation-and-ids-reviewed
Closed

refactor(batch): atomic ID counter, ItemError, ctx-aware cancellation#63
MasterOfBinary wants to merge 6 commits into
masterfrom
refactor/cancellation-and-ids-reviewed

Conversation

@MasterOfBinary

Copy link
Copy Markdown
Owner

Summary

Ships the cancellation-and-IDs refactor plus the doc corrections from its ultrareview.

Refactor (existing commits):

  • Atomic ID counter — removes the dedicated ID-generator goroutine/channel (BREAKING: drops BufferConfig.IDBufferSize and DefaultIDBufferSize).
  • New exported ItemError type — per-item failures now surface as *ItemError (carrying ItemID) instead of *ProcessorError (BREAKING).
  • waitForItems now honors ctx.Done() — previously the context was ignored, delaying shutdown by up to MaxTime.
  • 247-line cancellation contract test suite + planning/codebase docs.

Ultrareview fixes (this branch's commit):

  • bug_002 — added ItemError to the error-handling guidance in CLAUDE.md/AGENTS.md (missed when the other docs were updated).
  • bug_003 — softened the post-cancel drain wording (CHANGELOG, waitForItems godoc, planning docs). The drain is best-effort/probabilistic, not a one-at-a-time guarantee; no items are dropped but post-cancel batch sizes are not guaranteed. Docs now match behavior (no logic change).

Test plan

  • go build ./... && go vet ./... clean
  • gofmt clean
  • Existing cancellation test suite unaffected (doc/comment-only changes in the fixup commit)

🤖 Generated with Claude Code

MasterOfBinary and others added 6 commits April 10, 2026 07:45
ARCHITECTURE/CONCERNS/CONVENTIONS/STRUCTURE/TESTING/INTEGRATIONS were
written before the doIDGenerator goroutine was removed and the ItemError
type was added. Update them in place rather than deleting:

- ARCHITECTURE: drop the third goroutine; describe the atomic counter
  and ctx-aware waitForItems; add a Cancellation Contract section.
- CONCERNS: mark the medium 'unused ctx' item resolved; mark the low
  ID-overflow item 'implementation simplified' (uint64 wrap still
  exists); split MaxTime timer concern into resolved (leak fixed via
  NewTimer/Stop) and remaining (idle-timeout policy still TBD); split
  Processor Contract into resolved (ItemError makes the per-item vs
  processor-wide distinction explicit) and remaining (godoc still
  doesn't formally specify item.Error handling).
- CONVENTIONS/STRUCTURE: drop doIDGenerator references; add ItemError
  to the error type list; note the atomic ID counter.
- TESTING: reference cancellation_test.go and ItemError coverage.

Leaves the High-priority panic concern explicitly open.
Replace doIDGenerator goroutine + ids channel with an atomic uint64
counter on Batch. doReader assigns IDs inline via
atomic.AddUint64(&b.nextID, 1) - 1. Drops BufferConfig.IDBufferSize and
DefaultIDBufferSize.

Introduce ItemError{ItemID, Err} for per-item failures. doProcessors
now emits *ItemError for items where item.Error is set, while
*ProcessorError remains for processor-wide failures.

Wire ctx into waitForItems: on ctx.Done() return any partial batch
immediately, then drain remaining buffered items one at a time so
already-read items are not dropped. Closes a send-on-closed-channel
race where doProcessors could close b.errs while doReader was still
alive. Replaces time.After with time.NewTimer + defer Stop() to avoid
timer leaks. Adds godoc documenting the cancellation contract: sources
must propagate ctx for cancellation to terminate.

BREAKING: BufferConfig.IDBufferSize removed; per-item errors moved
from *ProcessorError to *ItemError. See CHANGELOG for migration.
New batch/cancellation_test.go with four tests covering the ctx-aware
shutdown path:

- TestCancellation_BufferedItemsDrained: items already read from the
  source must not be dropped when ctx is cancelled. Uses a 'delivered'
  channel signalled by the source after every item has been handed off
  to doReader (rather than a wall-clock sleep) so the test is
  deterministic — no flake risk in CI.
- TestCancellation_SourceErrorNoRace: 50 iterations under -race to
  exercise the previously broken send-on-closed-channel path between
  doReader's SourceError emit and doProcessors closing b.errs.
- TestCancellation_ItemErrorType: verifies per-item failures are
  wrapped as *ItemError and that ItemID identifies the right item.
- TestCancellation_ProcessorErrorDistinct: verifies that
  *ProcessorError (processor-wide) and *ItemError (per-item) remain
  distinguishable via errors.As.
Add an Unreleased section covering:

- Added: ItemError type for per-item errors, with ItemID to correlate
  failures to specific items.
- Changed (BREAKING): per-item errors now emitted as *ItemError instead
  of *ProcessorError; BufferConfig.IDBufferSize and DefaultIDBufferSize
  removed; cancellation drains items 1-by-1 so partial batches are
  returned during shutdown; CollectErrors doc clarification.
- Fixed: ctx now honored in waitForItems; time.After leak fixed via
  NewTimer+Stop; send-on-closed-channel race window during cancellation
  closed.
- Migration: example errors.As switch including *ItemError; reminder
  to drop IDBufferSize from any BufferConfig literal.
bug_002: add ItemError to error-handling guidance in CLAUDE.md and
AGENTS.md (was missed when other docs were updated for the *ItemError
breaking change).

bug_003: soften the post-cancel drain wording in CHANGELOG, the
waitForItems godoc, and the planning docs. The drain is best-effort
(probabilistic select), not a one-at-a-time guarantee; no items are
dropped but post-cancel batch sizes are not guaranteed.

Doc/comment-only; no logic changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.31%. Comparing base (66070eb) to head (549a41e).

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #63      +/-   ##
==========================================
- Coverage   96.79%   96.31%   -0.48%     
==========================================
  Files          12       12              
  Lines         374      380       +6     
==========================================
+ Hits          362      366       +4     
- Misses          9       11       +2     
  Partials        3        3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several significant changes to the GoBatch library, including a new ItemError type for per-item failures, an atomic counter for ID generation that replaces a dedicated goroutine, and improved context cancellation logic in waitForItems to ensure buffered items are drained without dropping data. It also fixes potential timer leaks and a race condition during shutdown. The feedback highlights an inconsistency in the ItemError implementation, which uses pointer receivers for the error and Unwrap interfaces, whereas the existing ProcessorError and SourceError types use value receivers.

Comment thread batch/errors.go
Comment on lines +38 to +45
func (e *ItemError) Error() string {
return fmt.Sprintf("item %d error: %v", e.ItemID, e.Err)
}

// Unwrap returns the underlying error for compatibility with errors.Is and errors.As.
func (e *ItemError) Unwrap() error {
return e.Err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ItemError type correctly implements the error and Unwrap interfaces. Note that while ProcessorError and SourceError use value receivers for these methods, ItemError uses pointer receivers. While this is inconsistent with the other error types in the package, it is technically correct since ItemError instances are always sent as pointers to the error channel in doProcessors.

@MasterOfBinary

Copy link
Copy Markdown
Owner Author

Superseded by #64. Per maintainer direction the breaking changes in this PR (atomic ID counter, ItemError, ctx-aware cancellation) were reverted; #64 keeps only the non-breaking timer-leak fix and adds fixes for the CollectErrors reuse contract and a pre-existing ID-generator reuse race. Closing in favor of #64.

@MasterOfBinary
MasterOfBinary deleted the refactor/cancellation-and-ids-reviewed branch May 29, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant