THRIFT-211. erlang: Support "tethered" clients

Add a client option that causes clients to monitor their creators
and terminate when the creator dies.  This makes it possible to
prevent client leaks without linking, because the latter causes
application code to be killed when a transport error occurs and
exits are not trapped.

git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@781636 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/erl/src/thrift_client.erl b/lib/erl/src/thrift_client.erl
index 92c531a..d5bb146 100644
--- a/lib/erl/src/thrift_client.erl
+++ b/lib/erl/src/thrift_client.erl
@@ -101,12 +101,14 @@
 %% ProtocolFactory :: fun() -> thrift_protocol()
 start(ProtocolFactory, Service, ClientOpts)
   when is_function(ProtocolFactory), is_atom(Service) ->
-    Starter =
+    {Starter, Opts} =
         case lists:keysearch(monitor, 1, ClientOpts) of
             {value, {monitor, link}} ->
-                start_link;
+                {start_link, []};
+            {value, {monitor, tether}} ->
+                {start, [{tether, self()}]};
             _ ->
-                start
+                {start, []}
         end,
 
     Connect =
@@ -119,7 +121,7 @@
         end,
 
 
-    Started = gen_server:Starter(?MODULE, [Service], []),
+    Started = gen_server:Starter(?MODULE, [Service, Opts], []),
 
     if
         Connect ->
@@ -171,7 +173,13 @@
 %%                         {stop, Reason}
 %% Description: Initiates the server
 %%--------------------------------------------------------------------
-init([Service]) ->
+init([Service, Opts]) ->
+    case lists:keysearch(tether, 1, Opts) of
+        {value, {tether, Pid}} ->
+            erlang:monitor(process, Pid);
+        _Else ->
+            ok
+    end,
     {ok, #state{service = Service}}.
 
 %%--------------------------------------------------------------------
@@ -262,6 +270,11 @@
 %%                                       {stop, Reason, State}
 %% Description: Handling all non call/cast messages
 %%--------------------------------------------------------------------
+handle_info({'DOWN', MonitorRef, process, Pid, _Info}, State)
+  when is_reference(MonitorRef), is_pid(Pid) ->
+    %% We don't actually verify the correctness of the DOWN message.
+    {stop, parent_died, State};
+
 handle_info(_Info, State) ->
     {noreply, State}.
 
diff --git a/test/erl/src/test_tether.erl b/test/erl/src/test_tether.erl
index 35794e6..3088cbd 100644
--- a/test/erl/src/test_tether.erl
+++ b/test/erl/src/test_tether.erl
@@ -27,7 +27,16 @@
             io:format("PASS.  Linked owner is dead.~n")
     end,
 
-    check_extras(2),
+    Pid3 = erlang:spawn(?MODULE, test_tethered, []),
+    receive after 200 -> ok end,  % Wait for completion.
+    case is_up(Pid3) of
+        true ->
+            io:format("PASS.  Tethered owner still alive.~n");
+        false ->
+            io:format("FAIL.  Tethered owner is dead.~n")
+    end,
+
+    check_extras(3),
 
     erlang:halt().
 
@@ -54,7 +63,11 @@
                 {linked, true} ->
                     io:format("FAIL.  Linked client still alive.~n");
                 {linked, false} ->
-                    io:format("PASS.  Linked client dead.~n")
+                    io:format("PASS.  Linked client dead.~n");
+                {tethered, true} ->
+                    io:format("FAIL.  Tethered client still alive.~n");
+                {tethered, false} ->
+                    io:format("PASS.  Tethered client dead.~n")
             end,
             check_extras(N-1)
     after
@@ -115,3 +128,23 @@
     %% Exit abnormally to kill our linked extra client.
     %% But we should never get here.
     exit(die).
+
+test_tethered() ->
+    {ok, Client1} = make_thrift_client([{connect, false}, {monitor, tether}]),
+    tester ! {client, tethered, Client1},
+    {ok, Client2} = make_thrift_client([{connect, false}, {monitor, tether}]),
+    io:format("PASS.  Tethered clients created.~n"),
+    try
+        gen_server:call(Client2, {connect, make_protocol_factory(2)}),
+        io:format("FAIL.  Tethered client connected.~n", [])
+    catch
+        Kind:Info ->
+            io:format("PASS.  Caught tethered error.  ~p:~p~n", [Kind, Info])
+    end,
+    receive after 100 ->
+                    io:format("PASS.  Still alive after tethered death.~n"),
+                    % Hang around a little longer so our parent can verify.
+                    receive after 200 -> ok end
+    end,
+    %% Exit abnormally to kill our tethered extra client.
+    exit(die).