whoami

Lucas Jenß

cat /etc/motd

The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.

Close-up of white dominoes with black dots standing on green felt surface, shallow depth of field, focused mood

ls -la ~/languages/

total 8
drwxr-xr-x
▶ PHP
▶ Ruby
▶ Scala
▶ C#
▶ JavaScript
▶ Objective-C
▶ Shell Scripting

ls -la ~/toolchain/

total 7
drwxr-xr-x
▶ Typo3
▶ Akka
▶ Capistrano
▶ Git
▶ MAMP
▶ Adobe Illustrator
▶ NSTrackingArea (Cocoa)

uname -a

platforms
drwxr-xr-x
▶ Mac OS X
▶ Unix

Strategies For Graceful Shutdown Of Akka HTTP Servers

An Akka HTTP server rarely stops at the exact moment an operating system sends SIGTERM. Active requests may still be reading a request body, waiting for a database response, or streaming data to a client. A reliable shutdown sequence gives those operations time to finish before the process exits.

The practical goal is controlled termination rather than an artificially long delay. New traffic must stop arriving, existing work must receive a reasonable deadline, connection pools must close, and the actor system must terminate cleanly. If any stage is skipped, deployments can produce broken responses, noisy logs, or messages that are processed twice.

Akka HTTP provides the building blocks, while the application remains responsible for sequencing them. This approach is useful whether a service runs under systemd on a small VPS, in Docker, or behind an AWS load balancer in the Sydney region. The same principles apply across Akka Classic and Akka Typed, although the APIs differ slightly.

Treat Shutdown As A Lifecycle

A graceful stop should be considered a lifecycle with explicit stages:

  • Stop accepting new traffic.
  • Allow in-flight requests to complete.
  • Apply a deadline to requests that do not finish.
  • Close the HTTP server binding.
  • Stop application actors and supporting resources.
  • Terminate the actor system.

Calling ActorSystem.terminate() first is usually too abrupt. It may interrupt actors while the server still has work in progress, and it makes the final outcome dependent on timing. Instead, register an orderly shutdown task with CoordinatedShutdown, or invoke the same sequence from the application’s main shutdown path.

For Akka Classic, the binding returned by Http().bindAndHandle exposes an asynchronous unbind() operation. For Akka Typed, the server materialised from Http().newServerAt(...).bind(...) can be managed through the binding future and the typed actor system. The exact syntax changes, but the central idea remains: retain the binding and make its termination part of the application lifecycle. Practical notes on related development problems can be found in these engineering notes.

Stop New Requests Before Closing Resources

Unbinding the listener prevents new TCP connections from being accepted, but it does not always mean that every request has disappeared. Existing keep-alive connections may still contain requests, and traffic already queued by a proxy can arrive briefly during the transition.

A reverse proxy or cloud load balancer should therefore be considered part of the shutdown design. First mark the instance unhealthy or remove it from service discovery. Wait for the deregistration delay, then unbind the Akka HTTP server. This gives an AWS Application Load Balancer, Nginx, or another proxy time to stop routing ordinary traffic to the process.

In Australia, a service deployed in ap-southeast-2 may serve customers across Sydney, Melbourne, Brisbane, and Perth. A short health-check interval can make a rollout appear successful while requests are still in flight from another city. Testing the actual load-balancer drain period in AEST and AEDT avoids assuming that a local laptop behaves like production.

Use CoordinatedShutdown As The Control Plane

CoordinatedShutdown provides named phases, dependencies, and timeouts for a predictable termination process. Rather than scattering cleanup calls across signal handlers, add tasks to the appropriate phase. A server binding can be closed in the HTTP termination phase, while database pools and message consumers can stop in later application-specific phases.

A simplified Akka Classic example looks like this:

val binding = Http().newServerAt("0.0.0.0", 8080).bind(route)

CoordinatedShutdown(system).addTask(
  CoordinatedShutdown.PhaseServiceUnbind,
  "unbind-http",
  () => binding.flatMap(_.unbind())
)

In a complete service, the task should also account for the possibility that binding failed during startup. The future returned by bind can be converted into a shutdown task only after successful materialisation, so startup and termination errors need separate logging and recovery paths. Avoid swallowing those failures: an apparently clean shutdown can hide a listener that never bound correctly.

The miscellaneous troubleshooting notes are a useful reminder that operational fixes often depend on the surrounding runtime, not just the line that throws an exception. Akka shutdown code deserves the same wider view.

Choose Timeouts Based On Real Work

A shutdown timeout should reflect the slowest legitimate operation, not an arbitrary number copied from a sample. A short JSON request might finish in milliseconds, while an upload, server-sent event stream, or report generated from a slow database query may need substantially longer.

A useful policy separates normal work from exceptional work. Allow ordinary requests perhaps 10 to 30 seconds to finish, then force the process to stop after a larger hard deadline. Streaming endpoints may need a specific policy, such as closing immediately during deployment or moving long-lived streams to a separate service.

Shutdown Stage Purpose Typical Policy Failure Handling
Health removal Prevent new traffic Begin before process termination Log failed deregistration
Listener unbind Stop accepting connections Complete quickly Continue to drain existing work
Request drain Finish valid requests 10–30 seconds for ordinary APIs Return errors after deadline
Resource cleanup Close pools and consumers Short, bounded phase Force close after timeout
Actor termination End the process safely Final hard deadline Escalate and exit

Do not use an unlimited Await.result to make the sequence look synchronous. Blocking a dispatcher thread can prevent the very futures needed to complete shutdown. Prefer future composition, phase deadlines, and non-blocking callbacks. If a blocking library must be closed synchronously, isolate it on an appropriate dispatcher and enforce a finite timeout.

Handle Signals And Orchestrators Carefully

On Linux, systemd commonly sends SIGTERM and waits for TimeoutStopSec before issuing SIGKILL. Docker and Kubernetes follow a similar pattern, with a configurable termination grace period. The application’s final shutdown deadline must be shorter than the supervisor’s deadline, leaving enough time for logging and process exit.

A Kubernetes deployment should use a readiness probe that becomes unhealthy before termination, and often a preStop hook or application endpoint to begin draining. The hook is not a substitute for signal handling because it can be interrupted or delayed. The JVM process must still respond correctly to SIGTERM, and the pod’s terminationGracePeriodSeconds must exceed the expected drain window.

For systemd, set a stop timeout comfortably above the application’s own maximum. If Akka allows 20 seconds to drain but systemd kills the process after 15, the application never gets the chance to complete its plan. This mismatch is common during early deployments, particularly when a service is moved from a developer machine to a managed host.

Protect Messages And External State

HTTP shutdown is only one part of a service’s consistency story. If an endpoint publishes an event, writes a payment record, or updates a session, stopping at the wrong point can leave the client uncertain about whether the operation succeeded. The application should make important operations idempotent and use durable state transitions where possible.

Consumers should stop taking new messages before producers and database resources are closed. Otherwise an actor can receive work, begin processing, and then lose the connection it needs to acknowledge completion. A typical order is to pause ingress, stop consumers, drain HTTP traffic, finish application actors, close pools, and then terminate the actor system.

Load-balanced web applications provide a familiar example. A process can shut down cleanly while users still experience inconsistent state if sessions are stored locally. Lessons from a TYPO3 session bug apply here: graceful process termination cannot compensate for state that is tied to one instance when requests move between instances.

Make Draining Observable

A shutdown mechanism is much easier to trust when its phases are visible. Log the signal, the start and end of each phase, the number of active requests if available, and the reason for any forced termination. Include a deployment identifier and instance name so operators can distinguish a normal rollout from a crash.

Metrics should cover request duration during draining, rejected connections, shutdown duration, and the number of requests that exceed the deadline. A counter for forced exits is especially valuable. One forced exit during a rare network failure may be acceptable; a steady increase after every release points to an endpoint or dependency that needs attention.

Use structured logs rather than a single message such as “server stopped”. In an Australian production environment, an incident may be handed between teams in Sydney, Adelaide, and Perth across different shifts. Clear timestamps with UTC offsets, phase names, and correlation IDs save time when an evening deployment turns into an after-hours investigation.

Test The Full Termination Path

A unit test can verify that a shutdown task is registered, but it cannot prove that a real client receives a complete response while a pod is being removed. Add integration tests that start the server, issue slow requests, send SIGTERM, and confirm that requests within the drain window complete successfully.

Test the awkward cases as well: a request that exceeds the timeout, a failed database close, a half-open client connection, a stream that never ends, and a binding that fails before shutdown registration. Run the same scenarios behind the production proxy or ingress controller. A local curl test does not reproduce connection draining by an AWS load balancer or Kubernetes service.

Deployments should also be checked during a realistic release window. Australian teams often schedule changes outside local business hours, but customer traffic may continue across time zones and overnight operations. A controlled canary in Melbourne or Sydney, followed by log review and rollback testing, exposes configuration mistakes before a broad rollout.

A robust shutdown path is a small operational contract: new work stops entering, valid work gets time to finish, state is left consistent, and the process exits before its supervisor loses patience. Add those phases to the service, measure them in production, and exercise them during every significant Akka HTTP upgrade or deployment.


cat ~/interests.json

KeyValue
editorTerminal-first workflow
osMac OS X / Unix
vcsGit, distributed version control
deployCapistrano, cron automation
graphicsSVG, Adobe Illustrator troubleshooting
networkingIP validation, SSH, VPN

git log --oneline --reverse

2013-10-30

Solving SVG import issues in Adobe Illustrator CS6 and CC

When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.

2013

Solving NDK build issues on OS X

Troubleshooting native development kit compilation problems on Mac OS X.

2013

Programmatically adding PHP generated TypoScript to the backend configuration

Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.

2013

ArgumentError: Could not parse PKey: no start line

Debugging an SSH key parsing error encountered during deployment.

2011-08-04

Validating IP-Addresses in PHP

Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.

2011-07-09

Cocoa: Using NSTrackingArea

A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.


cat ~/contact.txt

github: github.com/x3ro
stackoverflow: x3ro
coderwall: coderwall.com/x3ro
twitter: @x3rames