Back to blog
2026-06-27 Thijs Creemers

Release: v1.0.1-alpha-34

This release closes the remaining horizontal-scaling gaps surfaced by the sizing & scaling work: graceful shutdown for zero-downtime rolling restarts, a rate limiter that shares state across replicas, and job processing that stays correct when more than one worker is running. It also adds actionable diagnostics to Stripe checkout. All changes are backward compatible — the new enforcement paths are opt-in.

Added: graceful connection draining on shutdown

The HTTP server now configures Jetty 12 GracefulHandler with a stop timeout, so shutdown drains in-flight requests instead of cutting them — enabling zero-downtime rolling restarts. On stop the server stops accepting new connections, rejects new requests with 503, and waits up to :drain-timeout-ms (default 30000, env HTTP_DRAIN_TIMEOUT_MS) for in-flight requests to finish before halting. An explicit nil disables draining.

Added: replica-shared HTTP rate limiting

A config-driven rate-limit interceptor is now part of the default HTTP pipeline. With an active Redis cache it enforces a fixed-window limit shared across all replicas; with no cache it falls back to a per-process counter (single-node only) and warns at startup. Enforcement is opt-in (default off) so a framework upgrade cannot start returning 429 to consumers. Limits are configurable per environment via :boundary/http :rate-limit and the HTTP_RATE_LIMIT / HTTP_RATE_LIMIT_WINDOW_MS env vars.

This release also closes a latent bug where a rejected request set the response but not :halt?, letting the downstream handler overwrite the 429 with a 200 — the limit was counted but never enforced.

Fixed: bounded in-memory rate limiter

The per-process fallback limiter only pruned a client’s own timestamps and never evicted stale client keys, so high-cardinality client ids (rotating API keys, many remote addresses) could grow process state without bound on a long-running node. The tracked-client map is now bounded by a hard cap (max-tracked-clients = 10000): stale clients are swept, and if still full the least-recently-active client is evicted, all atomically inside one swap!. The bundled prod/acc configs now default rate limiting to disabled — enable it together with an active Redis cache. dev stays enabled (single node, where a per-process counter is correct).

Fixed: exactly-once job processing across workers

Two multi-instance correctness gaps in the job system are closed:

  • Missing-handler re-enqueue. The handler registry is per-process, so an instance can dequeue a job type it has no handler for. Such a job is now re-enqueued (bounded by :max-requeues, default 5) so an instance that owns the handler can pick it up, rather than being silently dead-lettered. Only once the budget is exhausted is it failed terminally with a clear NoHandlerError. A worker started with an empty registry warns loudly.

  • Atomic scheduled-job claim. Promoting a due scheduled job is now an atomic claim — Redis uses ZREM (only the worker whose ZREM returns 1 enqueues), in-memory uses swap-vals! — so a scheduled job runs exactly once across N workers instead of being promoted by several at once.

Fixed: Stripe checkout diagnostics and blank-safe URLs

Two hardening fixes for boundary-payments:

  • create-checkout-session now logs Stripe’s error type, code, param, and message on any non-2xx response, so a 400 is actionable from the logs instead of just status=%d id=%s.

  • success_url / cancel_url now defer a blank (empty or whitespace) override to the redirect URL. Previously an empty override — e.g. an unset PUBLIC_BASE_URL upstream — produced an empty success_url that Stripe rejects with a 400, because "" is truthy.

Version alignment

All libraries bumped to v1.0.1-alpha-34 to maintain lockstep versioning.

Upgrade

Re-run the installer to pick up the latest release:

curl -fsSL https://get.boundary-app.org | bash

No migration required. The new rate-limit enforcement and graceful draining are opt-in and configurable per environment; existing deployments behave as before until enabled.