Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions erts/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 <arpa/nameser.h> header file. */
#undef HAVE_ARPA_NAMESER_H

Expand Down
8 changes: 8 additions & 0 deletions erts/configure
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions erts/configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2243,6 +2243,10 @@ AS_CASE([$host_os],
[[#include <sys/socket.h>]])
])

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.
Expand Down
131 changes: 84 additions & 47 deletions erts/emulator/nifs/common/prim_socket_nif.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if SO_LINGER is not defined on a given platform? I see other places in this file ifdef-ing on the macro's existence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9b0fdcf. When the macro is missing the close can't linger, so it skips the dirty scheduler. Also switched the length to SOCKOPTLEN_T while at it, SOCKLEN_T falls back to size_t on Windows where getsockopt wants an int.

(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,
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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}
};


Expand Down
9 changes: 6 additions & 3 deletions erts/emulator/nifs/unix/unix_socket_syncio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
Loading