Elixir. Long Waiting Sync Calls Using OTP GenServer or “when Poolboy is on a Vacation”

Elixir has no magic. Really.

Give us a message if you’re interested in Blockchain and FinTech software development or just say Hi at Pharos Production Inc.

Let’s assume we have a long waited GenServer initialization. That can be anything from a cold storage start to remote call to a third-party service. GenServer has a default timeout set to 5 seconds. You can easily crash your supervisor initialization just by waiting for children initializations. So what should we do? That’s quite straightforward — GenServer should send an asynchronous request to itself and immediately send :ok response to its supervisor. Let’s see how to do that.

Make it in a synchronous way

This is our synchronous GenServer. As you can see init callback contains long-waiting task and respond with a nil state. Surely both sleep and nil state are just an example for our toy use case. Don’t ever do like this. Send back something that makes sense.

Let’s fire a brand new IEX and test our initialization.

As we expected we need to wait for 3 seconds then receive a success tuple. So how do we make it asynchronously? Check this out! We’re using simple send function and handle_info callback. One more thing, if you’re adding a new handle_info callback to your module, don’t forget to handle all possible use cases. You always should be saved from mailbox overloading. You can skip boilerplate catch-all use case when you’re not using handle info, Elixir does this for you.

Async initialization

Let’s try this in a shell. This time return is super fast and initialization has been spawned in a concurrent process! Cool!

Testing our async initialization

What about requests to GenServer. There are two approaches to handle requests call and cast. Everyone loves call because it always returns a value and not just fire-and-forget like a cast. But when you start a call request, then the starting process get stuck waiting for the response. And 5 seconds timeout will restart your whole GenServer.

Sync request

What if that process is a database GenServer module which serving millions of requests per minute? You should use workers. Usually, you use Poolboy or Ecto(Ecto uses Poolboy too) to handle your DB workers. Poolboy is on a vacation. So what you should do? It’s a good time to run synchronous call in an asynchronous way!

Async request from sync handle_call

That’s it! We spawn a new process from handle_call function and return :noreply atom. Now your module can handle other incoming requests. When database will respond with a new bunch of data, GenServer will reply to the caller with GenServer.reply function. BTW, that’s how you can use from a parameter of a handle_call. Awesome!

async response from sync request

DONE

You can find a full source here: Link to Github repo pharosproduction/OTP-async-call

Thanks for reading!

--

--

Dmytro Nasyrov
Pharos Production

We build high-load software. Pharos Production founder and CTO.