Elixir. Long Waiting Sync Calls Using OTP GenServer or “when Poolboy is on a Vacation”
Distributed Systems Development A-Z Guide.
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.
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.
Let’s try this in a shell. This time return is super fast and initialization has been spawned in a concurrent process! Cool!
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.
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!
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!
DONE
You can find a full source here: Link to Github repo pharosproduction/OTP-async-call
Thanks for reading!