Implementing a GenServer
This is the slide walkthrough of the talk I gave at Erlang & Elixir Bangalore meetup.
We use spawn, send, receive concepts from Elixir and a function loop to implement a GenServer.
GenServers expand to Generic Servers. What do we mean by servers here? Let’s consider a web server. We can create new data, change data and get data from the running web server. Similar to web servers these are microservers within elixir which work on top of processes.
spawn function creates a new process. spawn takes module name, function name and array of params to be passed as params. When spawn is called, the specified function from the module is executed in separate process. The spawner process from where the spawn is executed and the spawned process with the function work independently.
If the spawner and spawned behave independently, how can we communicate with each other. We use send function to send messages between processes. send takes up destination process id as first parameter and message as the second parameter. The messages just reach the destination process’s mailbox.
We use receive block to catch the messages sent by processes. receive reads messages from the mailbox one by one and executes the matching branch.
loop (we can name it anything)is just a custom function that calls itself infinitely. We don’t have to worry about stack overflow because of recursion, because it is tail call optimized(last statement is just the call to itself.).
The diagram just describes the spawning of new process with loop function. It also shows the communication between loop and external process X.
The black portion of the code indicates code execution in spawner process. The red portion indicates code execution in spawned process.
start function spawns loop function with a map of runs and wickets and just returns pid. loop function encounters receive block and waits for a message.
In update_runs function from the spawner, we just send a message to the spawned process. It matches the appropriate branch and appropriate function is called. As a result run is incremented by one. Final statement of the branch calls the loop again.
Similarly wicket is updated.
What we did in previous step is a one way updation. What if we want to get back the result. We need to send spawner pid(using self() function) along with the message to the spawned and wait in receive block. After receiving the message with spawner pid, appropriate branch which gets the data is executed and the data is passed back as message to the spawner. The spawner which waits in the receive block returns the value.
In the slide above, left hand side has duplication of send and receive blocks. We can extract those out as separate methods on the right hand side.(named call and cast)
Here also we can use the power of elixir’s pattern matching and extract handle_call and handle_cast methods.
Previously we used to just send message alone. If we want to add parameter, we have to send the parameter along with the message. Here we added a tuple with :cast, because we want to differentiate between cast and call methods. Cast just posts data. Call gets back the data.
We are doing the same refactoring here. Passing parameter with call and adding :call to the tuple to identify.
Instead of hard coding initial state, we can get from external services. So we are refactoring start method to have init method call before spawning.
We just rearrange the methods based on the codes that change and codes that are generic.
Now we put them in different modules. In order to call between modules we pass module name in start method. This helps the server to call back the implementation(we pass callback along with state in loop function).
we add stop method to stop the loop and handle_info to handle stray messages which we might get randomly from other processes. If we don’t handle them it might fill up the mailbox and the process would become slower.
These abstractions with some other helper functions are there inside GenServer module provided by Elixir. Left side is our code. Right side we can replace with GenServer.
Another usecase for GenServer.
These are the callbacks provided by Elixir’s Genserver.
Hence we implemented a GenServer using process, send, receive, and loop.
Check my book for Elixir starters at https://gum.co/XgbKSE