From d86d55c1f90b37d991e20ad0f1ac37b1e38b36e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 26 Sep 2019 10:43:12 +0200 Subject: Add a function to change the connection owner While at it the gun:info/1 function has been fixed to work even when we are in the not_connected state, and the owner is now also returned. --- doc/src/manual/gun.asciidoc | 1 + doc/src/manual/gun.info.asciidoc | 2 ++ doc/src/manual/gun.set_owner.asciidoc | 57 +++++++++++++++++++++++++++++++++++ src/gun.erl | 39 ++++++++++++++++++++---- test/gun_SUITE.erl | 14 +++++++++ 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 doc/src/manual/gun.set_owner.asciidoc diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc index 478d40b..9670e2e 100644 --- a/doc/src/manual/gun.asciidoc +++ b/doc/src/manual/gun.asciidoc @@ -16,6 +16,7 @@ Connection: * link:man:gun:open(3)[gun:open(3)] - Open a connection to the given host and port * link:man:gun:open_unix(3)[gun:open_unix(3)] - Open a connection to the given Unix domain socket +* link:man:gun:set_owner(3)[gun:set_owner(3)] - Set a new owner for the connection * link:man:gun:shutdown(3)[gun:shutdown(3)] - Gracefully close the connection * link:man:gun:close(3)[gun:close(3)] - Brutally close the connection * link:man:gun:info(3)[gun:info(3)] - Obtain information about the connection diff --git a/doc/src/manual/gun.info.asciidoc b/doc/src/manual/gun.info.asciidoc index cf861c9..edd97f6 100644 --- a/doc/src/manual/gun.info.asciidoc +++ b/doc/src/manual/gun.info.asciidoc @@ -12,6 +12,7 @@ info(ConnPid) -> Info ConnPid :: pid() Info :: #{ + owner => pid(), socket => inet:socket() | ssl:sslsocket(), transport => tcp | tls, protocol => http | http2 | socks | ws, @@ -45,6 +46,7 @@ the connection. == Changelog +* *2.0*: The value `owner` was added. * *1.3*: The values `socket`, `transport`, `protocol`, `origin_host`, `origin_port` and `intermediaries` were added. * *1.0*: Function introduced. diff --git a/doc/src/manual/gun.set_owner.asciidoc b/doc/src/manual/gun.set_owner.asciidoc new file mode 100644 index 0000000..ee12a67 --- /dev/null +++ b/doc/src/manual/gun.set_owner.asciidoc @@ -0,0 +1,57 @@ += gun:set_owner(3) + +== Name + +gun:set_owner - Set a new owner for the connection + +== Description + +[source,erlang] +---- +set_owner(ConnPid, OwnerPid) -> ok + +ConnPid :: pid() +OwnerPid :: pid() +---- + +Set a new owner for the connection. + +Only the current owner of the connection can set a new +owner. + +Gun monitors the owner of the connection and automatically +shuts down gracefully when the owner exits. + +== Arguments + +ConnPid:: + +The pid of the Gun connection process. + +OwnerPid:: + +The pid of the new owner for the connection. + +== Return value + +The atom `ok` is returned. + +== Changelog + +* *2.0*: Function introduced. + +== Examples + +.Set a new owner for the connection +[source,erlang] +---- +ok = gun:set_owner(ConnPid, OwnerPid). +---- + +== See also + +link:man:gun(3)[gun(3)], +link:man:gun:open(3)[gun:open(3)], +link:man:gun:open_unix(3)[gun:open_unix(3)], +link:man:gun:shutdown(3)[gun:shutdown(3)], +link:man:gun:close(3)[gun:close(3)] diff --git a/src/gun.erl b/src/gun.erl index 12f4319..3154b9b 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -23,6 +23,7 @@ -export([open/2]). -export([open/3]). -export([open_unix/2]). +-export([set_owner/2]). -export([info/1]). -export([close/1]). -export([shutdown/1]). @@ -396,9 +397,14 @@ consider_tracing(ServerPid, #{trace := true}) -> consider_tracing(_, _) -> ok. +-spec set_owner(pid(), pid()) -> ok. +set_owner(ServerPid, NewOwnerPid) -> + gen_statem:cast(ServerPid, {set_owner, self(), NewOwnerPid}). + -spec info(pid()) -> map(). info(ServerPid) -> {_, #state{ + owner=Owner, socket=Socket, transport=Transport, protocol=Protocol, @@ -407,22 +413,33 @@ info(ServerPid) -> origin_port=OriginPort, intermediaries=Intermediaries }} = sys:get_state(ServerPid), - {ok, {SockIP, SockPort}} = Transport:sockname(Socket), - #{ + Info0 = #{ + owner => Owner, socket => Socket, transport => case OriginScheme of <<"http">> -> tcp; <<"https">> -> tls end, - protocol => Protocol:name(), - sock_ip => SockIP, - sock_port => SockPort, origin_scheme => OriginScheme, origin_host => OriginHost, origin_port => OriginPort, %% Intermediaries are listed in the order data goes through them. intermediaries => lists:reverse(Intermediaries) - }. + }, + Info = case Socket of + undefined -> + Info0; + _ -> + {ok, {SockIP, SockPort}} = Transport:sockname(Socket), + Info0#{ + sock_ip => SockIP, + sock_port => SockPort + } + end, + case Protocol of + undefined -> Info; + _ -> Info#{protocol => Protocol:name()} + end. -spec close(pid()) -> ok. close(ServerPid) -> @@ -1253,6 +1270,16 @@ handle_common_connected_no_input(Type, Event, StateName, State) -> handle_common(Type, Event, StateName, State). %% Common events. +handle_common(cast, {set_owner, CurrentOwner, NewOwner}, _, + State=#state{owner=CurrentOwner, status={up, CurrentOwnerRef}}) -> + demonitor(CurrentOwnerRef, [flush]), + NewOwnerRef = monitor(process, NewOwner), + {keep_state, State#state{owner=NewOwner, status={up, NewOwnerRef}}}; +%% We cannot change the owner when we are shutting down. +handle_common(cast, {set_owner, CurrentOwner, _}, _, #state{owner=CurrentOwner}) -> + CurrentOwner ! {gun_error, self(), {badstate, + "The owner of the connection cannot be changed when the connection is shutting down."}}, + keep_state_and_state; handle_common(cast, {shutdown, Owner}, StateName, State=#state{ owner=Owner, status=Status, socket=Socket, transport=Transport, protocol=Protocol}) -> case {Socket, Protocol} of diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl index a0ecddd..715bec5 100644 --- a/test/gun_SUITE.erl +++ b/test/gun_SUITE.erl @@ -384,6 +384,20 @@ retry_timeout(_) -> error(shutdown_too_late) end. +set_owner(_) -> + doc("The owner of the connection can be changed."), + Self = self(), + Pid = spawn(fun() -> + {ok, ConnPid} = gun:open("localhost", 12345), + gun:set_owner(ConnPid, Self), + Self ! {conn, ConnPid} + end), + Ref = monitor(process, Pid), + receive {'DOWN', Ref, process, Pid, _} -> ok after 1000 -> error(timeout) end, + ConnPid = receive {conn, C} -> C after 1000 -> error(timeout) end, + #{owner := Self} = gun:info(ConnPid), + gun:close(ConnPid). + shutdown_reason(_) -> doc("The last connection failure must be propagated."), {ok, Pid} = gun:open("localhost", 12345, #{retry => 0}), -- cgit v1.2.3