Skip to content

Commit

Permalink
trap exits from user processes to make the mutex safe
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Rehfeld committed Dec 1, 2011
1 parent 07b4af2 commit ade85ec
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 6 deletions.
31 changes: 27 additions & 4 deletions apps/warmup-semaphore/src/mutex.erl
Expand Up @@ -7,7 +7,7 @@


-export([start/0, stop/0, wait/0, signal/0]). -export([start/0, stop/0, wait/0, signal/0]).


-record(state, {semaphore}). -record(state, {semaphore, holder=undefined}).


%% @doc mutex:start() ⇒ ok. %% @doc mutex:start() ⇒ ok.
start() -> start() ->
Expand Down Expand Up @@ -39,17 +39,40 @@ signal() ->


%% @private %% @private
init() -> init() ->
process_flag(trap_exit, true),
loop(#state{semaphore=free}). loop(#state{semaphore=free}).


%% @private %% @private
loop(#state{semaphore=Semaphore}=S) -> loop(#state{semaphore=Semaphore, holder=Holder}=S) ->
receive receive
{Pid, MsgRef, wait} when Semaphore =:= free -> {Pid, MsgRef, wait} when Semaphore =:= free ->
catch link(Pid),
Pid ! {MsgRef, ok}, Pid ! {MsgRef, ok},
loop(#state{semaphore=busy}); loop(S#state{semaphore=busy, holder=Pid});
{Pid, MsgRef, signal} when Semaphore =:= busy -> {Pid, MsgRef, signal} when Semaphore =:= busy ->
Pid ! {MsgRef, ok}, Pid ! {MsgRef, ok},
loop(#state{semaphore=free}); unlink(Pid),
%% clean any pending exit messages for Pid from mailbox
receive
{'EXIT', Pid, _} -> true
after 0 ->
true
end,
loop(S#state{semaphore=free, holder=undefined});

{'EXIT', FromPid, _Reason} ->
%% clean any pending messages from Pid from mailbox
receive
{FromPid, _, wait} -> true;
{FromPid, _, signal} -> true
after 0 ->
true
end,
%% free the semaphore when exit came from the current holder
case FromPid of
Holder -> loop(S#state{semaphore=free});
_ -> loop(S)
end;


shutdown -> shutdown ->
exit(shutdown); exit(shutdown);
Expand Down
43 changes: 41 additions & 2 deletions apps/warmup-semaphore/test/mutex_tests.erl
Expand Up @@ -15,7 +15,9 @@ main_test_() ->
fun setup/0, fun setup/0,
fun cleanup/1, fun cleanup/1,
[ [
fun test_mutex/1 fun test_mutex/1,
fun test_semaphore_holder_terminates/1,
fun test_waiting_process_terminates/1
]}. ]}.


%% tests with started mutex %% tests with started mutex
Expand Down Expand Up @@ -62,13 +64,50 @@ test_mutex(_) ->
?_assert(timer:now_diff(MutexEndA, MutexWaitB) > 0), ?_assert(timer:now_diff(MutexEndA, MutexWaitB) > 0),
?_assert(timer:now_diff(MutexStartB, MutexEndA) > 0)]. ?_assert(timer:now_diff(MutexStartB, MutexEndA) > 0)].


%% What happens if a process that currently holds the semaphore terminates prior to releasing it?
test_semaphore_holder_terminates(_) ->
PidA = spawn(fun () ->
ok = mutex:wait(),
timer:sleep(100)
end),

PidB = spawn(fun () ->
timer:sleep(200),
ok = mutex:wait(),
ok = mutex:signal()
end),

ExitReasonPidA = wait_for_exit(PidA),
ExitReasonPidB = wait_for_exit(PidB),

[?_assertEqual(normal, ExitReasonPidA),
?_assertMatch(normal, ExitReasonPidB)].

%% Or what happens if a process waiting to execute is terminated due to an exit signal?
test_waiting_process_terminates(_) ->
PidA = spawn(fun () ->
ok = mutex:wait(),
timer:sleep(100),
ok = mutex:signal()
end),

PidB = spawn(fun () ->
ok = mutex:wait()
end),

exit(PidB, kill),

ExitReasonPidA = wait_for_exit(PidA),

[?_assertEqual(normal, ExitReasonPidA)].

%% helper %% helper


setup() -> setup() ->
mutex:start(). mutex:start().


cleanup(Pid) -> cleanup(Pid) ->
mutex:stop(), catch mutex:stop(),
wait_for_exit(Pid), wait_for_exit(Pid),
timer:sleep(100). timer:sleep(100).


Expand Down

0 comments on commit ade85ec

Please sign in to comment.