Browse Source

Add post-listen callback

Jan Uhlig 3 years ago
parent
commit
4687f74954

+ 38 - 0
doc/src/guide/listeners.asciidoc

@@ -177,6 +177,44 @@ file must not exist: Ranch must be able to create it.
     ]}, echo_protocol, []
     ]}, echo_protocol, []
 ).
 ).
 
 
+=== Performing additional setup steps on a listening socket
+
+If it is necessary to perform additional setup steps on the listening
+socket, it is possible to specify a function with the transport option
+`post_listen_callback`. This function will be called after the listening
+socket has been created but before accepting connections on it,
+with the socket as the single argument.
+
+The function must return either the atom `ok`, after which the listener
+will start accepting connections on the socket, or a tuple
+`{error, Reason}` which will cause the listener to fail starting with
+`Reason`.
+
+.Setting permissions on a UNIX Domain socket file
+
+[source,erlang]
+----
+PostListenCb = fun (Sock) ->
+    case ranch_tcp:sockname(Sock) of
+        {ok, {local, SockFile}} ->
+            file:change_mode(SockFile, 8#777);
+	{ok, _} ->
+	    ok;
+	Error = {error, _} ->
+            Error
+    end
+end,
+
+{ok, _} = ranch:start_listener(tcp_echo,
+    ranch_tcp, #{
+        socket_opts => [
+            {ip, {local, "/tmp/ranch_echo.sock"}},
+            {port, 0}],
+        post_listen_callback => PostListenCb},
+    echo_protocol, []
+).
+----
+
 === Accepting connections on an existing socket
 === Accepting connections on an existing socket
 
 
 If you want to accept connections on an existing socket, you can write
 If you want to accept connections on an existing socket, you can write

+ 19 - 8
doc/src/manual/ranch.asciidoc

@@ -93,14 +93,15 @@ Unique name used to refer to a listener.
 [source,erlang]
 [source,erlang]
 ----
 ----
 transport_opts(SocketOpts) = #{
 transport_opts(SocketOpts) = #{
-    connection_type   => worker | supervisor,
-    handshake_timeout => timeout(),
-    max_connections   => max_conns(),
-    logger            => module(),
-    num_acceptors     => pos_integer(),
-    num_conns_sups    => pos_integer(),
-    shutdown          => timeout() | brutal_kill,
-    socket_opts       => SocketOpts
+    connection_type      => worker | supervisor,
+    handshake_timeout    => timeout(),
+    max_connections      => max_conns(),
+    logger               => module(),
+    num_acceptors        => pos_integer(),
+    num_conns_sups       => pos_integer(),
+    post_listen_callback => fun((term()) -> ok | {error, term()}),
+    shutdown             => timeout() | brutal_kill,
+    socket_opts          => SocketOpts
 }
 }
 ----
 ----
 
 
@@ -137,6 +138,16 @@ num_conns_sups - see below::
 Number of processes that supervise connection processes.
 Number of processes that supervise connection processes.
 If not specified, defaults to be equal to `num_acceptors`.
 If not specified, defaults to be equal to `num_acceptors`.
 
 
+post_listen_callback (fun(_ListenSock) -> ok end)::
+
+A function which will be called after a listen socket has been successfully
+created, with the socket as argument. It can be used to perform any
+necessary setup steps on the socket.
++
+If the callback function returns `ok`, the listener will start accepting
+connections on the socket. If it returns `{error, Reason}`, the listener
+will fail to start.
+
 shutdown (5000)::
 shutdown (5000)::
 
 
 Maximum allowed time for children to stop on listener shutdown.
 Maximum allowed time for children to stop on listener shutdown.

+ 1 - 0
doc/src/manual/ranch.set_transport_options.asciidoc

@@ -29,6 +29,7 @@ Changes to the following options will take effect...
 * only after the listener has been suspended and resumed:
 * only after the listener has been suspended and resumed:
 ** `num_acceptors`
 ** `num_acceptors`
 ** `num_listen_sockets`
 ** `num_listen_sockets`
+** `post_listen_callback`
 ** `socket_opts`
 ** `socket_opts`
 * only when the entire listener is restarted:
 * only when the entire listener is restarted:
 ** `connection_type`
 ** `connection_type`

+ 4 - 1
src/ranch.erl

@@ -58,11 +58,12 @@
 -type transport_opts(SocketOpts) :: #{
 -type transport_opts(SocketOpts) :: #{
 	connection_type => worker | supervisor,
 	connection_type => worker | supervisor,
 	handshake_timeout => timeout(),
 	handshake_timeout => timeout(),
-	max_connections => max_conns(),
 	logger => module(),
 	logger => module(),
+	max_connections => max_conns(),
 	num_acceptors => pos_integer(),
 	num_acceptors => pos_integer(),
 	num_conns_sups => pos_integer(),
 	num_conns_sups => pos_integer(),
 	num_listen_sockets => pos_integer(),
 	num_listen_sockets => pos_integer(),
+	post_listen_callback => fun((term()) -> ok | {error, term()}),
 	shutdown => timeout() | brutal_kill,
 	shutdown => timeout() | brutal_kill,
 	socket_opts => SocketOpts
 	socket_opts => SocketOpts
 }.
 }.
@@ -131,6 +132,8 @@ validate_transport_opt(num_conns_sups, Value, _) ->
 validate_transport_opt(num_listen_sockets, Value, Opts) ->
 validate_transport_opt(num_listen_sockets, Value, Opts) ->
 	is_integer(Value) andalso Value > 0
 	is_integer(Value) andalso Value > 0
 		andalso Value =< maps:get(num_acceptors, Opts, 10);
 		andalso Value =< maps:get(num_acceptors, Opts, 10);
+validate_transport_opt(post_listen_callback, Value, _) ->
+	is_function(Value, 1);
 validate_transport_opt(shutdown, brutal_kill, _) ->
 validate_transport_opt(shutdown, brutal_kill, _) ->
 	true;
 	true;
 validate_transport_opt(shutdown, infinity, _) ->
 validate_transport_opt(shutdown, infinity, _) ->

+ 7 - 1
src/ranch_acceptors_sup.erl

@@ -77,7 +77,13 @@ start_listen_sockets(Ref, NumListenSockets, Transport, TransOpts0, Logger) when
 start_listen_socket(Ref, Transport, TransOpts, Logger) ->
 start_listen_socket(Ref, Transport, TransOpts, Logger) ->
 	case Transport:listen(TransOpts) of
 	case Transport:listen(TransOpts) of
 		{ok, Socket} ->
 		{ok, Socket} ->
-			Socket;
+			PostListenCb = maps:get(post_listen_callback, TransOpts, fun (_) -> ok end),
+			case PostListenCb(Socket) of
+				ok ->
+					Socket;
+				{error, Reason} ->
+					listen_error(Ref, Transport, TransOpts, Reason, Logger)
+			end;
 		{error, Reason} ->
 		{error, Reason} ->
 			listen_error(Ref, Transport, TransOpts, Reason, Logger)
 			listen_error(Ref, Transport, TransOpts, Reason, Logger)
 	end.
 	end.

+ 43 - 0
test/acceptor_SUITE.erl

@@ -77,6 +77,8 @@ groups() ->
 		misc_info_embedded,
 		misc_info_embedded,
 		misc_metrics,
 		misc_metrics,
 		misc_opts_logger,
 		misc_opts_logger,
+		misc_post_listen_callback,
+		misc_post_listen_callback_error,
 		misc_set_transport_options,
 		misc_set_transport_options,
 		misc_wait_for_connections,
 		misc_wait_for_connections,
 		misc_multiple_ip_local_socket_opts
 		misc_multiple_ip_local_socket_opts
@@ -340,6 +342,47 @@ misc_opts_logger(_) ->
 warning(Format, Args) ->
 warning(Format, Args) ->
 	misc_opts_logger ! {warning, Format, Args}.
 	misc_opts_logger ! {warning, Format, Args}.
 
 
+misc_post_listen_callback(_) ->
+	doc("Ensure that the post-listen callback works."),
+	Name = name(),
+	Self = self(),
+	Ref = make_ref(),
+	PostListenCb = fun (LSock) ->
+		ok = ranch_tcp:setopts(LSock, [{send_timeout, 1000}]),
+		Self ! {post_listen, Ref, LSock},
+		ok
+	end,
+	{ok, _} = ranch:start_listener(Name,
+		ranch_tcp, #{post_listen_callback => PostListenCb,
+			socket_opts => [{send_timeout, infinity}]},
+		echo_protocol, []),
+	receive
+		{post_listen, Ref, LSock} ->
+			{ok, [{send_timeout, 1000}]} = ranch_tcp:getopts(LSock, [send_timeout]),
+			ok
+	after 1000 ->
+		error(timeout)
+	end,
+	Port = ranch:get_port(Name),
+	{ok, S} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
+	ok = gen_tcp:send(S, <<"Test">>),
+	{ok, <<"Test">>} = gen_tcp:recv(S, 4, 1000),
+	ok = ranch:stop_listener(Name),
+	{error, closed} = gen_tcp:recv(S, 0, 1000),
+	%% Make sure the listener stopped.
+	{'EXIT', _} = begin catch ranch:get_port(Name) end,
+	ok.
+
+misc_post_listen_callback_error(_) ->
+	doc("Ensure that starting a listener fails when the post-listen callback returns an error."),
+	Name = name(),
+	PostListenCb = fun (_) -> {error, test} end,
+	{error, _} = ranch:start_listener(Name,
+		ranch_tcp, #{post_listen_callback => PostListenCb},
+		echo_protocol, []),
+	{'EXIT', _} = begin catch ranch:get_port(Name) end,
+	ok.
+
 misc_repeated_remove(_) ->
 misc_repeated_remove(_) ->
 	doc("Ensure repeated removal of connection does not crash the connection supervisor."),
 	doc("Ensure repeated removal of connection does not crash the connection supervisor."),
 	Name = name(),
 	Name = name(),