diff --git a/erts/config.h.in b/erts/config.h.in index ba077f7b9c7a..3660d419eeaa 100644 --- a/erts/config.h.in +++ b/erts/config.h.in @@ -462,6 +462,9 @@ /* Define to 1 if _Float16 can be converted from/to double. */ #undef FLOAT16_IS_CONVERTIBLE +/* Define to 1 if you have the 'accept4' function. */ +#undef HAVE_ACCEPT4 + /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_NAMESER_H diff --git a/erts/configure b/erts/configure index afa96f23fd0b..9ca2da32c3c4 100755 --- a/erts/configure +++ b/erts/configure @@ -21341,6 +21341,14 @@ fi ;; esac +ac_fn_c_check_func "$LINENO" "accept4" "ac_cv_func_accept4" +if test "x$ac_cv_func_accept4" = xyes +then : + printf "%s\n" "#define HAVE_ACCEPT4 1" >>confdefs.h + +fi + + saved_cppflags=$CPPFLAGS diff --git a/erts/configure.ac b/erts/configure.ac index c5113a595312..7d8b48ae8756 100644 --- a/erts/configure.ac +++ b/erts/configure.ac @@ -2243,6 +2243,10 @@ AS_CASE([$host_os], [[#include ]]) ]) +dnl Check for accept4, which accepts a socket and sets it non-blocking +dnl and close-on-exec in one call +AC_CHECK_FUNCS([accept4]) + dnl ---------------------------------------------------------------------- dnl Checks for library functions. diff --git a/erts/emulator/nifs/common/prim_socket_nif.c b/erts/emulator/nifs/common/prim_socket_nif.c index db477e17bb93..baca3b38c719 100644 --- a/erts/emulator/nifs/common/prim_socket_nif.c +++ b/erts/emulator/nifs/common/prim_socket_nif.c @@ -7606,9 +7606,9 @@ ERL_NIF_TERM nif_close(ErlNifEnv* env, * Socket (ref) - Points to the socket descriptor. */ static -ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, - int argc, - const ERL_NIF_TERM argv[]) +ERL_NIF_TERM nif_finalize_close_dirty(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) { ESockDescriptor* descP; ERL_NIF_TERM result; @@ -7644,6 +7644,56 @@ ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, } +/* Only a lingering close - SO_LINGER {onoff = true, linger > 0} - + * can block in close(), so only then do we need the dirty scheduler. + * The check reads descP->sock without the socket locks; a racing + * close/down makes the getsockopt fail and we fall back to the dirty + * path, where the proper state checks run under the locks as before. + */ +static +BOOLEAN_T finalize_close_may_block(ESockDescriptor* descP) +{ +#if defined(SO_LINGER) + struct linger lval; + SOCKOPTLEN_T lsz = sizeof(lval); + + if (descP->sock == INVALID_SOCKET) + return FALSE; + + if (sock_getopt(descP->sock, SOL_SOCKET, SO_LINGER, + (void*) &lval, &lsz) != 0) + return TRUE; + + return (lval.l_onoff != 0) && (lval.l_linger > 0); +#else + /* Without SO_LINGER a close cannot linger, so it cannot block */ + return FALSE; +#endif +} + + +static +ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ + ESockDescriptor* descP; + + ESOCK_ASSERT( argc == 1 ); + + if (! ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) { + return enif_make_badarg(env); + } + + if (finalize_close_may_block(descP)) + return enif_schedule_nif(env, "nif_finalize_close", + ERL_NIF_DIRTY_JOB_IO_BOUND, + nif_finalize_close_dirty, argc, argv); + + return nif_finalize_close_dirty(env, argc, argv); +} + + extern int esock_close_socket(ErlNifEnv* env, ESockDescriptor* descP, @@ -11426,57 +11476,44 @@ int socket_setopt(int sock, int level, int opt, int res; #if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) - int tmpIValPRIO = 0; - int tmpIValTOS = 0; - int resPRIO; - int resTOS; - SOCKOPTLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); - SOCKOPTLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); - - resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, - &tmpIValPRIO, &tmpArgSzPRIO); - resTOS = sock_getopt(sock, SOL_IP, IP_TOS, - &tmpIValTOS, &tmpArgSzTOS); + /* Setting IP_TOS makes the kernel derive a new SO_PRIORITY from it, + * so that one option - and only that one - has to have the priority + * saved and put back to keep the two looking independent to the + * user. Setting anything else leaves both alone, and paying a + * getsockopt for each of them plus a setsockopt to restore on every + * option set was most of the system calls a connection made. + */ + if ((level == SOL_IP) && (opt == IP_TOS)) { + int savedPRIO; + int resPRIO; + SOCKOPTLEN_T savedSzPRIO = sizeof(savedPRIO); - res = sock_setopt(sock, level, opt, optVal, optLen); - if (res == 0) { + resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, + &savedPRIO, &savedSzPRIO); - /* Ok, now we *maybe* need to "maybe" restore PRIO and TOS... - * maybe, possibly, ... - */ + res = sock_setopt(sock, level, opt, optVal, optLen); - if (opt != SO_PRIORITY) { - if ((opt != IP_TOS) && (resTOS == 0)) { - resTOS = sock_setopt(sock, SOL_IP, IP_TOS, - (void *) &tmpIValTOS, - tmpArgSzTOS); - res = resTOS; - } - if ((res == 0) && (resPRIO == 0)) { - resPRIO = sock_setopt(sock, SOL_SOCKET, SO_PRIORITY, - &tmpIValPRIO, - tmpArgSzPRIO); - - /* Some kernels set a SO_PRIORITY by default - * that you are not permitted to reset, - * silently ignore this error condition. - */ + if ((res == 0) && (resPRIO == 0)) { + resPRIO = sock_setopt(sock, SOL_SOCKET, SO_PRIORITY, + &savedPRIO, savedSzPRIO); - if ((resPRIO != 0) && (sock_errno() == EPERM)) { - res = 0; - } else { - res = resPRIO; - } - } - } - } - -#else + /* Some kernels set a SO_PRIORITY by default + * that you are not permitted to reset, + * silently ignore this error condition. + */ - res = sock_setopt(sock, level, opt, optVal, optLen); + if ((resPRIO != 0) && (sock_errno() == EPERM)) + res = 0; + else + res = resPRIO; + } + return res; + } #endif + res = sock_setopt(sock, level, opt, optVal, optLen); + return res; } @@ -18744,7 +18781,7 @@ ErlNifFunc esock_funcs[] = * is called after the close *select* has "completed". */ {"nif_cancel", 3, nif_cancel, 0}, - {"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND} + {"nif_finalize_close", 1, nif_finalize_close, 0} }; diff --git a/erts/emulator/nifs/unix/unix_socket_syncio.c b/erts/emulator/nifs/unix/unix_socket_syncio.c index 3049d287f775..091adbb3a040 100644 --- a/erts/emulator/nifs/unix/unix_socket_syncio.c +++ b/erts/emulator/nifs/unix/unix_socket_syncio.c @@ -195,10 +195,11 @@ * ======================================================================== * */ -#ifdef HAS_ACCEPT4 -// We have to figure out what the flags are... +#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) #define sock_accept(s, addr, len) \ - accept4((s), (addr), (len), (SOCK_CLOEXEC)) + accept4((s), (addr), (len), (SOCK_CLOEXEC | SOCK_NONBLOCK)) +/* The accepted socket is already non-blocking */ +#define ESSIO_ACCEPTED_NONBLOCK 1 #else #define sock_accept(s, addr, len) accept((s), (addr), (len)) #endif @@ -2863,7 +2864,9 @@ BOOLEAN_T essio_accept_accepted(ErlNifEnv* env, &accDescP->ctrlPid, &accDescP->ctrlMon) == 0 ); +#ifndef ESSIO_ACCEPTED_NONBLOCK SET_NONBLOCKING(accDescP->sock); +#endif accDescP->writeState |= ESOCK_STATE_CONNECTED;