diff --git a/src/grpcbox_socket.erl b/src/grpcbox_socket.erl index 993ea89..9a7f247 100644 --- a/src/grpcbox_socket.erl +++ b/src/grpcbox_socket.erl @@ -30,7 +30,8 @@ init([Pool, ListenOpts, PoolOpts]) -> %% Trapping exit so can close socket in terminate/2 _ = process_flag(trap_exit, true), Opts = [{active, false}, {mode, binary}, {packet, raw}, {ip, IPAddress} | SocketOpts], - case gen_tcp:listen(Port, Opts) of + {LPort, LOpts} = maybe_adjust_port_opts_for_fdopt(Port, Opts), + case gen_tcp:listen(LPort, LOpts) of {ok, Socket} -> %% acceptor could close the socket if there is a problem MRef = monitor(port, Socket), @@ -40,6 +41,17 @@ init([Pool, ListenOpts, PoolOpts]) -> {stop, Reason} end. +maybe_adjust_port_opts_for_fdopt(Port, Opts) -> + case lists:keymember(fd, 1, Opts) of + true -> + %% If an already opened (bound) file descriptor is passed, + %% we must not set port or ip, or there will be an error + %% when it would have gotten bound again. + {0, lists:keydelete(ip, 1, Opts)}; + false -> + {Port, Opts} + end. + handle_call(Req, _, State) -> {stop, {bad_call, Req}, State}. diff --git a/test/grpcbox_SUITE.erl b/test/grpcbox_SUITE.erl index 7a3691b..9430548 100644 --- a/test/grpcbox_SUITE.erl +++ b/test/grpcbox_SUITE.erl @@ -13,6 +13,7 @@ groups() -> [{ssl, [], [unary_authenticated]}, {tcp, [], [unary_no_auth, multiple_servers, unary_garbage_collect_streams]}, + {socket_options, [], [fd_socket_option]}, {concurrent, [{repeat_until_any_fail, 5}], [unary_concurrent]}, {negative_tests, [], [unimplemented, closed_stream, generate_error, streaming_generate_error]}, {negative_ssl, [], [unauthorized]}, @@ -22,6 +23,7 @@ groups() -> all() -> [{group, ssl}, {group, tcp}, + {group, socket_options}, {group, concurrent}, {group, negative_tests}, {group, negative_ssl}, @@ -75,6 +77,14 @@ init_per_group(tcp, Config) -> transport_opts => #{}}]), application:ensure_all_started(grpcbox), Config; +init_per_group(socket_options, Config) -> + application:set_env(grpcbox, client, #{channels => [{default_channel, [{http, "localhost", 8080, []}], + #{}}]}), + application:set_env(grpcbox, servers, [#{grpc_opts => #{service_protos => [route_guide_pb], + services => #{'routeguide.RouteGuide' => + routeguide_route_guide}}, + transport_opts => #{}}]), + Config; init_per_group(concurrent, Config) -> application:set_env(grpcbox, client, #{channels => [{default_channel, [{http, "localhost", 8080, []}], #{}}]}), @@ -310,7 +320,7 @@ end_per_testcase(unary_no_auth, _Config) -> ok; end_per_testcase(multiple_servers, _Config) -> ok; -end_per_testcase(unary_garbage_collect_streams, _Config) -> +end_per_testcase(fd_socket_option, _Config) -> ok; end_per_testcase(unary_concurrent, _Config) -> ok; @@ -563,6 +573,24 @@ multiple_servers(_Config) -> unary(_Config), unary(_Config). +fd_socket_option(_Config) -> + %% Use the fd option to dynamically select a free port + {ok, Ip} = inet:getaddr("localhost", inet), + {ok, Sock} = gen_tcp:listen(0, [{ip, Ip}, inet]), + {ok, Fd} = inet:getfd(Sock), + {ok, {_ListenIp, ListenPort}} = inet:sockname(Sock), + application:set_env(grpcbox, client, #{channels => [{default_channel, + [{http, "localhost", ListenPort, []}], #{}}]}), + + application:set_env(grpcbox, servers, [#{grpc_opts => #{service_protos => [route_guide_pb], + services => #{'routeguide.RouteGuide' => + routeguide_route_guide}}, + listen_opts => #{socket_options => [{fd, Fd}]}}]), + ok = application:ensure_all_started(grpcbox), + unary(_Config), + application:stop(grpcbox), + gen_tcp:close(Sock). + unary_concurrent(Config) -> Nrs = lists:seq(1,100), ParentPid = self(),