From 1c412c62ba3be17b7a818f264049a7ee7942351e Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 28 Sep 2018 18:40:08 +0200 Subject: [socket-nif] Add support for socket (level otp) buffer options Add support for otp level socket options rcvbuf, rcvctrlbuf and sndctrlbuf. These options define default sizes for these buffers. The 'rcvbuf' is used when receiving messages when calling the recv, recvfrom and recvmsg functions. The 'rcvctrlbuf' is used for the control message header info when calling the recvmsg function. The 'sndctrlbuf' is used for the control message header info when calling the sendmsg function. OTP-14831 --- erts/doc/src/socket_usage.xml | 21 +++++ erts/emulator/nifs/common/socket_nif.c | 146 ++++++++++++++++++++++++++++++++ erts/emulator/nifs/common/socket_util.c | 29 +++++++ erts/emulator/nifs/common/socket_util.h | 6 ++ erts/preloaded/ebin/socket.beam | Bin 66040 -> 66584 bytes erts/preloaded/src/socket.erl | 33 +++++++- lib/kernel/test/socket_SUITE.erl | 112 +++++++++++++++++++++++- 7 files changed, 342 insertions(+), 5 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 9785830637..23d0f319f1 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -81,6 +81,27 @@ yes none + + rcvbuf + default | pos_integer() + yes + yes + default only valid for set + + + rcvctrlbuf + default | pos_integer() + yes + yes + default only valid for set + + + sndctrlbuf + default | pos_integer() + yes + yes + default only valid for set +

Options for level socket:

diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index ccbcf63ece..876bed3135 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -501,6 +501,9 @@ typedef union { #define SOCKET_OPT_OTP_DEBUG 1 #define SOCKET_OPT_OTP_IOW 2 #define SOCKET_OPT_OTP_CTRL_PROC 3 +#define SOCKET_OPT_OTP_RCVBUF 4 +#define SOCKET_OPT_OTP_RCVCTRLBUF 6 +#define SOCKET_OPT_OTP_SNDCTRLBUF 7 #define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BINDTODEVICE 3 @@ -1004,6 +1007,15 @@ static ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, static ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, SocketDescriptor* descP, ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); static ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -1492,6 +1504,12 @@ static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -5001,6 +5019,18 @@ ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, result = nsetopt_otp_ctrl_proc(env, descP, eVal); break; + case SOCKET_OPT_OTP_RCVBUF: + result = nsetopt_otp_rcvbuf(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_RCVCTRLBUF: + result = nsetopt_otp_rcvctrlbuf(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_SNDCTRLBUF: + result = nsetopt_otp_sndctrlbuf(env, descP, eVal); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -5079,6 +5109,74 @@ ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, +/* nsetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_RECV_BUFFER_SIZE_DEFAULT, &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->rBufSz = val; + + return esock_atom_ok; +} + + + +/* nsetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT, + &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->rCtrlSz = val; + + return esock_atom_ok; +} + + + +/* nsetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT, + &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->wCtrlSz = val; + + return esock_atom_ok; +} + + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is and value as a binary). */ @@ -8169,6 +8267,18 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, result = ngetopt_otp_ctrl_proc(env, descP); break; + case SOCKET_OPT_OTP_RCVBUF: + result = ngetopt_otp_rcvbuf(env, descP); + break; + + case SOCKET_OPT_OTP_RCVCTRLBUF: + result = ngetopt_otp_rcvctrlbuf(env, descP); + break; + + case SOCKET_OPT_OTP_SNDCTRLBUF: + result = ngetopt_otp_sndctrlbuf(env, descP); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -8220,6 +8330,42 @@ ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, +/* ngetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf options + */ +static +ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->rBufSz); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf options + */ +static +ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->rCtrlSz); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf options + */ +static +ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->wCtrlSz); + + return esock_make_ok2(env, eVal); +} + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is). In this case it will have the * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()} diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 8bb725fb5b..ff50fd2384 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -1261,6 +1261,35 @@ char* esock_decode_protocol(ErlNifEnv* env, +/* +++ esock_decode_bufsz +++ + * + * Decode an buffer size. The size of a buffer is: + * + * Sz > 0 => Use provided value + * Sz => Use provided default + * + */ +extern +char* esock_decode_bufsz(ErlNifEnv* env, + ERL_NIF_TERM eVal, + size_t defSz, + size_t* sz) +{ + int val; + + if (!GET_INT(env, eVal, &val)) + return ESOCK_STR_EINVAL; + + if (val > 0) + *sz = (size_t) val; + else + *sz = defSz; + + return NULL; +} + + + /* *** esock_decode_string *** * * Decode a string value. A successful decode results in an diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index a38453e238..1b5d003155 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -157,6 +157,12 @@ char* esock_encode_protocol(ErlNifEnv* env, int type, ERL_NIF_TERM* eProtocol); +extern +char* esock_decode_bufsz(ErlNifEnv* env, + ERL_NIF_TERM eVal, + size_t defSz, + size_t* sz); + extern BOOLEAN_T esock_decode_string(ErlNifEnv* env, const ERL_NIF_TERM eString, diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index 9e6d9f4709..a0550990e3 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 652054457f..8093bad885 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -320,12 +320,15 @@ %% Should we just document it and leave it to the user? %% Or catch it in the encode functions? %% A setopt for a readonly option leads to einval? +%% Do we really need a sndbuf? -type otp_socket_option() :: debug | iow | controlling_process | rcvbuf | - sndbuf. + sndbuf | + rcvctrlbuf | + sndctrlbuf. %% Shall we have special treatment of linger?? %% read-only options: %% domain | protocol | type. @@ -645,6 +648,10 @@ -define(SOCKET_OPT_OTP_DEBUG, 1). -define(SOCKET_OPT_OTP_IOW, 2). -define(SOCKET_OPT_OTP_CTRL_PROC, 3). +-define(SOCKET_OPT_OTP_RCVBUF, 4). +%%-define(SOCKET_OPT_OTP_SNDBUF, 5). +-define(SOCKET_OPT_OTP_RCVCTRLBUF, 6). +-define(SOCKET_OPT_OTP_SNDCTRLBUF, 7). %% *** SOCKET (socket) options -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). @@ -2136,9 +2143,9 @@ getopt(#socket{ref = SockRef}, Level, Key) -> end end catch - throw:T -> - T; - error:Reason -> + throw:E:_S -> + E; + error:Reason:_Stack -> {error, Reason} % Process more? end. @@ -2401,6 +2408,18 @@ enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> V; enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) -> V; +enc_setopt_value(otp, rcvbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, rcvbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> not_supported({L, Opt, V}); @@ -2871,6 +2890,12 @@ enc_sockopt_key(otp, iow, _, _, _, _) -> ?SOCKET_OPT_OTP_IOW; enc_sockopt_key(otp, controlling_process, _, _, _, _) -> ?SOCKET_OPT_OTP_CTRL_PROC; +enc_sockopt_key(otp, rcvbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_RCVBUF; +enc_sockopt_key(otp, rcvctrlbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_RCVCTRLBUF; +enc_sockopt_key(otp, sndctrlbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_SNDCTRLBUF; enc_sockopt_key(otp = L, Opt, _, _, _, _) -> not_supported({L, Opt}); diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 0255c8ef75..d4cd144168 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -38,6 +38,9 @@ api_b_send_and_recv_tcp4/1, api_b_sendmsg_and_recvmsg_tcp4/1, + %% API Options + api_opt_simple_otp_options/1, + %% API Operation Timeout api_to_connect_tcp4/1, api_to_connect_tcp6/1, @@ -90,13 +93,15 @@ all() -> groups() -> [{api, [], api_cases()}, {api_basic, [], api_basic_cases()}, - {api_op_with_timeout, [], api_op_with_timeout_cases()} + {api_op_with_timeout, [], api_op_with_timeout_cases()}, + {api_options, [], api_options_cases()} %% {tickets, [], ticket_cases()} ]. api_cases() -> [ {group, api_basic}, + {group, api_options}, {group, api_op_with_timeout} ]. @@ -110,6 +115,11 @@ api_basic_cases() -> api_b_sendmsg_and_recvmsg_tcp4 ]. +api_options_cases() -> + [ + api_opt_simple_otp_options + ]. + api_op_with_timeout_cases() -> [ api_to_connect_tcp4, @@ -431,6 +441,92 @@ api_b_send_and_recv_tcp(Domain, Send, Recv) -> +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_options(suite) -> + []; +api_opt_simple_otp_options(doc) -> + []; +api_opt_simple_otp_options(_Config) when is_list(_Config) -> + tc_begin(api_opt_simple_otp_options), + + p("Create sockets"), + S1 = sock_open(inet, stream, tcp), + S2 = sock_open(inet, dgram, udp), + + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + p("Create dummy process"), + Pid = spawn_link(fun() -> + receive + die -> + exit(normal) + end + end), + + F = fun(Sock) -> + p("Test IOW"), + {ok, IOW} = Get(Sock, iow), + NotIOW = not IOW, + ok = Set(Sock, iow, NotIOW), + {ok, NotIOW} = Get(Sock, iow), + + p("Test rcvbuf"), + {ok, RcvBuf} = Get(Sock, rcvbuf), + RcvBuf2 = RcvBuf*2, + ok = Set(Sock, rcvbuf, RcvBuf2), + {ok, RcvBuf2} = Get(Sock, rcvbuf), + ok = Set(Sock, rcvbuf, default), + {ok, RcvBuf} = Get(Sock, rcvbuf), + + p("Test rcvctrlbuf"), + {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), + RcvCtrlBuf2 = RcvCtrlBuf*2, + ok = Set(Sock, rcvctrlbuf, RcvCtrlBuf2), + {ok, RcvCtrlBuf2} = Get(Sock, rcvctrlbuf), + ok = Set(Sock, rcvctrlbuf, default), + {ok, RcvCtrlBuf} = Get(Sock, rcvctrlbuf), + + p("Test sndctrlbuf"), + {ok, SndCtrlBuf} = Get(Sock, sndctrlbuf), + SndCtrlBuf2 = SndCtrlBuf*2, + ok = Set(Sock, sndctrlbuf, SndCtrlBuf2), + {ok, SndCtrlBuf2} = Get(Sock, sndctrlbuf), + ok = Set(Sock, sndctrlbuf, default), + {ok, RcvCtrlBuf} = Get(Sock, sndctrlbuf), + + p("Test controlling-process"), + Self = self(), + {ok, Self} = Get(Sock, controlling_process), + ok = Set(Sock, controlling_process, Pid), + {ok, Pid} = Get(Sock, controlling_process) + + end, + + p("Test stream/tcp "), + F(S1), + + p("Test dgram/udp "), + F(S2), + + p("kill dummy process"), + %% This will also close its sockets (S1 and S2), + %% This should really be tested explicitly... + Pid ! die, + + %% p("close sockets"), + %% sock_close(S1), + %% sock_close(S2), + + tc_end(). + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% This test case is intended to test the connect timeout option @@ -1067,6 +1163,20 @@ sock_accept(LSock) -> end. +sock_close(Sock) -> + try socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + p("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -- cgit v1.2.3