Browse Source

Merge pull request #110 from uwiger/uw-claim-wrapper

release claim even if claimant killed
Ulf Wiger 9 years ago
parent
commit
6b1601de7b
1 changed files with 40 additions and 5 deletions
  1. 40 5
      src/gproc_pool.erl

+ 40 - 5
src/gproc_pool.erl

@@ -507,16 +507,51 @@ try_claim(K, Pid, F) ->
     case gproc:update_counter(K, [0, {1, 1, 1}]) of
         [0, 1] ->
             %% have lock
-            try  Res = F(K, Pid),
-                 {true, Res}
-            after
-                gproc:reset_counter(K)
-            end;
+            execute_claim(F, K, Pid);
         [1, 1] ->
             %% no
             false
     end.
 
+%% Wrapper to handle the case where the claimant gets killed by another
+%% process while executing within the critical section.
+%% This is likely a rare case, but if it happens, the claim would never
+%% get released.
+%% Solution:
+%% - spawn a monitoring process which resets the claim if the parent dies
+%%   (spawn_link() might be more efficient, but we cannot enable trap_exit
+%%    atomically, which introduces a race condition).
+%% - for all return types, kill the monitor and release the claim.
+%% - the one case where the monitor *isn't* killed is when Parent itself
+%%   is killed before executing the `after' clause. In this case, it should
+%%   be safe to release the claim from the monitoring process.
+%%
+%% Overhead in the normal case:
+%% - spawning the monitoring process
+%% - (possibly scheduling the monitoring process to set up the monitor)
+%% - killing the monitoring process (untrappably)
+%% Timing the overhead over 100,000 claims on a Core i7 MBP running OTP 17,
+%% this wrapper increases the cost of a minimal claim from ca 3 us to
+%% ca 7-8 us.
+execute_claim(F, K, Pid) ->
+    Parent = self(),
+    Mon = spawn(
+            fun() ->
+                    Ref = erlang:monitor(process, Parent),
+                    receive
+                        {'DOWN', Ref, _, _, _} ->
+                            gproc:reset_counter(K)
+                    end
+            end),
+    try begin
+            Res = F(K, Pid),
+            {true, Res}
+        end
+    after
+        exit(Mon, kill),
+        gproc:reset_counter(K)
+    end.
+
 setup_wait(nowait, _) ->
     nowait;
 setup_wait({busy_wait, MS}, Pool) ->