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

Handling Akka actor cleanup without resource leaks

Handling Akka actor lifecycle callbacks for resource cleanup works best when ownership is explicit. An actor may look lightweight, yet it can hold a database connection, HTTP client, file stream, temporary directory, or subscription. If termination arrives while one of those resources is live, the JVM process can survive while the resource leaks. The useful question is not merely whether the actor stopped, but whether every resource it owned was released on normal, failed, restarted, and rebalanced paths.

Akka provides convenient lifecycle points, but their ordering and guarantees are easy to misread. This guide focuses on Akka Classic’s preStart and postStop, then connects them to supervision, cluster movement, and system shutdown. The discipline transfers to Akka Typed’s setup behaviour and PostStop signal, although the API names differ. For teams in Sydney and Melbourne running long-lived JVM services, the aim is predictable teardown without tying up a dispatcher thread or leaving sockets behind.

Understand the lifecycle before holding resources

After construction, classic actors normally move through postStart, preStart, message processing, and postStop. preStart runs before the actor accepts its first message, making it a convenient acquisition boundary. postStop runs after processing has ended and is the right place to release actor-owned resources. Code in the constructor is less reliable as a cleanup contract because a construction failure can occur before the instance becomes a running actor.

Do not treat postStop as an unlimited graceful-shutdown routine. It is synchronous, runs during actor termination, and should return promptly. A slow close can block the dispatcher and delay sibling work. If graceful closure needs a timeout, cancellation, or an asynchronous acknowledgement, move that work to a dedicated owner or a CoordinatedShutdown task while keeping the actor hook small.

Field notes from related JVM and Scala experiments live on The Coding Journal. They are useful because they preserve the surrounding environment—versions, deployment shape, and the exact failure—not just the final patch.

Acquire dependencies without blocking the mailbox

Resource acquisition can be placed in preStart, but it should not hold the dispatcher hostage. Opening a remote connection or waiting for a cloud endpoint can take seconds, while Akka expects actors to remain responsive. A robust design starts the actor in a Connecting or Starting state, performs acquisition on a bounded future, and begins accepting work only after readiness. For a local file, the same operation may be quick enough, but the timeout rule still matters.

final class ExportActor(path: Path) extends Actor {
  private val writer =
    new AtomicReference[BufferedWriter](null)

  override def preStart(): Unit = {
    super.preStart()
    writer.set(Files.newBufferedWriter(path))
  }

  override def postStop(): Unit = {
    val openWriter = writer.getAndSet(null)
    try {
      if (openWriter != null) openWriter.close()
    } catch {
      case NonFatal(error) =>
        log.error("Could not close export writer", error)
    } finally {
      super.postStop()
    }
  }
}

If acquisition fails, retry only when the failure is transient and the supervisor policy makes sense. The factory that creates the resource must close any partially opened dependency itself; do not rely on postStop to repair a failed preStart. The atomic reference also prevents a second stop attempt from closing the same writer twice.

For a separate JavaFX support console, IDE setup belongs outside the actor runtime. My Scene Builder setup notes show why keeping build and presentation configuration separate makes troubleshooting cleaner.

Make release safe and repeatable

Cleanup should be idempotent because an actor can stop after a normal message, an exception, a cluster rebalance, or a system shutdown request. The simplest rule is to atomically take ownership of the resource, then close it once. AtomicReference.getAndSet(null) creates that hand-off cleanly: the first teardown receives the object, while later calls see nothing to do.

Catching NonFatal errors inside postStop prevents a failed close from suppressing the rest of Akka’s termination sequence. Log the resource type and actor path, but avoid secrets such as credentials or full connection strings. A cleanup counter labelled by resource type is often more useful than a generic error log during an incident.

Ordering also matters. Stop and release dependants before their provider, close streams before their underlying sockets, and delete a temporary directory only after its files are closed. Where several resources share one client, give the actor either exclusive ownership of that client or a lease, never ambiguous shared ownership.

Account for supervision and restart paths

Supervision restarts need special care because the replacement is a different actor instance. Under the default Akka Classic restart path, the old instance is stopped and a new instance starts; cleanup of the old resource belongs in its postStop, and acquisition of the replacement resource belongs in the new preStart. preRestart and postRestart are not substitutes for those duties, and overriding them without preserving default behaviour can compromise lifecycle guarantees.

Failures during construction deserve the same attention. A child created with context.actorOf can fail before preStart, leaving the parent responsible for deciding whether startup should continue. Use a resource factory with bracket-style acquisition: success transfers ownership to the actor, while failure closes everything acquired so far.

Finally, distinguish actor scope from process scope. postStop handles a resource owned by one actor. Register shared pools, metrics exporters, and other process-wide services with CoordinatedShutdown. Even an orderly JVM exit can skip actor callbacks after a forced kill, so cleanup code is necessary but cannot replace durable lease expiry or external service timeouts.

Use repeatable implementation checklists

A compact review sheet helps prevent a cleanup path from being overlooked during a busy sprint:

Before startup

  • Record every resource and its owner in the actor documentation.
  • Acquire dependencies in preStart or a bounded asynchronous startup state.
  • Make the resource factory release partial dependencies when acquisition fails.
  • Keep constructors free of slow network calls and blocking operations.

During teardown

  • Take the resource atomically and close it once in postStop.
  • Catch and record non-fatal close failures.
  • Release dependants before providers and use leases for shared clients.
  • Register process-wide resources with CoordinatedShutdown.

These checklists are also useful when reviewing pull requests. Readers following related experiments can find the background material through my author profile.

In a Sydney-to-Melbourne support handover, include the resource owner, expected close timeout, and escalation path in the runbook. A 5 pm AEDT handover should not depend on someone remembering an undocumented close call.

Compare callback and shutdown responsibilities

The practical split between lifecycle mechanisms is:

Responsibility Primary mechanism Expected timing Important limitation
Actor-owned resource preStart and postStop Acquisition before message processing; release after processing stops postStop is synchronous and cannot guarantee cleanup after a JVM kill
Slow asynchronous acquisition Startup behaviour and command The actor can remain in a starting state until readiness The startup path needs its own timeout and cancellation rules
Supervision restart Default restart lifecycle The replacement receives fresh startup handling Overridden restart hooks can break default lifecycle behaviour
Whole actor system CoordinatedShutdown Ordered termination tasks run during requested shutdown It does not replace actor-instance cleanup

Australian market teams commonly evaluate AWS, Azure, or Google Cloud regions in Sydney and Melbourne, often with invoices approved in AUD. Those buying conditions do not change lifecycle semantics, but regulated customers may also expect Australian data residency and reject a backup region selected only for cost. Record the chosen region in deployment notes and keep those governance decisions separate from cleanup logic.

Make cleanup an explicit actor contract

A reliable actor contract can be stated in one sentence: every resource has one owner, acquisition happens before work begins, and teardown releases it once without blocking the dispatcher. Store lifecycle logs in UTC while noting the Australian local time; daylight-saving shifts between AEST and AEDT otherwise make incident timelines harder to compare during a Sydney or Melbourne handover.

Run a shutdown drill before the next release: stop one healthy actor, trigger one failing actor, and assert that each owned resource reports closed exactly once. Add a cluster-rebalance case when the service uses sharding, then make any failed cleanup assertion a release blocker.


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