Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

README

Example of OTP deadlock via the application controller.

In OTP, the application environment is handled via a ETS table, which is accessed via the application_controller process. Whilst reading an environment variable is implemented as a mere lookup operation, writes are serialized through the application controller. In other words, it is not possible to set an environment variable from a terminate/1 callback in a gen_server which traps exits, since in case of application termination the application controller is busy shutting down the application itself and cannot handle other requests, causing a deadlock.

To reproduce it:

$ erl
Erlang R15B03 (erts-5.9.3.1) [source] [64-bit] [smp:8:8] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.9.3.1  (abort with ^G)
1> c(deadlock).
{ok,deadlock}
2> c(deadlock_sup).
{ok,deadlock_sup}
3> c(deadlock_app..
{ok,deadlock_app}
4> application:start(deadlock).
ok
5> application:stop(deadlock).
ok
6>
=ERROR REPORT==== 9-Dec-2014::09:37:34 ===
** Generic server deadlock terminating
** Last message in was {'EXIT',<0.52.0>,shutdown}
** When Server state == {state}
** Reason for termination ==
** {timeout,{gen_server,call,[application_controller,{set_env,sasl,x,y}]}}

=INFO REPORT==== 9-Dec-2014::09:37:34 ===
application: deadlock
exited: stopped
type: temporary
{application, deadlock,
[{description, "Deadlock example"},
{vsn, "1.0"},
{modules, [deadlock_app, deadlock_sup, deadlock]},
{registered, [deadlock_sup, deadlock]},
{applications, [stdlib, kernel]},
{env, []},
{mod, {deadlock_app, []}}
]}.
-module(deadlock).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
erlang:process_flag(trap_exit, true),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
application:set_env(sasl, x, y),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
-module(deadlock_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
deadlock_sup:start_link().
stop(_State) ->
ok.
-module(deadlock_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
init([]) ->
RestartStrategy = one_for_one,
MaxRestarts = 1000,
MaxSecondsBetweenRestarts = 3600,
SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
Restart = permanent,
Shutdown = infinity,
Type = worker,
AChild = {deadlock, {deadlock, start_link, []},
Restart, Shutdown, Type, [deadlock]},
{ok, {SupFlags, [AChild]}}.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment