Skip to content

Add PublishAsync to Publisher for non-blocking message publishing#93

Merged
Gsantomaggio merged 8 commits intomainfrom
publish-async
Apr 8, 2026
Merged

Add PublishAsync to Publisher for non-blocking message publishing#93
Gsantomaggio merged 8 commits intomainfrom
publish-async

Conversation

@Gsantomaggio
Copy link
Copy Markdown
Member

@Gsantomaggio Gsantomaggio commented Mar 30, 2026

Introduce PublishAsync(ctx, message, callback) on Publisher as a fire-and-forget alternative to Publish. The send itself is synchronous (SendWithReceipt), but the broker-confirmation wait (SendReceipt.Wait) runs in a background goroutine and delivers its outcome via a PublishAsyncCallback.

Back-pressure is enforced through a channel-based semaphore (inFlight) whose capacity is controlled by PublisherOptions.MaxInFlight (default 256): PublishAsync blocks the caller until a slot is available or the context is cancelled. Each confirmation goroutine respects a configurable PublishTimeout (default 10 s) before surfacing a timeout error through the callback.

Also add integration tests covering the happy path, bulk sending, MaxInFlight throttling, context-cancellation back-pressure, validation errors, StateReleased outcomes, and custom timeouts, together with a runnable example in docs/examples/publish_async/.

Introduce PublishAsync(ctx, message, callback) on Publisher as a
fire-and-forget alternative to Publish. The send itself is synchronous
(SendWithReceipt), but the broker-confirmation wait (SendReceipt.Wait)
runs in a background goroutine and delivers its outcome via a
PublishAsyncCallback.

Back-pressure is enforced through a channel-based semaphore (inFlight)
whose capacity is controlled by PublisherOptions.MaxInFlight (default
256): PublishAsync blocks the caller until a slot is available or the
context is cancelled. Each confirmation goroutine respects a configurable
PublishTimeout (default 30 s) before surfacing a timeout error through
the callback.

Also add integration tests covering the happy path, bulk sending,
MaxInFlight throttling, context-cancellation back-pressure, validation
errors, StateReleased outcomes, and custom timeouts, together with a
runnable example in docs/examples/publish_async/.

Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
@Gsantomaggio Gsantomaggio mentioned this pull request Mar 30, 2026
Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
@Gsantomaggio Gsantomaggio marked this pull request as ready for review April 7, 2026 19:26
@Gsantomaggio Gsantomaggio self-assigned this Apr 7, 2026
@Gsantomaggio Gsantomaggio added the enhancement New feature or request label Apr 7, 2026
@Gsantomaggio Gsantomaggio added this to the 1.0.0 milestone Apr 7, 2026
Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
@Gsantomaggio Gsantomaggio requested a review from Copilot April 8, 2026 08:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new PublishAsync(ctx, message, callback) API to Publisher to allow non-blocking publish confirmations with bounded in-flight concurrency and a configurable confirmation timeout, plus supporting docs/examples and test coverage.

Changes:

  • Introduce PublishAsync with MaxInFlight back-pressure (semaphore) and PublishTimeout confirmation wait timeout.
  • Extend publisher options/types with defaults and callback type, plus lifecycle doc improvements.
  • Add integration tests and runnable docs utilities (example + perf harness), and wire vet/staticcheck for the new docs code.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pkg/rabbitmqamqp/life_cycle.go Moves lifecycle state constants earlier and adds extended StateChanged documentation.
pkg/rabbitmqamqp/amqp_types.go Adds publisher async options defaults + PublishAsyncCallback and option accessors.
pkg/rabbitmqamqp/amqp_publisher.go Implements PublishAsync with in-flight semaphore and timeout-based receipt waits.
pkg/rabbitmqamqp/amqp_publisher_test.go Adds integration tests covering async publish behavior and throttling/cancellation.
docs/perf_test/main.go Adds a perf harness supporting both Publish and PublishAsync.
docs/examples/publish_async/main.go Adds a runnable example demonstrating PublishAsync usage and tuning.
README.md Documents the new perf test harness and the two publish modes.
Makefile Includes docs/perf_test in vet/staticcheck.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/rabbitmqamqp/amqp_publisher.go Outdated
Comment thread pkg/rabbitmqamqp/amqp_types.go Outdated
Comment thread pkg/rabbitmqamqp/amqp_publisher.go Outdated
Comment thread docs/perf_test/main.go
if err = env.CloseConnections(context.Background()); err != nil {
fmt.Fprintf(os.Stderr, "%sclose:%s %v\n", colorRed, colorReset, err)
}
close(stateChanged)
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

stateChanged is passed to NotifyStatusChange, and the library likely sends to that channel. Closing a channel from the receiver side can panic if the sender attempts to write after close (including during/after connection shutdown). Don’t close stateChanged here; instead, signal your logging goroutine to stop via a separate done channel/context, or simply let the process exit without closing it.

Suggested change
close(stateChanged)

Copilot uses AI. Check for mistakes.

rmq.Info("AMQP connection closed")
time.Sleep(100 * time.Millisecond)
close(stateChanged)
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

Same receiver-side channel close issue as in docs/perf_test: NotifyStatusChange likely writes to stateChanged, and closing it here can trigger a send-on-closed-channel panic. Avoid closing this channel from the example; stop the logger goroutine via context/done signaling instead.

Suggested change
close(stateChanged)
// Do not close stateChanged here: notification senders may still write to it.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +34
const (
queueName = "publish-async-go-queue"
totalMessages = 10_000_000
maxInFlight = 100
publishTimeout = 10 * time.Second
)
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

totalMessages = 10_000_000 makes this “example” effectively a long-running load test (and WaitGroup.Add(10_000_000) can be heavy). Consider reducing this to a smaller, runnable-by-default count (e.g., 1k–10k) and/or making it configurable via flags/env so the example is practical for quick verification.

Copilot uses AI. Check for mistakes.
Comment thread pkg/rabbitmqamqp/life_cycle.go Outdated
Comment thread pkg/rabbitmqamqp/amqp_publisher.go Outdated
Gsantomaggio and others added 3 commits April 8, 2026 10:29
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Gsantomaggio and others added 2 commits April 8, 2026 10:31
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Gsantomaggio Gsantomaggio merged commit 6259533 into main Apr 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants