Skip to content

Changelog ​

Snake ​

中文

All version change logs. Format based on Keep a Changelog.

[0.0.5] - 2026-08-26 ​

Added ​

  • Automatic WorkerID derivation now logs the derived workerID and its source IP at startup for collision troubleshooting

Changed ​

  • On millisecond rollover the sequence now starts from a random offset instead of 0, so replicas whose auto-derived WorkerIDs collide no longer emit duplicate IDs within the same millisecond

Fixed ​

  • Generator now packs (timestamp, sequence) into a single 64-bit atomic word and CASes it as a whole, eliminating the in-process duplicate-ID race where the old two-step atomics let the timestamp advance before the sequence was reset
  • Validate() now rejects a SequenceBits whose packed state (timestamp << SequenceBits) would overflow the 63-bit atomic word used by the new Generator

[0.0.4] - 2026-06-19 ​

Added ​

  • Validate() now checks bit width: returns an error when WorkerIDBits + SequenceBits exceeds 63 to avoid timestamp field overflow
  • Added full unit tests with 97.8% coverage (concurrent uniqueness, clock backward, sequence overflow, bit-width boundaries, etc.)
  • Added CI workflow (.github/workflows/ci-snake.yml) integrating Codecov coverage reporting

Improved ​

  • Removed two pieces of dead code in CalculateWorkerID (fnv.Write never returns an error; the modulo operation guarantees workerID stays within the valid range)

[0.0.3] - 2026-06-04 ​

Dependencies ​

  • github.com/zeromicro/go-zero v1.10.0 → v1.10.2
  • go.opentelemetry.io/otel* v1.24.0 → v1.40.0 (indirect; intentionally not upgraded to v1.44+ to avoid forcing go 1.25)
  • Synced go mod tidy to clean up unused indirect dependencies
  • go directive remains 1.24.0

[0.0.2] - 2026-03-20 ​

  • Upgraded Go version to 1.24.0
  • Upgraded go-zero to v1.10.0
  • Updated dependencies

[0.0.1] - 2026-01-26 ​

  • Initial project release

Cron ​

中文

All version change logs. Format based on Keep a Changelog.

[0.1.2] - 2026-08-06 ​

Changed ​

  • Scheduled task deduplication moved from a fixed TaskID to asynq.Unique: the previous approach used the task type as a fixed TaskID, while asynq decides an ID conflict purely by whether the task key exists — and the lifetime of that key is decided by how the previous run ended (deleted on success, kept for the retention window when asynq.Retention is passed, 90 days by default once archived, the run duration while still active). None of those equals the schedule period. CronAdd now injects asynq.Unique(TTL) instead: underneath it is a plain Redis SET NX EX lock whose key is derived from the queue name and the task type, and whose TTL is a relative duration that lines up with the period by construction.
    • The TTL is derived from the spec and the effective timeout: TTL = max(shortest trigger interval − 1s, effective timeout, 1s); callers do not have to provide it
    • TTL ≥ effective timeout guarantees that no second copy can be enqueued while the previous run is still going; TTL < shortest trigger interval (when the timeout is smaller than the interval) guarantees that a single failure blocks less than one period
    • The effective timeout is read back from asynq.Timeout in opts; when it is absent, asynq's 30 minute default is assumed
    • Behaviour change: the overlap protection window goes from "as long as the previous task exists" (up to 90 days) down to a single TTL. When the timeout is not smaller than the trigger interval the lock outlives the next tick and that tick is skipped, which is exactly what "a run may take longer than the period" has to mean
    • An explicitly passed asynq.Unique is overridden

Fixed ​

  • A single failed run no longer stops a scheduled task forever: archived tasks no longer take part in the deduplication decision, and the dedup lock carries its own bounded expiry (asynq does not clear that lock when it archives a task), so a failure blocks for at most one TTL and the next tick recovers
  • Enqueue failures are no longer silent: SchedulerOpts.PostEnqueueFunc only counted successful enqueues and EnqueueErrorHandler was not set, so asynq returned without a single log line or metric when a tick failed to enqueue. Failures are now logged and counted
  • CronAdd no longer writes into the spare capacity of the caller's opts: the queue and dedup options used to be appended straight onto the incoming opts, which reaches into the caller's backing array whenever the caller passes opts... and that slice has spare capacity. The options are now copied before anything is appended. Plain variadic calls (including the workers.go generated by cztctl) have cap == len and were never affected

Added ​

  • Metric cron_server_scheduler_enqueue_error_total{task_type, reason}: number of failed enqueues at tick time. reason=duplicate is the normal outcome of a multi-replica deployment (replicas−1 times per tick) and is only recorded as a debug log; reason=other (for example Redis being unavailable) is reported loudly and is the only entry point for detecting such failures

Dependencies ​

  • github.com/zeromicro/go-zero v1.10.2 → v1.10.3
  • github.com/redis/go-redis/v9 v9.20.0 → v9.21.0 (raised by the minimum requirement of go-zero v1.10.3)
  • github.com/robfig/cron/v3 v3.0.1 promoted from indirect to direct: used to parse the spec and derive the shortest trigger interval, with the same parser asynq's Scheduler uses internally
  • github.com/alicebob/miniredis/v2 v2.38.0 promoted from indirect to direct: used directly by the unit tests
  • go mod tidy dropped 22 indirect dependencies that are no longer needed (grpc, grpc-gateway, otel exporters, pyroscope, zipkin and others)
  • Intentionally kept go.opentelemetry.io/otel* at v1.40.0 (v1.44+ forces go 1.25; not upgraded for compatibility)
  • go directive remains 1.24.0

[0.1.1] - 2026-06-04 ​

Dependencies ​

  • github.com/zeromicro/go-zero v1.10.0 → v1.10.2
  • github.com/redis/go-redis/v9 v9.17.3 → v9.20.0
  • Synced go mod tidy to clean up unused indirect dependencies
  • Intentionally kept go.opentelemetry.io/otel* at v1.40.0 (v1.44+ forces go 1.25; not upgraded for compatibility)
  • go directive remains 1.24.0

[0.1.0] - 2026-06-04 ​

Breaking Changes ​

  • CronAdd signature adjustment: added required parameter handler HandlerFunc; completes scheduler registration and handler registration in one step, avoiding repeating the same task type in both CronAdd and Add
    • Old usage: CronAdd(spec, pattern, opts...) + Add(pattern, handler)
    • New usage: CronAdd(spec, pattern, handler, opts...)
  • Scheduled task timeout now managed by go-zero RestConf.Timeout: before v0.1.0, scheduled tasks did not inject a timeout and fell back to the asynq default hard-coded constant of 30 minutes; from v0.1.0, it is recommended to inject serverCtx.Config.Timeout (go-zero rest.RestConf.Timeout) into CronAdd via asynq.Timeout(d) in workers.go, so that the ctx received by the handler carries a Deadline, unified with go-zero HTTP timeout management
    • The cztctl template will be updated in subsequent versions; when upgrading, business teams need to manually adjust workers.go. See the migration guide for details

Migration Guide ​

API Signature Migration

diff
- serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB")
- serverCtx.CronServer.Add("GDemoB", demoA.GDemoBHandler(serverCtx))
+ serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB", demoA.GDemoBHandler(serverCtx))

Integrate go-zero Timeout Management

diff
+ import (
+     "time"
+     "github.com/hibiken/asynq"
+ )
+
  func RegisterHandlers(server *service.ServiceGroup, serverCtx *svc.ServiceContext) {
+     var taskOpts []asynq.Option
+     taskOpts = append(taskOpts,
+         asynq.Timeout(time.Duration(serverCtx.Config.Timeout)*time.Millisecond),
+         asynq.MaxRetry(0),
+     )
-     serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB", demoA.GDemoBHandler(serverCtx))
+     serverCtx.CronServer.CronAdd("*/1 * * * *", "GDemoB",
+         demoA.GDemoBHandler(serverCtx), taskOpts...)
      server.Add(serverCtx.CronServer)
  }

rest.RestConf.Timeout defaults to 3000ms, which is short for IO-intensive scheduled tasks; it is recommended to explicitly increase it in yaml (e.g., Timeout: 30000).

Added ​

  • CronAdd / Client.Push* can set an arbitrary timeout per task via the asynq.Timeout(d) opt (not limited by the asynq default 30 minutes), e.g., CronAdd("*/1 * * * *", "X", h, asynq.Timeout(time.Hour))
  • README advanced guide added the "Task Timeout Control" section, detailing the timeout injection mechanism for scheduled tasks and pushed tasks

Fixed ​

  • ExponentialRetryDelay formula corrected to 2^(n+1) - 1: the n passed by asynq is the number of retries already performed (0-indexed); the original formula 2^n - 1 returned 0s on the first retry, causing no backoff on the first retry; after correction the retry sequence is stable at 1s, 3s, 7s, 15s, 31s, ...

[0.0.9] - 2026-06-04 ​

Added ​

  • Added SetBaseContext(ctx context.Context) method, supporting injection of a base context; all task handler ctxs will use this as parent

Changed ​

  • asynq.Server is created lazily at Start(), supporting dynamic context configuration after construction and before startup

[0.0.8] - 2026-06-04 ​

Added ​

  • Added GetTaskID, GetRetryCount, GetMaxRetry, GetQueueName wrapper functions; the handler layer can obtain task metadata without directly depending on asynq

Changed ​

  • README.md fully refactored: full examples organized by go-zero / standalone scripts + Server / Client quadrants
  • Advanced guide added task group aggregation and skip retry usage examples
  • Added asynq.SkipRetry wrapping notes (pkg/errors incompatible)
  • Metrics section heading levels corrected

[0.0.7] - 2026-06-04 ​

Added ​

  • Added WithGroupAggregator ServerOption, supporting injection of a task group aggregator to make Group grouping available
  • Added WithRetryDelayFunc ServerOption, supporting custom retry delay strategy
  • Added built-in retry strategy ExponentialRetryDelay (2^n - 1 seconds: 1s, 3s, 7s, 15s, 31s...)
  • CronAdd automatically uses realPattern as TaskID so scheduled tasks are not duplicated across multi-instance deployments

Fixed ​

  • Removed dead code validation for GroupGracePeriod in ServerConfig.Validate() (int64 cannot be >0 and <1)

Changed ​

  • GroupMaxDelay default changed from 0 (unlimited) to 300 seconds (5 minutes), preventing tasks from never aggregating when they keep arriving
  • Server default retry strategy changed from asynq built-in n⁴ exponential backoff to ExponentialRetryDelay (2^n - 1 seconds)
  • Add logs unified to [CRON] Worker registered: %s

[0.0.6] - 2026-05-20 ​

Changed ​

  • Replaced log.Printf with logx.Errorf in metrics.go to unify with go-zero logging conventions
  • Removed LoggingMiddleware to avoid duplication with asynq internal error logs
  • Removed redundant logc.Errorf in RecoveryMiddleware; asynq perform already has built-in panic recovery logging

[0.0.5] - 2026-05-15 ​

Added ​

  • Added full metrics system
    • Server side: cron_server_consume_total, cron_server_consume_duration_ms, cron_server_consume_bytes, cron_server_active_workers, cron_server_retry_total, cron_server_skip_retry_total, cron_server_panic_total, queue_groups, tasks_aggregating_total, cron_scheduler_trigger_total, cron_scheduler_registered, cron_tasks_enqueued_total, cron_queue_size, cron_queue_latency_seconds
    • Client side: cron_client_push_total, cron_client_push_duration_ms, cron_client_push_bytes, cron_client_cancel_total
  • Added queue whitelist filtering to avoid metric mixing when multiple services share Redis
  • Client defaults to no retries (MaxRetry=0); users can override via opts

Changed ​

  • Metrics namespace changed from asynq_ to cron_
  • Server-side metrics added server subsystem

[0.0.4] - 2026-04-10 ​

Changed ​

  • Upgraded Go version to 1.24.0
  • Upgraded go-zero to v1.10.0
  • Updated dependencies

ConfigCenter (Consul) ​

中文

All notable changes to this project are recorded here. Format based on Keep a Changelog.

[0.1.2] - 2026-06-04 ​

Dependencies ​

  • github.com/zeromicro/go-zero v1.10.0 → v1.10.2
  • Run go mod tidy to clean up unused indirect dependencies
  • Pin github.com/hashicorp/consul/api to v1.25.1 (v1.33+ requires go 1.25; pinned for go 1.24 compatibility)
  • go directive kept at 1.24.0

[0.1.1] - 2026-03-20 ​

  • Upgrade Go version to 1.24.0
  • Upgrade go-zero to v1.10.0
  • Update dependency versions

[0.1.0] - 2025-11-26 ​

  • Initial release

RegisterCenter (Consul) ​

中文

All notable changes to this project are recorded here. Format based on Keep a Changelog.

[0.1.7] - 2026-06-04 ​

Dependencies ​

  • github.com/zeromicro/go-zero v1.10.0 → v1.10.2
  • google.golang.org/grpc v1.65.0 → v1.80.0
  • go.opentelemetry.io/otel/trace v1.24.0 → v1.40.0 (indirect)
  • Run go mod tidy to clean up unused indirect dependencies
  • Pin github.com/hashicorp/consul/api to v1.25.1 (v1.33+ requires go 1.25; pinned for go 1.24 compatibility)
  • go directive kept at 1.24.0

[0.1.6] - 2026-03-20 ​

  • Upgrade Go version to 1.24.0
  • Upgrade go-zero to v1.10.0
  • Update dependency versions

[0.1.5] - 2025-12-01 ​

  • Optimization release

[0.1.3] - 2025-12-01 ​

  • Optimization release

[0.1.2] - 2025-12-01 ​

  • Optimization release

[0.1.1] - 2025-12-01 ​

  • Optimization release

[0.1.0] - 2025-12-01 ​

  • Initial release

RabbitMQ ​

中文

All version change logs. Format based on Keep a Changelog.

[0.2.0] - 2026-08-03 ​

A whole-module redesign. 0.1.6 was never released and its content is folded into this version.

Every new configuration field has a default, so existing configurations in your config center need no changes to upgrade.

Breaking Changes ​

1. Interceptors unified into middleware ​

Interceptor / SenderInterceptor / SenderFunc / SenderChain are removed in favour of a single Meta / Handler / Middleware / Chain set:

diff
-type Interceptor func(ctx context.Context, queueName string, message []byte, next func(context.Context, []byte) error) error
-type SenderInterceptor func(ctx context.Context, exchange, routeKey string, msg []byte, next SenderFunc) error
+type Handler func(ctx context.Context, meta Meta, body []byte) error
+type Middleware func(next Handler) Handler

Note: Chain and SenderChain were logically identical and had been copied only because their signatures differed. Once Meta describes the message coordinates uniformly (the consumer side fills Queue, the publisher side Exchange and RouteKey), the two isomorphic sets collapse into one. There is no third-party user, so the breakage is limited to custom interceptors.

WithConsumeMiddlewares / WithPublishMiddlewares are added as injection points: the interceptor chain used to be hard-coded inside the constructor with no way for callers to append.

2. Send blocks for the confirm, and errors are classified into five sentinels ​

go
err := sender.Send(ctx, exchange, routeKey, body)
switch {
case errors.Is(err, rabbitmq.ErrPublishUnroutable): // wrong routeKey or missing binding
case errors.Is(err, rabbitmq.ErrPublishNacked):     // broker refused; already retried internally
case errors.Is(err, rabbitmq.ErrPublishTimeout):    // unknown whether the broker got it
case errors.Is(err, rabbitmq.ErrPublishUnknown):    // channel closed before the confirm arrived; also unknown
case errors.Is(err, rabbitmq.ErrSenderClosed):      // the sender is closed
}

Note: previously PublishWithContext returning nil only meant the frame was written to the socket, so the [RABBITMQ_SEND_OK] log did not hold. Now nil means the broker has accepted the message. The cost is higher latency and lower throughput, compensated by PoolSize.

The sentinels are cut along a single line: whether the outcome is known. ErrPublishNacked and ErrPublishUnroutable both carry a complete response from the broker, so the framework can act on them — the former is retried with backoff, the latter is not, because the result would be identical. ErrPublishTimeout and ErrPublishUnknown never got a confirm, and the message may or may not have landed; neither is retried, because retrying would duplicate the message behind the caller's back and only the business knows whether that is acceptable. ErrPublishUnknown is kept distinct from ErrPublishTimeout despite sharing that fate, because the two point in opposite directions when investigating: a timeout points at PublishTimeout and broker load, a closed channel points at network and connection stability. Folding them together would send a rising timeout metric chasing the wrong fix.

3. Publishing defaults to persistent + mandatory ​

Note: the old implementation never set DeliveryMode, while QueueConf.Durable defaults to true — the classic half-durable trap: after a broker restart the durable queue survives but every message is gone. And mandatory: false let a mistyped routeKey be silently dropped by the broker while the confirm still returned an ack. A routeKey matching no queue now returns ErrPublishUnroutable instead of silently succeeding.

4. ConsumerConf.NoLocal, ChannelQosConf.Global and RabbitListenerConf.ContentType removed ​

diff
  ChannelQos:
    PrefetchCount: 10
-   Global: false
  ListenerQueues:
    - Name: demoA
-     NoLocal: true

Note: RabbitMQ never implemented no-local; the broker silently ignores the flag. Keeping it was in fact more dangerous: the application believes "consuming my own published messages is disabled" while it still receives them. Global was equally illusory: the flag only scopes the prefetch allowance within one channel (per-consumer vs. shared across the channel) and never across channels, so with one dedicated channel and exactly one consumer per queue, both values resolved to the same allowance — a knob that looked like it tuned flow control but could not. Qos now always passes false. ContentType was a dead field in the consumer config. After removal the extra yaml keys are ignored, so existing configurations are unaffected.

5. Admin gains NewAdmin and Close ​

diff
-admin := rabbitmq.MustNewAdmin(conf)
+admin, err := rabbitmq.NewAdmin(conf)
+if err != nil {
+    return err
+}
+defer admin.Close()

Note: the old implementation had no Close, leaking one TCP connection per call, and log.Fatalf-ed when opening the channel failed, leaving the connection open. MustNewAdmin is kept.

6. MustNewListener returns the module's own Listener interface ​

diff
-func MustNewListener(c RabbitListenerConf, handler ConsumeHandler) queue.MessageQueue
+func MustNewListener(c RabbitListenerConf, handler ConsumeHandler, opts ...ListenerOption) Listener

Note: Listener's method set (Start() / Stop()) is identical to go-zero's queue.MessageQueue, so it can still be handed straight to service.ServiceGroup and neither listeners.go nor ServiceContext needs a change. The point is to drop the dependency on a go-zero type from the public signature: this module uses nothing else from the queue package, and exposing that type suggests go-zero queue semantics that are not there. NewListener is added alongside, matching NewSender — the old implementation offered only the Must variant, so a configuration error could surface only as a panic.

7. The trace context moves from the message envelope to AMQP headers ​

RabbitMsgBody is removed; Meta gains Headers:

diff
-type RabbitMsgBody struct {
-	Carrier *propagation.HeaderCarrier // OpenTelemetry trace headers
-	Msg     []byte                     // business payload
-}
+type Meta struct {
+	Queue    string
+	Exchange string
+	RouteKey string
+	Headers  map[string]any // the trace context travels here
+}

The publisher injects the trace context into Meta.Headers, which ends up as amqp.Publishing.Headers; the consumer extracts it from amqp.Delivery.Headers. Neither end touches the body any more, so rabbitmq_listener_parse_error_total goes away with the envelope — nothing is left that could fail to parse.

Note: jamming the trace context into the body is what kept this module from interoperating with anything but itself. Carrying it in the header table instead — the way the OpenTelemetry messaging convention prescribes for RabbitMQ — settles four things at once: messages published by other systems become consumable (see the corresponding fix below), the body stops paying for an extra layer of JSON plus the 33% base64 inflation encoding/json applies to a []byte field, traces genuinely continue across language boundaries, and message bodies are legible in the management UI.

This is a wire-format change. A 0.2.0 consumer does not unwrap a 0.1.x envelope: it hands the envelope JSON to the handler as-is. Publishers and consumers sharing a queue therefore need to be upgraded together, and the queue should be drained of envelope-shaped messages first.

8. Connection metrics gain endpoint and vhost labels ​

rabbitmq_sender_reconnect_total, rabbitmq_sender_disconnect_total, rabbitmq_listener_reconnect_total and rabbitmq_listener_disconnect_total carried no labels at all; they now carry endpoint (host:port) and vhost. The two pool metrics introduced in this version (rabbitmq_sender_pool_wait_duration_ms, rabbitmq_sender_pool_in_use) carry the same pair.

Why: one process can hold several Senders and Listeners pointed at different clusters, and a label-less counter piles them all onto one curve — when an alert fires there is no telling which broker is flapping. vhost falls back to the AMQP default / when unset, so one vhost written two ways does not split into two series. Neither label carries credentials: metrics get scraped into a monitoring system and kept for a long time.

This changes the cardinality of existing series. Dashboards and alert rules querying those four metrics need an aggregation added (sum without (endpoint, vhost) (...) or equivalent), otherwise what used to be a single curve becomes one curve per broker.

Added ​

  • Per-queue consume concurrency: ConsumerConf.Concurrency (default 1, capped at 256). 1 means strict ordering; greater than 1 starts that many workers processing in parallel. Requires Concurrency <= ChannelQos.PrefetchCount
  • Processing timeout: ConsumerConf.Timeout (default 0, unlimited) and the WithConsumeTimeout(d) option. The old middleware chain rooted at a bare context.Background() with no deadline anywhere. When hosted by go-zero, inject the host's RestConf.Timeout explicitly in the handler
  • TLS: RabbitConf.TLS, one-way and mutual authentication. The old implementation hard-coded Scheme: "amqp"
  • Heartbeat: RabbitConf.Heartbeat (default 30 seconds), to detect dead links and trigger reconnection
  • Publish tuning: RabbitSenderConf gains PoolSize(10) / PublishTimeout(5000) / MaxRetry(3)
  • Graceful shutdown bound: RabbitListenerConf.GracefulTimeout (default 10000 ms)
  • Publish-side graceful shutdown bound: RabbitSenderConf.GracefulTimeout (default 10000 ms), symmetric with the listener-side field — the bound Close waits for in-flight publishes to be confirmed by the broker
  • Middleware injection: WithConsumeMiddlewares(...) on the consumer side and WithPublishMiddlewares(...) on the publisher side, both appended after the built-in chain and therefore bounded by the timeout. The publisher side sits outside the retry orchestration, so it runs exactly once per Send rather than once per attempt
  • Publisher-side option: WithPublishTimeout(d), symmetric with WithConsumeTimeout(d), letting the host framework inject a timeout uniformly; it takes precedence over RabbitSenderConf.PublishTimeout
  • Five metrics: rabbitmq_sender_confirm_duration_ms, rabbitmq_sender_pool_wait_duration_ms, rabbitmq_sender_pool_in_use, rabbitmq_sender_retry_total, rabbitmq_listener_in_flight
  • Tests and CI: unit tests (all Validate branches, Chain composition order, the timeout middleware, state machine transitions, channel pool checkout/return), integration tests (//go:build integration, real broker), and .github/workflows/ci-rabbitmq.yml. This module used to be the only one without CI

Fixed ​

  • Concurrent Send sharing one channel: amqp091-go's Channel is not thread-safe, yet every concurrent publish shared a single channel, so interleaved protocol frames got the channel closed by the broker. Publishing now goes through a channel pool where each channel handles a single in-flight message at a time — NotifyReturn carries no deliveryTag, which is also the prerequisite for attributing an unroutable return correctly
  • Data race on the sender's conn/channel: Send read q.channel and reconnect wrote q.channel = nil with no lock at all, allowing a data race and a nil dereference panic. Both are now guarded by a RWMutex, with the publish path taking only the read lock
  • Sender reconnection could not be interrupted: it used a fixed 10 × 2s busy wait — a second reconnection philosophy alongside the listener's exponential backoff — and could not be interrupted by a shutdown signal. Both now use interruptible exponential backoff
  • Admin leaked connections: see breaking change 5 above
  • WaitGroup cross-generation reuse panic risk: listenerWg/taskWg are now allocated per generation. Previously, once the reconnect wait timed out, the waiter stayed blocked in Wait() while the new generation called Add on the same wg — when the counter hit zero this could trigger sync: WaitGroup misuse and crash the process (trigger: stuck handler + connection flap)
  • Stop worst-case duration doubled: the "wait for old routines" step in reconnect now includes the shutdown signal in its select. It previously used a bare time.After, so a shutdown arriving mid-reconnect had to wait out reconnect's GracefulTimeout before waiting out Stop's own — easily exceeding the shutdown grace period
  • Messages from other publishers were silently dropped: consumerTrace unmarshalled every body as an envelope unconditionally, and a parse failure returned an error — which the "acknowledge anything that has been executed" rule then turned into an ack. A message published by a Java service, or by hand from the management UI, was thus discarded without the handler ever running. The body is no longer parsed at all
  • A panic in the middleware chain itself stalled the queue silently: handle's panic safety net sat outside the acknowledgement decision, so a panic skipped the acknowledgement altogether — the delivery was neither Acked nor Rejected and held on to a prefetch slot forever. After PrefetchCount such messages the queue stopped receiving entirely, with nothing but the panic in the logs and no signal of the stall, until a reconnect or shutdown released it. The exposure is not narrow: business middleware injected via WithConsumeMiddlewares sits outside consumerRecovery, so any user middleware panic took this path. The safety net moved one level down (invoke) and turns the panic into an error in place, so the acknowledgement always runs — consistent with the "acknowledge anything that has been executed" rule
  • ErrPublishUnknown said nothing about why: a closed confirm channel was reported as "outcome unknown" with no cause attached, yet the most common configuration error of all — a mistyped exchange name — arrives exactly this way, as a 404 NOT_FOUND the broker delivers on the channel-close notification alone. pubChannel now registers NotifyClose and appends the broker's code and reason to the error, so the situation that most needs diagnosing is no longer the least diagnosable
  • Shutdown turned in-flight publishes into ErrPublishUnknown: Close tore the connection down without waiting, closing the confirm channel of every publish that had already written its frames — and the shutdown hook is registered by NewSender itself, so every process exit manufactured a batch of unknown outcomes. Close now waits out GracefulTimeout for in-flight publishes to be confirmed before destroying channels, and logs one [RABBITMQ_STOP] line if any are still unconfirmed when the window elapses
  • Send after Close waited out the publish deadline: channel checkout selected only on a free slot and the caller's ctx, so once the pool was closed — slots collected and never returned — the publish sat there until PublishTimeout and then reported a misleading timeout. Checkout now also selects on the pool's shutdown signal and returns ErrSenderClosed immediately

Changed ​

  • Validate unified into three rules, exactly one applied per field: a missing required field or an impossible value returns an error; an invalid value that has a safe default falls back and logs one line; exceeding a protocol or resource limit returns an error. The old implementation mixed "fail" and "silently fall back" arbitrarily
  • The first connection is attempted only once: configuration errors should surface immediately at startup, while runtime connection failures are healed by reconnection. Sender and Listener now agree on this
  • A configured timeout is a backstop that applies only when the caller supplied no deadline: timeoutMiddleware now checks whether the incoming ctx already carries a deadline and passes it through untouched if so, instead of wrapping context.WithTimeout unconditionally. The latter takes the earlier of the parent deadline and the configured value, silently truncating a longer deadline the caller explicitly asked for and turning the configuration from a backstop into a hard ceiling. The consumer side is unaffected because its message ctx is derived by the Listener from context.Background() and carries no deadline; Send is what changes — if the caller's ctx already has a deadline (inside a go-zero rest handler, say), PublishTimeout no longer intervenes
  • Files split by responsibility: conf.go / errors.go / const.go / message.go / middleware.go / metrics.go / conn.go / consumer.go / listener.go / chanpool.go / sender.go / admin.go. The old RabbitListener was a single struct carrying five responsibilities (connection management, channel lifecycle, generation orchestration, message dispatch, acknowledgement decisions) with 4 synchronisation primitives and 3 atomics among its 16 fields
  • The listener lifecycle is now an explicit state machine: stateNew → stateRunning → stateStopping → stateStopped, with generations expressed by layered contexts. State checks used to be scattered across 7 closed.Load() and 4 epoch.Load() call sites

Dependencies ​

  • github.com/rabbitmq/amqp091-go v1.11.0 → v1.13.0
  • go.opentelemetry.io/otel/sdk promoted from an indirect to a direct dependency: the trace-propagation round-trip test drives a real TracerProvider
  • go directive remains 1.24.0
  • example/ is now its own module (example/go.mod, with a replace pointing back at ../): the example depends on the config centre and the registry, and while it shared this module, running go mod tidy in the library directory wrote the consul and etcd dependency trees into this module's go.mod. Now neither go mod tidy nor go build ./... in the library directory touches the example
  • go.mod and go.sum trimmed: indirect requirements drop from 42 to 20, go.sum from 139 to 85 lines. What went is 0.1.x residue that nothing imports any more (gRPC, the Zipkin and OTLP exporters, genproto, pyroscope, gonum, k8s.io/utils and friends). The number of transitive dependencies that actually take part in a build is unchanged (281 packages) — the residue existed only as declarations in go.mod, harmless to the build itself, but enough to make dependency audits and SBOMs count a pile of unrelated packages as this library's surface

Documentation ​

  • README and readme-cn rewritten around the new model, covering publish semantics, consume semantics, timeouts, middleware, the metric catalogue, and the upgrade guide
  • Clarified that PrefetchCount is not a concurrency setting: it is a flow-control parameter (driven by message size and network RTT), while concurrency is set by Concurrency (driven by downstream capacity)
  • Clarified the cost of at-least-once: retrying inevitably duplicates messages, so consumers must be idempotent
  • Clarified that deadlines are cooperative: the handler must pass ctx to downstream blocking calls or the timeout has no effect
  • Clarified the difference in how timeouts apply, caused by the consumer and publisher sides obtaining their ctx from different places

Deviations from the design document ​

Six decisions during implementation differ from the original design proposal:

  • Recovery sits innermost in the middleware chain, whereas the design document places it outermost. Outermost, a panic would pass through metrics and logging, so a panicking message would neither be counted in consume_total nor produce a single log line — and a panic is precisely what most needs to be observed
  • rabbitmq_listener_worker_in_use is not implemented: the value is derivable from rabbitmq_listener_in_flight divided by Concurrency, a separate gauge would only add two more atomic writes to the tail of every message, and two metrics with the same source contradict each other once they drift
  • No separate inflightWg was introduced: the per-generation taskWg already expresses "wait for in-flight messages to finish", and a second waitgroup would blur exactly what shutdown is waiting for — the hardest part of the old implementation to read
  • The middleware injection point was split into a symmetric pair: the design document offered only WithMiddlewares (consumer side only). The publisher side needs an injection point too, and a single name cannot say which side it applies to; the two also differ in execution semantics (the publisher side sits outside the retry orchestration and runs once per Send), which sharing one name would hide
  • The cztctl template was left untouched (design document §8, "cztctl integration"): cztctl's handler template is served from a remote repository, so the in-repo .tpl is not the one actually used at generation time and editing it here would have no effect. Host timeout injection is documented as a snippet for the caller to add explicitly in the handler instead
  • The trace context is carried in AMQP headers, not in a message envelope: the design document keeps the envelope (message.go's responsibilities, and both trace middlewares, are described in terms of wrapping and unwrapping it). The envelope turned every consumed message into a JSON parse, and a body that was not an envelope got acknowledged away without ever reaching the handler. Its supposed cost — "envelope JSON parsing is not bounded by the deadline, which is acceptable because parsing is pure CPU and bounded" — is moot now that no parsing happens

Migration ​

  • No change needed: existing config-center configuration, listeners.go (a generated file), ServiceContext
  • Change needed: custom interceptors, if any; Send callers that need to branch by error type; MustNewAdmin call sites need a defer admin.Close(); publishers and consumers sharing a queue must be upgraded together, because the trace context moved out of the message body (breaking change 7); dashboards and alert rules querying the reconnect / disconnect metrics need an aggregation added (breaking change 8)
  • Recommended: inject the host timeout into existing handlers, i.e. add the rabbitmq.WithConsumeTimeout(...) line at the MustNewListener call by hand (and "time" to the imports). cztctl's handler template is served from a remote repository and is out of this module's scope; besides, handler files are skipped if they already exist during generation, so existing services have to add it by hand either way
  • Caution: rest.RestConf.Timeout defaults to only 3000ms. Once injected, message processing is implicitly capped at 3 seconds, so consumers usually need to raise Timeout explicitly in the yaml

[0.1.5] - 2026-06-04 ​

Dependencies ​

  • github.com/rabbitmq/amqp091-go v1.10.0 → v1.11.0
  • github.com/zeromicro/go-zero v1.10.0 → v1.10.2
  • go.opentelemetry.io/otel* v1.24.0 → v1.40.0 (intentionally not upgraded to v1.44+ to avoid forcing go 1.25)
  • Synced go mod tidy to clean up unused indirect dependencies
  • go directive remains 1.24.0

[0.1.4] - 2026-03-20 ​

Breaking Changes ​

1. MustNewSender Removed the ctx Parameter ​

diff
- rabbitmq.MustNewSender(ctx, conf)
+ rabbitmq.MustNewSender(conf)

Description: Consistent with MustNewListener; ctx is not required during initialization

[0.1.3] - 2026-03-20 ​

New Features ​

1. Connection Reliability Enhancements ​

  • NotifyClose Listener: Both Sender and Listener listen for Connection and Channel close events
  • Auto Reconnect: Automatically attempts to reconnect after disconnection (up to 10 times)
  • Thread Safety: Uses atomic.Bool to ensure thread safety of the closed flag
  • Double-Checked Locking: Avoids duplicate reconnections (pre-lock check + post-lock check)
  • Graceful Shutdown: Stops reconnecting after receiving the go-zero proc stop signal

2. Sender Graceful Shutdown ​

  • Automatically registers proc.AddShutdownListener shutdown hook
  • Marks closed on shutdown, preventing further reconnections

3. Greatly Expanded Metrics ​

Sender Metrics (5):

Metric NameTypeLabelsDescription
rabbitmq_sender_send_totalCounterexchange, route_key, statusTotal sends
rabbitmq_sender_send_duration_msHistogramexchange, route_keySend duration
rabbitmq_sender_send_size_bytesHistogramexchange, route_keyMessage size
rabbitmq_sender_reconnect_totalCounter-Number of reconnections
rabbitmq_sender_disconnect_totalCounter-Number of disconnections

Listener Metrics (9):

Metric NameTypeLabelsDescription
rabbitmq_listener_consume_totalCounterqueue, statusTotal consumed
rabbitmq_listener_consume_duration_msHistogramqueueConsumption duration
rabbitmq_listener_consume_size_bytesHistogramqueueMessage size
rabbitmq_listener_in_flightGaugequeueMessages currently being processed
rabbitmq_listener_parse_error_totalCounterqueueNumber of parse failures
rabbitmq_listener_panic_totalCounterqueueNumber of panics
rabbitmq_listener_ack_totalCounterqueue, typeACK/Reject count
rabbitmq_listener_reconnect_totalCounter-Number of reconnections
rabbitmq_listener_disconnect_totalCounter-Number of disconnections

4. Interceptor Optimization ​

  • Order adjusted to: recovery → prometheus → logging → trace
  • Ensures all interceptors execute even if trace parsing fails

5. Other Improvements ​

  • StartConsumerSpan adds carrier nil protection
  • Message parsing moved to traceInterceptor to avoid duplicate parsing
  • ACK/Reject logic unified to the outer processMessage layer

Removed ​

  • Removed client.go
  • Removed parseMessage method (message parsing moved to traceInterceptor)
  • Removed requeueMessage duplicate consumption logic

1. Metric Name Changes ​

diff
- mq_consume_total
+ rabbitmq_listener_consume_total

- mq_consume_duration_ms
+ rabbitmq_listener_consume_duration_ms

Impact: Prometheus queries and Grafana dashboards need to be updated

2. client.go Removed ​

diff
- client, err := rabbitmq.NewClient(ctx, conf)
+ sender, err := rabbitmq.NewSender(conf)
// or
+ sender := rabbitmq.MustNewSender(conf)

3. Sender Auto-Registers Shutdown Hook ​

  • NewSender and MustNewSender automatically register proc.AddShutdownListener
  • In the go-zero environment, there is no need to call Close() manually
  • Outside the go-zero environment, Close() still needs to be called manually

4. MustNewListener Removed the ctx Parameter ​

diff
- rabbitmq.MustNewListener(ctx, conf, handler)
+ rabbitmq.MustNewListener(conf, handler)

Description: The consumer internally derives from context.Background(); the externally passed ctx has no practical effect

Aliyun Gateway ​

中文

All version change records. Format based on Keep a Changelog.

[0.0.3] - 2026-06-14 ​

Fixed ​

  • MultipartBuilder.File ignoring CreateFormFile error could cause nil panic; changed to error accumulation mode
  • MultipartBuilder.Build() signature changed to (contentType string, body []byte, err error), uniformly returning build errors

Improved ​

  • Replaced uuid.New().String() with uuid.NewString() to reduce intermediate UUID struct allocation

[0.0.2] - 2026-06-14 ​

Improved ​

  • sortedQuery now uses strings.IndexByte for zero-allocation iteration, eliminating strings.Split []string allocation
  • sortedQuery eliminates redundant result slice, uses strings.Builder for direct output; map/slice pre-allocated with capacity
  • signOption leverages GetBody for form/multipart body to skip full buffering, avoiding unnecessary memory copies during large file uploads
  • []byte(AppSecret) is pre-computed once outside the closure at NewClient time, no longer converted on every signature
  • strings.Join(signHeaders) promoted to package-level signHeadersValue, computed only once

Fixed ​

  • signRequest now shares a single time.Now() call for Date and Timestamp, eliminating the risk of inconsistency across millisecond boundaries

[0.0.1] - 2026-06-13 ​

Added ​

  • Alibaba Cloud API Gateway Go client, built on go-zero httpc, with automatic HMAC-SHA256 v1 signing
  • Do method: structured requests with path / form / json / header tag auto-mapping
  • DoRaw method: raw byte requests for file uploads, XML, plain text, and custom body
  • WithClient option: inject a custom *http.Client (TLS, connection pool, etc.)
  • Parse function: wraps go-zero httpc.Parse, accepts (resp, err) from Do/DoRaw, auto-parses JSON response and closes Body
  • NewMultipart() / MultipartBuilder: chain-build multipart/form-data request body
  • Conf.Validate() automatically strips trailing / from Host
  • Built-in go-zero circuit breaker integration, auto-shared per Host

Kong HMAC Auth ​

中文

All version change records. Format based on Keep a Changelog.

[0.0.1] - 2026-06-18 ​

Added ​

  • Kong HMAC Auth Go client, built on go-zero httpc, with automatic HMAC signing, following the official Kong HMAC Auth plugin specification
  • Do method: structured requests with path / form / json / header tag auto-mapping
  • DoRaw method: raw byte requests for file uploads, XML, plain text, and custom body
  • WithClient option: inject a custom *http.Client (TLS, connection pool, etc.)
  • Parse function: wraps go-zero httpc.Parse, accepts (resp, err) from Do/DoRaw, auto-parses JSON response and closes Body
  • Support for 5 HMAC algorithms: hmac-sha1, hmac-sha224 (Kong 3.14+), hmac-sha256, hmac-sha384, hmac-sha512
  • @request-target pseudo-header support, value is method /path?query (method in lowercase)
  • Conf.Headers allows custom header list for signing, defaults to ["date", "@request-target"]
  • Digest header auto-computation (SHA-256=base64(sha256(body))), supports empty body zero-length digest, form / multipart skipped
  • Date header auto-injection (GMT format), used for Kong clock skew validation
  • User-Agent header auto-injection (Go-Kong-HmacAuth-Client), can be overridden by the caller
  • Host header injection and signing value uniformly use r.Host, ensuring consistency between signed and actually sent values
  • Conf.Algorithm and Conf.Headers support go-zero optional / default tags, can be omitted in YAML configuration
  • Built-in go-zero circuit breaker, tracing, and metrics integration, auto-shared per Host

cztctl ​

中文

All notable changes to this project are recorded here. Format based on Keep a Changelog.

[1.10.11] - 2026-08-03 ​

Changed ​

  • BREAKING cztctl api rabbitmq now generates handlers and logic against czt-contrib/mq/rabbitmq v0.2.0+. Code generated by this version does not compile against earlier releases of the library
    • The handler closure and the logic constructor both take a new meta rabbitmq.Meta parameter. Business code reads l.meta.Queue to tell which queue a message came from, which is what makes a single handler able to serve multiple queues
    • The handler now always injects rabbitmq.WithConsumeTimeout(time.Duration(svcCtx.Config.Timeout)*time.Millisecond), so MQ consumption and HTTP requests share one deadline from RestConf.Timeout. Note this option outranks the per-queue ListenerQueues[].Timeout in the config file, which therefore has no effect in generated services
    • When the DSL declares a message body type (e.g. order.created(GOrderOrderCreatedReq)), the handler does the json.Unmarshal and the logic method receives *types.XxxReq. A route with no declared type (e.g. payment.success) now generates a logic method that takes no parameter at all: its signature goes from func(req []byte) error to func() error, so business code that used to read the raw bytes has to declare a type in the DSL
  • Upgraded github.com/zeromicro/go-zero to v1.10.3 and github.com/zeromicro/go-zero/tools/goctl to v1.10.2
    • api/swagger builds on goctl's api/spec and pkg/parser/api/parser; neither spec.Type nor parser.Parse changed shape in this release, so no call site needed adapting
    • The --version and cztctl env sample outputs in the README were refreshed to match (1.10.11, go-zero v1.10.3)

Fixed ​

  • Fixed generated code failing to compile when the DSL declared a message body type: internal/types was imported by both the handler and the logic but never referenced, tripping Go's unused-import check
    • internal/types is now imported only when the route actually declares a request type
  • Fixed parser error messages being mangled when the offending DSL text contained a %: the joined text was passed to fmt.Errorf as a format string, turning Foo%sBar into Foo%!s(MISSING)Bar. It now goes through errors.New and is reported verbatim (errorManager.error, Parser.CheckErrors)
    • This also clears the non-constant format string in call to fmt.Errorf finding that Go 1.24's vet reports as an error, which had been failing go vet ./... and go test ./...

[1.10.10] - 2026-06-18 ​

Fixed ​

  • Fixed an issue in cztctl rpc sdk where generating .kong.proto from a CRLF (Windows) line-ending proto file caused rpc line semicolons to not be removed and annotations to be misaligned
    • Root cause: strings.Split(data, "\n") left trailing \r at the end of each line, causing TrimSuffix(";") to match \r instead of ;, preserving the semicolon and shifting {
    • Fix: Normalize \r\n / \r to \n at read time to eliminate \r interference on suffix matching at the source

Changed ​

  • rpcMethodRe regex now uses the returns keyword to identify rpc method declarations, which is more semantic than the original \( bracket matching (a line with returns is a true rpc method)

[1.10.9] - 2026-06-18 ​

Fixed ​

  • Fixed an issue in cztctl rpc sdk where generating .kong.proto with an rpc line using an empty {} block caused annotation misalignment
    • The original check strings.Contains(line, "{") incorrectly treated {} as an already-opened brace, causing the option (google.api.http) annotation to float outside the block with an extra closing brace
    • Fixed by first stripping the closing } of {}, then using HasSuffix("{") for detection, covering all three rpc line styles: semicolon / open brace / empty block

[1.10.8] - 2026-06-16 ​

Added ​

  • cztctl rpc sdk automatically generates .kong.proto files (Kong gRPC-gateway HTTP annotations)
    • Automatically generates a .kong.proto variant with the same name as the proto in the _sdk/ directory when cztctl rpc sdk is executed
    • Automatically adds import "google/api/annotations.proto"
    • Generates option (google.api.http) annotations for each rpc method
    • Path rule: /{ServiceName}/{RpcMethodName}
    • HTTP method is uniformly POST with body: "*"
    • Skips inserting the annotations import if the proto already contains it

[1.10.7] - 2026-06-04 ​

Changed ​

  • cztctl api cron generator adapted to the new API of cron v0.1.0
    • CronAdd unified signature: pass the handler directly at call time; registration + scheduling are completed internally, no additional Add call required
    • Scheduled tasks now default-inject asynq.Timeout(time.Duration(serverCtx.Config.Timeout) * time.Millisecond); timeout is managed by go-zero RestConf.Timeout
    • When there are multiple scheduled tasks, a timeoutOpt local variable is extracted to avoid repeating the timeout expression
    • MaxRetry is still configured per-task via CronRetry
    • Non-scheduled tasks (external dispatch consumers) remain unchanged, registering only the handler

Dependencies ​

  • github.com/zeromicro/go-zero v1.10.1 → v1.10.2
  • github.com/zeromicro/go-zero/tools/goctl stays at v1.10.1 (upstream has not released v1.10.2)

[1.10.6] - 2026-05-27 ​

Added ​

  • cztctl rpc sdk — automatically generate RPC client code as a standalone Go module and publish it to a Git repository
    • Automatically clone/initialize the SDK repository with HTTPS + Token authentication support
    • Recursively parse proto import dependencies and automatically copy all related proto files
    • Call goctl to generate client code; clean up server-side code to keep only client/
    • Automatically run go mod tidy to tidy dependencies
    • SemVer version management: auto-increment patch, auto-carry minor when patch reaches 99
    • Automatic Git commit, tagging, and push (branch + tag)
    • Support --remote / --branch goctl remote template pass-through
    • Support --multiple multi-service mode
    • Support --goproxy custom proxy
    • Support --repo-branch to specify the SDK repository branch (default main)
    • goctl version pre-check (not installed is a hard error; version too low is a soft warning)

[1.10.5] - 2026-04-09 ​

Fixed ​

  • Fixed an issue in cztctl api cron where generating workers.go included a handler argument in CronAdd incorrectly
    • Scheduled tasks (@cron) now correctly generate two lines: Add(pattern, handler) to register the handler, followed by CronAdd(cronExpr, pattern, opts...) to register the schedule
    • Pure async tasks (without @cron) still generate only Add(pattern, handler)
  • Fixed a go.sum checksum mismatch for goctl causing go install to fail

Changed ​

  • Upgrade goctl dependency v1.10.0 → v1.10.1
  • Upgrade go-zero dependency v1.10.0 → v1.10.1

[1.10.3] - 2026-04-07 ​

Added ​

  • .cron route names support - (hyphen) and : (colon) separators, e.g. sync-order, email:send
  • .rabbitmq route names support - (hyphen) separator, e.g. payment-refund, order.pay-callback
  • Route name separators are semantically isolated by file type: .cron only allows - :, .rabbitmq only allows . -

[1.10.2] - 2026-03-22 ​

Added ​

  • cztctl api swagger — generate Swagger 2.0 documentation from a .api file
    • Full info block property mapping (title / description / version / host / basePath / schemes, etc.)
    • Support @server annotations (tags / summary / prefix / group / deprecated / operationId / authType)
    • Support automatic validate tag parameter constraint annotations
    • Support multi-line field header comment parsing
    • Support useDefinitions mode ($ref references)
    • Support wrapCodeMsg response wrapping
    • Support both JSON and YAML output formats
  • cztctl api cron — generate a distributed cron job service from a .cron file
    • Based on the czt-contrib/cron framework
    • Support internal scheduled tasks (@cron expression + @cronRetry retries)
    • Support externally triggered tasks (no @cron, triggered via asynq.Client.Enqueue)
    • Generate complete directory structure: etc / config / handler / logic / svc / types / worker / main
    • Support @doc in both string and KV forms
    • Support @server grouping (group / tags / summary / middleware)
    • Support --remote / --branch remote templates
    • Support --style file naming style (gozero / go_zero / goZero)
  • cztctl api rabbitmq — generate a RabbitMQ consumer service from a .rabbitmq file
    • Based on the czt-contrib/mq/rabbitmq framework
    • Support dot-separated queue names (e.g. order.created, payment.refund.success)
    • Support optional message parameter types
    • Generate complete directory structure: etc / config / handler / logic / svc / types / listener / main
    • Support --remote / --branch remote templates
    • Support --style file naming style
  • cztctl env — environment variable management
    • View current environment variables (CZTCTL_OS / CZTCTL_ARCH / CZTCTL_HOME / CZTCTL_CACHE / CZTCTL_VERSION)
    • Edit environment variables (cztctl env -w KEY=VALUE)
    • CZTCTL_EXPERIMENTAL toggle: off uses ANTLR4 parser, on uses the handwritten recursive-descent parser
  • DSL syntax parsing
    • .cron and .rabbitmq share the base syntax: syntax / info / import / type / @server
    • ANTLR4 parser (default) + handwritten recursive-descent parser (experimental)
    • Full type system support: primitive types, slices, maps, pointers, nested structs, struct tags
    • Support cross-file type references via import
  • Versioning rule: v<go-zero-major-version>.<micro-version> (currently based on go-zero v1.10.0)