Skip to content

feat: 30.2 — unified api surface#4

Merged
vieiralucas merged 1 commit intomainfrom
feat/30.2-unified-api
Mar 25, 2026
Merged

feat: 30.2 — unified api surface#4
vieiralucas merged 1 commit intomainfrom
feat/30.2-unified-api

Conversation

@vieiralucas
Copy link
Copy Markdown
Member

@vieiralucas vieiralucas commented Mar 25, 2026

Summary

  • Update Go SDK for unified API from Story 30.1
  • Remove BatchEnqueue RPC and BatchEnqueue() method
  • Enqueue, Ack, Nack now use repeated message fields in the proto
  • Consume uses only the repeated messages field (singular message removed)
  • Rename BatchMode -> AccumulatorMode, batch.go -> accumulator.go
  • Add EnqueueMany() replacing BatchEnqueue() for explicit multi-message enqueue
  • ItemError wraps sentinel errors (ErrQueueNotFound, ErrMessageNotFound) via Unwrap() so errors.Is works on per-item results
  • All 20 tests pass (5 unit + 15 integration)

Test plan

  • Unit tests: TestExtractMessages*, TestProtoToConsumeMessageNil
  • Integration tests: TestEnqueueManyExplicit, TestEnqueueManyPartialFailure, TestEnqueueManyEmpty, TestEnqueueManyNonexistentQueue
  • Accumulator tests: TestEnqueueWith*Accumulator*, TestEnqueueConcurrentAccumulation, TestCloseFlushesPendingMessages
  • Core flow tests: TestEnqueueConsumeAck, TestEnqueueConsumeNackRedeliver, TestEnqueueNonexistentQueue
  • TLS/auth tests: TestTLSConnection, TestAPIKeyAuth, TestAPIKeyAuthRejected, TestNoAuthBackwardCompatible

Summary by cubic

Unifies the Go SDK with the broker’s unified API. Removes BatchEnqueue, adds an internal accumulator and EnqueueMany(), and moves Enqueue/Ack/Nack/Consume to repeated message fields for multi-message operations.

  • New Features

    • Enqueue/Ack/Nack requests now use repeated message fields in proto/fila/v1/service.proto.
    • Added internal accumulator with AccumulatorMode (default AccumulatorModeAuto) for opportunistic multi-message sends.
    • Added EnqueueMany() for explicit multi-message enqueue.
    • ItemError wraps sentinel errors (e.g., ErrQueueNotFound, ErrMessageNotFound) so errors.Is works per item.
    • Consume now delivers only via messages (singular message removed).
  • Migration

    • Replace BatchEnqueue() with EnqueueMany().
    • Replace WithBatchMode(...) and all BatchMode* types with WithAccumulatorMode(...) and AccumulatorMode*.
    • Update any consumers relying on ConsumeResponse.message to use ConsumeResponse.messages.
    • For per-item failures from EnqueueMany(), check results with errors.Is(err, fila.ErrQueueNotFound) etc.

Written for commit a18e208. Summary will update on new commits.

- update service.proto to unified api (no more BatchEnqueue rpc)
- enqueue/ack/nack use repeated message fields
- consume uses only repeated messages field (singular removed)
- rename BatchMode to AccumulatorMode, batch.go to accumulator.go
- add EnqueueMany() replacing BatchEnqueue()
- ItemError wraps sentinel errors (ErrQueueNotFound, ErrMessageNotFound)
  so errors.Is works on per-item results
- all 20 tests pass (5 unit + 15 integration)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 13 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="client.go">

<violation number="1" location="client.go:190">
P2: Disabled mode detection misses pointer values (`*AccumulatorModeDisabled`), so accumulation can be enabled unexpectedly.</violation>
</file>

<file name="proto/fila/v1/service.proto">

<violation number="1" location="proto/fila/v1/service.proto:25">
P2: Field numbers 2 and 3 from the old `EnqueueRequest` (and similarly in `AckRequest`, `NackRequest`, `ConsumeResponse`) are removed without `reserved` directives. If a future edit accidentally reuses these numbers, old serialized data will be silently misinterpreted. Add `reserved 2, 3;` to guard against that.</violation>
</file>

<file name="accumulator.go">

<violation number="1" location="accumulator.go:124">
P2: `runAuto` has no cap on the number of items drained into a single batch, unlike `runLinger` which respects `maxSize`. Under high throughput the entire 4096-item channel buffer can be flushed in one RPC, which risks exceeding gRPC's default 4 MB max message size and failing with `ResourceExhausted`. Consider adding a `maxSize` cap to the drain loop, consistent with `runLinger`.</violation>
</file>

<file name="enqueue.go">

<violation number="1" location="enqueue.go:71">
P2: `EnqueueMany` does not validate that response result count matches input message count, so missing per-item results are silently dropped.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread client.go
if _, disabled := batchMode.(BatchModeDisabled); !disabled {
c.batcher = newBatcher(svc, batchMode)
// Start accumulator unless disabled.
if _, disabled := accMode.(AccumulatorModeDisabled); !disabled {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 25, 2026

Choose a reason for hiding this comment

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

P2: Disabled mode detection misses pointer values (*AccumulatorModeDisabled), so accumulation can be enabled unexpectedly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At client.go, line 190:

<comment>Disabled mode detection misses pointer values (`*AccumulatorModeDisabled`), so accumulation can be enabled unexpectedly.</comment>

<file context>
@@ -186,19 +186,19 @@ func Dial(addr string, opts ...DialOption) (*Client, error) {
-	if _, disabled := batchMode.(BatchModeDisabled); !disabled {
-		c.batcher = newBatcher(svc, batchMode)
+	// Start accumulator unless disabled.
+	if _, disabled := accMode.(AccumulatorModeDisabled); !disabled {
+		c.accumulator = newAccumulator(svc, accMode)
 	}
</file context>
Suggested change
if _, disabled := accMode.(AccumulatorModeDisabled); !disabled {
disabled := false
switch accMode.(type) {
case AccumulatorModeDisabled, *AccumulatorModeDisabled:
disabled = true
}
if !disabled {
Fix with Cubic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 6e16884 (PR #5). Switched to type switch covering both AccumulatorModeDisabled and *AccumulatorModeDisabled.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the update! Noted that it’s handled via a type switch covering both value and pointer types.

}

// Enqueue one or more messages.
message EnqueueRequest {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 25, 2026

Choose a reason for hiding this comment

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

P2: Field numbers 2 and 3 from the old EnqueueRequest (and similarly in AckRequest, NackRequest, ConsumeResponse) are removed without reserved directives. If a future edit accidentally reuses these numbers, old serialized data will be silently misinterpreted. Add reserved 2, 3; to guard against that.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At proto/fila/v1/service.proto, line 25:

<comment>Field numbers 2 and 3 from the old `EnqueueRequest` (and similarly in `AckRequest`, `NackRequest`, `ConsumeResponse`) are removed without `reserved` directives. If a future edit accidentally reuses these numbers, old serialized data will be silently misinterpreted. Add `reserved 2, 3;` to guard against that.</comment>

<file context>
@@ -8,57 +8,137 @@ import "fila/v1/messages.proto";
 }
 
+// Enqueue one or more messages.
+message EnqueueRequest {
+  repeated EnqueueMessage messages = 1;
+}
</file context>
Fix with Cubic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 6e16884 (PR #5). Added reserved directives for removed field numbers in EnqueueRequest, ConsumeResponse, AckRequest, and NackRequest.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for addressing this—adding reserved numbers for removed fields helps prevent accidental reuse.

Comment thread accumulator.go
drain:
for {
select {
case item := <-a.ch:
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 25, 2026

Choose a reason for hiding this comment

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

P2: runAuto has no cap on the number of items drained into a single batch, unlike runLinger which respects maxSize. Under high throughput the entire 4096-item channel buffer can be flushed in one RPC, which risks exceeding gRPC's default 4 MB max message size and failing with ResourceExhausted. Consider adding a maxSize cap to the drain loop, consistent with runLinger.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At accumulator.go, line 124:

<comment>`runAuto` has no cap on the number of items drained into a single batch, unlike `runLinger` which respects `maxSize`. Under high throughput the entire 4096-item channel buffer can be flushed in one RPC, which risks exceeding gRPC's default 4 MB max message size and failing with `ResourceExhausted`. Consider adding a `maxSize` cap to the drain loop, consistent with `runLinger`.</comment>

<file context>
@@ -0,0 +1,266 @@
+	drain:
+		for {
+			select {
+			case item := <-a.ch:
+				batch = append(batch, item)
+			default:
</file context>
Fix with Cubic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 6e16884 (PR #5). Added maxAutoBatchSize (1000) cap to the drain loop, consistent with runLinger's maxSize behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for addressing this and aligning runAuto with runLinger’s behavior!

Comment thread enqueue.go
Comment on lines +71 to 81
results := make([]EnqueueManyResult, len(resp.Results))
for i, r := range resp.Results {
switch v := r.Result.(type) {
case *filav1.BatchEnqueueResult_Success:
results[i] = BatchEnqueueResult{MessageID: v.Success.MessageId}
case *filav1.BatchEnqueueResult_Error:
results[i] = BatchEnqueueResult{Err: &BatchItemError{Message: v.Error}}
case *filav1.EnqueueResult_MessageId:
results[i] = EnqueueManyResult{MessageID: v.MessageId}
case *filav1.EnqueueResult_Error:
results[i] = EnqueueManyResult{Err: enqueueErrorToItemError(v.Error)}
default:
results[i] = BatchEnqueueResult{Err: &BatchItemError{Message: "unknown result type"}}
results[i] = EnqueueManyResult{Err: &ItemError{Message: "unknown result type"}}
}
}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 25, 2026

Choose a reason for hiding this comment

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

P2: EnqueueMany does not validate that response result count matches input message count, so missing per-item results are silently dropped.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At enqueue.go, line 71:

<comment>`EnqueueMany` does not validate that response result count matches input message count, so missing per-item results are silently dropped.</comment>

<file context>
@@ -11,66 +12,116 @@ import (
 	}
 
-	results := make([]BatchEnqueueResult, len(resp.Results))
+	results := make([]EnqueueManyResult, len(resp.Results))
 	for i, r := range resp.Results {
 		switch v := r.Result.(type) {
</file context>
Suggested change
results := make([]EnqueueManyResult, len(resp.Results))
for i, r := range resp.Results {
switch v := r.Result.(type) {
case *filav1.BatchEnqueueResult_Success:
results[i] = BatchEnqueueResult{MessageID: v.Success.MessageId}
case *filav1.BatchEnqueueResult_Error:
results[i] = BatchEnqueueResult{Err: &BatchItemError{Message: v.Error}}
case *filav1.EnqueueResult_MessageId:
results[i] = EnqueueManyResult{MessageID: v.MessageId}
case *filav1.EnqueueResult_Error:
results[i] = EnqueueManyResult{Err: enqueueErrorToItemError(v.Error)}
default:
results[i] = BatchEnqueueResult{Err: &BatchItemError{Message: "unknown result type"}}
results[i] = EnqueueManyResult{Err: &ItemError{Message: "unknown result type"}}
}
}
results := make([]EnqueueManyResult, len(messages))
for i := range messages {
if i >= len(resp.Results) {
results[i] = EnqueueManyResult{Err: fmt.Errorf("enqueue: server returned fewer results than messages sent")}
continue
}
r := resp.Results[i]
switch v := r.Result.(type) {
case *filav1.EnqueueResult_MessageId:
results[i] = EnqueueManyResult{MessageID: v.MessageId}
case *filav1.EnqueueResult_Error:
results[i] = EnqueueManyResult{Err: enqueueErrorToItemError(v.Error)}
default:
results[i] = EnqueueManyResult{Err: &ItemError{Message: "unknown result type"}}
}
}
Fix with Cubic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 6e16884 (PR #5). EnqueueMany now allocates results based on input message count and fills missing slots with explicit error messages.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for addressing this in PR #5—allocating by input size and filling missing results with errors will prevent silent drops.

@vieiralucas vieiralucas merged commit 6d304a2 into main Mar 25, 2026
2 of 3 checks passed
vieiralucas added a commit that referenced this pull request Mar 25, 2026
- client.go: handle both value and pointer AccumulatorModeDisabled in
  type assertion to prevent accumulator from starting unexpectedly
- accumulator.go: cap runAuto drain loop at maxAutoBatchSize (1000) to
  prevent exceeding gRPC 4MB max message size under high throughput
- enqueue.go: validate EnqueueMany response result count matches input
  message count, fill missing results with explicit errors
- proto: add reserved directives for removed field numbers in
  EnqueueRequest, ConsumeResponse, AckRequest, NackRequest to prevent
  accidental field number reuse
vieiralucas added a commit that referenced this pull request Mar 25, 2026
fix: address cubic review findings from PR #4
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