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

How to fix a stalled Akka actor mailbox in Scala

Akka actors power a lot of the reactive pipelines running in Australian fintechs, from payment gateways in Sydney to loan processing systems in Melbourne. When an actor's mailbox stops draining, the whole pipeline grinds to a halt, and logs fill up with warnings about unhandled messages and timeouts. A stalled mailbox is rarely a bug in Akka itself; it usually points to a bottleneck in how messages are processed or how threads are allocated.

The tricky part is that a stalled mailbox often looks like a network problem or a database slowdown at first glance. Requests pile up, latency spikes, and dependent services start throwing exceptions. By the time you trace the issue back to a single actor, the production environment in Brisbane or Perth is already throwing alerts, and the on-call engineer is scrambling for a cup of flat white while staring at stack traces.

This guide walks through the practical steps I use to get a stalled Akka actor mailbox moving again. It covers diagnostic commands, dispatcher tuning, the stash pattern, and supervision strategies that have saved my projects from late-night rollbacks. The goal is to move from a frantic guess-and-check session to a measured response built on observable behaviour and tested workarounds.

If you maintain a Scala service that talks to Akka Http, Kafka, or Cassandra, the patterns below will help you isolate the culprit quickly. Most of these notes come from real bug hunts captured over the years and stored in my personal engineering journal, where I document the kind of fixes that never make it into official documentation.

Recognising the symptoms of a stalled mailbox

A stalled mailbox shows up as a sudden drop in throughput and a ballooning queue size. The actor appears alive in the dashboard, but the number of messages processed per second drops to zero. You might see the mailbox size climb past thousands of messages while the actor's aroundReceive logs stay empty.

In one incident affecting a checkout service running on infrastructure hosted in Adelaide, the symptoms looked like a database outage. The Akka Http routes returned 503 errors, but the database was healthy. The real cause was a single actor responsible for fraud scoring that had blocked on a synchronous call to an external risk API. Every message piled up behind that blocking call, and nothing moved until the call timed out.

Common signs include:

  • Steady growth in the actor's mailbox size without a matching rise in processed messages
  • Thread dumps showing many threads parked in MessageDispatcher awaits
  • Supervision logs complaining about AskTimeoutException from children
  • Increasing memory usage as queued messages hold references to payloads

When you see these patterns together, the actor is almost certainly stalled. The next step is to figure out whether the stall comes from the message handler itself or from the dispatcher that runs it.

Why actor mailboxes stop draining

Most stalls fall into three buckets. The first is blocking I/O inside the actor's receive method. Akka actors are designed to process one message at a time per instance, so any call that waits for a socket, a database, or a file will freeze the actor until the call returns. The second bucket is CPU-heavy work that takes longer than the dispatcher expects, causing the scheduler to assume the thread is hung. The third is misconfigured dispatchers that starve the actor of threads.

In Australia, a frequent offender is the way many teams configure a single default dispatcher for an entire application. When one actor hogs the pool, every other actor on the same dispatcher slows down. This is especially painful in services deployed across multiple regions, because the dispatcher usually runs on the same JVM as the API layer.

To check which case you are in, look at the thread dumps and the actor's processing time. If the handler blocks on I/O, the thread state will show WAITING or TIMED_WAITING on a socket read. If the dispatcher is the problem, you will see threads in RUNNABLE state doing useful work, but the actor never gets scheduled because the pool is full. Understanding which bottleneck you have dictates the fix.

Tuning the dispatcher for better throughput

The quickest win is often moving the problematic actor onto a dedicated dispatcher. Akka lets you define a dispatcher in application.conf with its own thread pool, parallelism factor, and throughput deadline. For actors doing blocking I/O, a dispatcher backed by a thread-pool-executor with a generous core pool size works well. For CPU-bound actors, the default fork-join-executor is usually fine, but you can tune its parallelism-min and parallelism-factor to match the host.

When I worked on a notification service for an Australian retail client in Melbourne, the actor responsible for sending SMS via a third-party gateway stalled every time the gateway slowed down. Moving that actor to a dedicated dispatcher with ten threads isolated the blockage. The rest of the system stayed responsive while the SMS actor worked through the backlog.

If your actor genuinely needs to do blocking work, consider wrapping the call in a Future and using pipeToSelf to continue processing without blocking the thread. This pattern preserves the actor model while still calling out to slow dependencies such as the credit reporting bureau APIs used by Australian lenders. The key is to keep the receive method short and non-blocking.

Comparing the available strategies

Different situations call for different fixes. The following comparison summarises the trade-offs between the main approaches for an Akka actor mailbox that has stalled.

Strategy Best for Impact on throughput Complexity
Dedicated dispatcher Blocking I/O or CPU-heavy actors High Low
Stash plus state machine Bursty input, awaiting external responses Medium Medium
Pull pattern with backpressure Rate-limited downstream services Medium High
Circuit breaker Unreliable external dependencies Medium Medium
Increase mailbox capacity Short spikes, tolerant systems Low Very low
Switch to Ask pattern One-off requests with timeouts Low Low

Most teams combine several of these. A typical setup might use a dedicated dispatcher for blocking work, a stash for buffering during slow calls, and a circuit breaker around external services. The combination keeps the actor responsive even when individual components degrade.

Using stash and pull patterns to control flow

Sometimes the actor itself is fine, but the upstream producer sends messages faster than the actor can handle. In this case, the Akka Stash trait provides a buffer that holds messages until the actor is ready. Calling stash() puts the current message aside, and calling unstashAll() replays everything once the actor is ready to handle new work.

A more advanced approach is the pull pattern, where the actor explicitly requests the next message only when it has the capacity to process it. This is similar to reactive streams backpressure and works well when downstream services impose strict rate limits.

Common stash patterns to remember:

  • Stash during state transitions and unstashed once the new state is ready
  • Use a bounded stash size to prevent unbounded memory growth
  • Combine stash with context.become for clear state-driven behaviour
  • Avoid stashing inside Future callbacks to keep the actor model intact

Combining stash with a state machine keeps the actor consistent. For example, an actor waiting for an external response can stash incoming messages, transition to a Waiting state, and only unstashed once the response arrives. This prevents message loss during slow calls and keeps the mailbox from growing without bound.

Timeouts, supervision, and circuit breakers

Defensive coding helps prevent stalls from cascading. Wrap blocking calls in Future with a strict Timeout, and use Akka's CircuitBreaker to fail fast when a dependency is unhealthy. The circuit breaker opens after a threshold of failures, causing calls to fail immediately until a cool-down period passes. This gives the actor a chance to drain its mailbox and recover.

Supervision strategies also matter. The default OneForOneStrategy restarts a failed child actor, which clears its mailbox. If a child repeatedly stalls because of an external dependency, consider using a Backoff supervisor with exponential delays. This avoids hammering the failing dependency and gives transient outages time to clear.

For production systems operating under the Australian Privacy Act and the Notifiable Data Breaches scheme, predictable failure handling is not just a nicety. When a data pipeline stalls and messages back up, you risk holding personal information in queues longer than necessary. Robust timeouts and supervision reduce the window where sensitive data sits in memory, helping you stay compliant with the 72-hour breach notification requirement.

The best defense against stalled mailboxes is a combination of observability and defensive coding. Set up alerts on mailbox size, not just CPU and memory. Add thread dumps to your incident playbook so the on-call knows exactly what to capture when an alert fires. Document the dispatcher configuration for every actor that handles external I/O, so the next engineer does not have to reverse-engineer the system at 3am.

For teams running Scala services in Australian data centres, it pays to review the Privacy Act implications of long message queues. Personal data sitting in a stalled mailbox for hours creates compliance risk under the Notifiable Data Breaches scheme, which requires notification within 72 hours. Tighter timeouts and circuit breakers keep the data footprint small and the audit trail clean.

For the full collection of Akka debugging notes and Scala performance fixes collected from real outages, visit coding-journal.com. The archive includes dispatcher configurations, stash pattern templates, and the thread dump scripts I use during incident response.


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