Create a gist now

Instantly share code, notes, and snippets.

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