Erlang Special Processes without behaviours
TweetNOTE: This is the translation of the original article I wrote in spanish for the wiki of Erlang Argentina.
An overview of the Erlang processes in the OTP environment and the BEAM
OTP has (in its design principles), things like behaviours, applications, releases, and supervision trees. If we zoom into the latter, we'll find supervisors, which supervise processes, that can also be supervisors themselves.
Now, processes can be "common processes" or "special processes". A common process is not integrated with OTP, it cant be supervised (in a standard way), and wont comply/attend to system messages, making process termination, hot code upgrades and the like a very hard task (if not impossible). A special process complies, by definition with a specification that makes it compatible with all the rules of an OTP environment. Let's now see what this spec is all about.
Erlang Special Processes: Behind the scenes of the standard Behaviors
As a matter of fact, we deal with special processes a lot, even without knowing it. For example, when we choose to implement a behaviour (like supervisor, gen_server, etc), we are actually coding "inside" a special process. Yes, behaviours are special processes, if we look under the hood (see Appendix, at the end of the article), we'll spot a special process just like the one we'll see in the example code.
The main difference between a common process and a special process, is that the special process receives the system messages and reacts upon them. A special process is also started in a specific way, and all these properties allows a process to fit right into supervision trees, and to support features such as hot-code upgrades, debugging, etc.
Why erlang special processes are key to understand how the standard behaviors work?
Knowing how a special process works (and how it should be implemented) is fun, and gives a little "low level" background (which is always good), but it's particular useful when we need a process to behave different than the standard provided behaviours (like gen_server, etc). There is a handful of cases where coding your own special process is the best solution. But knowing about them means to be sure that your code wont violate the OTP design principles.
And, it might allow you to impress your friends at the parties (well, if any of you goes to a party where people can actually be impressed by such things, please drop me a line!).
The example code (simpleproc.erl) of an Erlang Special Process
The following code is a rebar template, which is also available at GitHub. I though it would be best to show you the code first, explaining some of the stuff in the code comments (and pointing out the most useful manual links) and then go ahead with a little deeper explanation.
How it works
In this particular case, I've only implementes start_link, but proc_lib actually gives us a couple of options to start a process:
- start_link/3,4,5 and start/3,4,5 for a sync startup, where the supervisor will wait until the newly created process calls proc_lib:init_ack/1,2.
- spawn_link/1,2,3,4 and spawn/1,2,3,4 for an asynchronous start.
When we invoke simpleproc:start_link/0 (for example from a supervisor), it will call proc_lib:start_link/3, which in turns, calls simpleproc:init/1. This one will wait for a message that indicates a successful or failed init (sent by invoking proc_lib:init_ack/1,2). This is effectively a synchronous start.
As soon as the supervisor knows that the process started, the main loop is invoked.
The main loop receives the system messages (which are in the form {system, From, ...}) and process them by calling sys:handle_system_msg/6. This function DOES NOT RETURN, and will invoke system_code_change, system_terminate, system_continue.
NOTE: See that we don't call simpleproc:loop after calling sys:handle_system_msg/6, because this function will never return, but will call simpleproc:system_continue/3. The current process state will be passed from sys:handle_system_msg/6 to simpleproc:system_continue/3, which will return to the main loop.
In the "Appendix" section, you can see the implementation of the actual behaviours distributed with OTP. As you can see, their code is pretty similar to the one shown here, except that they reuse code that "lives" in the gen module.
Tracing and Debugging from inside your Special Process
Another requirement for special process is to support enabling and disabling debug output in the console. To comply with this, our debug should go through sys:handle_debug/4. In the example code, we do this when receiving an unknown message (the last receive clause). sys:handle_debug/4 will invoke the function we pass in the 3rd argument (in this case, simpleproc:write_debug/3), which will actually print the debug messages in the console.
The 4th argument in sys:handle_debug/4, is the system event we want to log. Actually, it's up to the user (us) to define system events, but usually incoming and outgoing messages are represented as tuples, in the form {in,Msg[,From]} and {out,Msg,To}, respectively.
To give this a try, we use sys:trace/2, so we can enable debug for our little module:
In our case, we use {in, Msg} as the system event, which is standarized.
NOTE: The debug options are initialized in simpleproc:init/1, by calling sys:debug_options/1, these are keep along the code, and can be changed by calling sys:trace/2,3 and sys:statistics/2,3.
Hot Code change in your Erlang Special Process
simpleproc:system_code_change/4 is invoked by sys:handle_system_msg/6 when a code change is required. This function must convert the process actual state (the State argument) to the new structure. What follows is an example of hot-code upgrade, by using sys:change_code/4:
Conclusion
Well that wasn't so hard, was it? :D With a few lines of code, and (relatively) few concepts, we can now create our own processes, that support fault tolerance and high availability, and mainly, that really fit into the normal activity of our OTP applications, just like a gen_server, gen_fsm, supervisor, etc. :) A skill useful in a very few scenarios, but nevertheless, will worth a million bucks when the moment comes!
Appendix
What follows is the source code for the behaviours distributed with OTP. Only the relevant code is shown, just to see how the pro's are doing it :)
At the end of the appendix, the gen module is shown, where some code that calls the proc_lib function "lives" and is reused from the behaviours.