File 2983-erts-Add-new-enif_select_error.patch of Package erlang
From 75d49429e0892c8747d3dbf43ac177c6cc807c18 Mon Sep 17 00:00:00 2001
From: Sverker Eriksson <sverker@erlang.org>
Date: Fri, 11 Dec 2020 19:03:06 +0100
Subject: [PATCH 3/3] erts: Add new enif_select_error
Return ERL_NIF_SELECT_NOTSUP if the underlying poll implementation
does not support error events (not Linux).
---
erts/emulator/beam/atom.names | 1 +
erts/emulator/beam/erl_drv_nif.h | 3 +-
erts/emulator/beam/erl_nif.h | 2 +
erts/emulator/beam/erl_nif_api_funcs.h | 3 +
erts/emulator/sys/common/erl_check_io.c | 73 +++++++++++++---
erts/emulator/sys/common/erl_check_io.h | 1 +
erts/emulator/test/nif_SUITE.erl | 85 ++++++++++++++++++-
erts/emulator/test/nif_SUITE_data/nif_SUITE.c | 26 +++++-
8 files changed, 176 insertions(+), 18 deletions(-)
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names
index a941534f3b..7febb56e28 100644
--- a/erts/emulator/beam/atom.names
+++ b/erts/emulator/beam/atom.names
@@ -561,6 +561,7 @@ atom re
atom re_pattern
atom re_run_trap
atom read_concurrency
+atom ready_error
atom ready_input
atom ready_output
atom reason
diff --git a/erts/emulator/beam/erl_drv_nif.h b/erts/emulator/beam/erl_drv_nif.h
index bf14d824b1..53d1a3a531 100644
--- a/erts/emulator/beam/erl_drv_nif.h
+++ b/erts/emulator/beam/erl_drv_nif.h
@@ -55,7 +55,8 @@ enum ErlNifSelectFlags {
ERL_NIF_SELECT_WRITE = (1 << 1),
ERL_NIF_SELECT_STOP = (1 << 2),
ERL_NIF_SELECT_CANCEL = (1 << 3),
- ERL_NIF_SELECT_CUSTOM_MSG= (1 << 4)
+ ERL_NIF_SELECT_CUSTOM_MSG= (1 << 4),
+ ERL_NIF_SELECT_ERROR = (1 << 5)
};
/*
diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h
index 5f25960053..1876193c6c 100644
--- a/erts/emulator/beam/erl_nif.h
+++ b/erts/emulator/beam/erl_nif.h
@@ -174,6 +174,8 @@ typedef int ErlNifEvent;
#define ERL_NIF_SELECT_FAILED (1 << 3)
#define ERL_NIF_SELECT_READ_CANCELLED (1 << 4)
#define ERL_NIF_SELECT_WRITE_CANCELLED (1 << 5)
+#define ERL_NIF_SELECT_ERROR_CANCELLED (1 << 6)
+#define ERL_NIF_SELECT_NOTSUP (1 << 7)
typedef enum
{
diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h
index f719dfe868..d8debba6a5 100644
--- a/erts/emulator/beam/erl_nif_api_funcs.h
+++ b/erts/emulator/beam/erl_nif_api_funcs.h
@@ -642,6 +642,9 @@ static ERL_NIF_INLINE ERL_NIF_TERM enif_make_list9(ErlNifEnv* env,
# define enif_select_write(ENV, E, OBJ, PID, MSG, MSG_ENV) \
enif_select_x(ENV, E, ERL_NIF_SELECT_WRITE | ERL_NIF_SELECT_CUSTOM_MSG, \
OBJ, PID, MSG, MSG_ENV)
+# define enif_select_error(ENV, E, OBJ, PID, MSG, MSG_ENV) \
+ enif_select_x(ENV, E, ERL_NIF_SELECT_ERROR | ERL_NIF_SELECT_CUSTOM_MSG, \
+ OBJ, PID, MSG, MSG_ENV)
#if SIZEOF_LONG == 8
# define enif_get_int64 enif_get_long
diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c
index fb0b6699ef..02ecd66cbc 100644
--- a/erts/emulator/sys/common/erl_check_io.c
+++ b/erts/emulator/sys/common/erl_check_io.c
@@ -91,6 +91,7 @@ typedef enum {
#else
ERTS_EV_FLAG_FALLBACK = ERTS_EV_FLAG_CLEAR,
#endif
+ ERTS_EV_FLAG_WANT_ERROR = 0x10, /* ERL_NIF_SELECT_ERROR turned on */
/* Combinations */
ERTS_EV_FLAG_USED_FALLBACK = ERTS_EV_FLAG_USED | ERTS_EV_FLAG_FALLBACK,
@@ -366,6 +367,7 @@ alloc_nif_select_data(void)
sizeof(ErtsNifSelectDataState));
dsp->in.pid = NIL;
dsp->out.pid = NIL;
+ dsp->err.pid = NIL;
return dsp;
}
@@ -717,6 +719,7 @@ deselect(ErtsDrvEventState *state, int mode)
case ERTS_EV_TYPE_NIF:
clear_select_event(&state->driver.nif->in);
clear_select_event(&state->driver.nif->out);
+ clear_select_event(&state->driver.nif->err);
enif_release_resource(state->driver.stop.resource->data);
state->driver.stop.resource = NULL;
break;
@@ -1092,7 +1095,7 @@ enif_select_x(ErlNifEnv* env,
}
on = 0;
mode = ERL_DRV_READ | ERL_DRV_WRITE | ERL_DRV_USE;
- ctl_events = ERTS_POLL_EV_IN | ERTS_POLL_EV_OUT;
+ ctl_events = ERTS_POLL_EV_IN | ERTS_POLL_EV_OUT | ERTS_POLL_EV_ERR;
ctl_op = ERTS_POLL_OP_DEL;
}
else {
@@ -1104,6 +1107,14 @@ enif_select_x(ErlNifEnv* env,
if (mode & ERL_DRV_WRITE) {
ctl_events |= ERTS_POLL_EV_OUT;
}
+ if (mode & ERL_NIF_SELECT_ERROR) {
+#if (!ERTS_ENABLE_KERNEL_POLL || ERTS_POLL_USE_EPOLL) && defined(ERTS_USE_POLL)
+ ctl_events |= ERTS_POLL_EV_ERR;
+#else
+ erts_mtx_unlock(fd_mtx(fd));
+ return INT_MIN | ERL_NIF_SELECT_NOTSUP;
+#endif
+ }
}
state = new_drv_ev_state(state,fd);
@@ -1152,6 +1163,8 @@ enif_select_x(ErlNifEnv* env,
state->active_events |= ctl_events;
if (state->type == ERTS_EV_TYPE_NONE)
ctl_op = ERTS_POLL_OP_ADD;
+ if (ctl_events & ERTS_POLL_EV_ERR)
+ state->flags |= ERTS_EV_FLAG_WANT_ERROR;
}
else {
ctl_events &= old_events;
@@ -1162,7 +1175,8 @@ enif_select_x(ErlNifEnv* env,
if (ctl_events || ctl_op == ERTS_POLL_OP_DEL) {
ErtsPollEvents new_events;
- new_events = erts_io_control_wakeup(state, ctl_op,
+ new_events = erts_io_control_wakeup(state,
+ ctl_op,
state->active_events,
&wake_poller);
@@ -1172,6 +1186,7 @@ enif_select_x(ErlNifEnv* env,
state->flags = 0;
state->driver.nif->in.pid = NIL;
state->driver.nif->out.pid = NIL;
+ state->driver.nif->err.pid = NIL;
state->driver.stop.resource = NULL;
}
ret = INT_MIN | ERL_NIF_SELECT_FAILED;
@@ -1203,6 +1218,11 @@ enif_select_x(ErlNifEnv* env,
if (mode & ERL_DRV_WRITE) {
prepare_select_msg(&state->driver.nif->out, mode, recipient,
resource, msg, msg_env, am_ready_output);
+ msg_env = NULL;
+ }
+ if (mode & ERL_NIF_SELECT_ERROR) {
+ prepare_select_msg(&state->driver.nif->err, mode, recipient,
+ resource, msg, msg_env, am_ready_error);
}
ret = 0;
}
@@ -1219,6 +1239,11 @@ enif_select_x(ErlNifEnv* env,
clear_select_event(&state->driver.nif->out);
ret |= ERL_NIF_SELECT_WRITE_CANCELLED;
}
+ if (mode & ERL_NIF_SELECT_ERROR
+ && is_not_nil(state->driver.nif->err.pid)) {
+ clear_select_event(&state->driver.nif->err);
+ ret |= ERL_NIF_SELECT_ERROR_CANCELLED;
+ }
}
if (mode & ERL_NIF_SELECT_STOP) {
ASSERT(state->events==0);
@@ -1249,6 +1274,7 @@ enif_select_x(ErlNifEnv* env,
state->type = ERTS_EV_TYPE_STOP_NIF;
ret |= ERL_NIF_SELECT_STOP_SCHEDULED;
}
+ state->flags &= ~ERTS_EV_FLAG_WANT_ERROR;
}
else
ASSERT(mode & ERL_NIF_SELECT_CANCEL);
@@ -1371,8 +1397,9 @@ steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
break;
}
case ERTS_EV_TYPE_NIF: {
- Eterm iid = state->driver.nif->in.pid;
- Eterm oid = state->driver.nif->out.pid;
+ const Eterm iid = state->driver.nif->in.pid;
+ const Eterm oid = state->driver.nif->out.pid;
+ const Eterm eid = state->driver.nif->err.pid;
const char* with = "with";
ErlNifResourceType* rt = state->driver.stop.resource->type;
@@ -1384,6 +1411,10 @@ steal(erts_dsprintf_buf_t *dsbufp, ErtsDrvEventState *state, int mode)
}
if (is_not_nil(oid)) {
erts_dsprintf(dsbufp, " %s out-pid %T", with, oid);
+ with = "and";
+ }
+ if (is_not_nil(eid)) {
+ erts_dsprintf(dsbufp, " %s err-pid %T", with, eid);
}
deselect(state, 0);
erts_dsprintf(dsbufp, "\n");
@@ -1738,7 +1769,8 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
DEBUG_PRINT_FD("triggered %s", state, ev2str(revents));
- if (revents & ERTS_POLL_EV_ERR) {
+ if (revents & ERTS_POLL_EV_ERR
+ && !(state->flags & ERTS_EV_FLAG_WANT_ERROR)) {
/*
* Handle error events by triggering all in/out events
* that has been selected on.
@@ -1812,13 +1844,14 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
}
case ERTS_EV_TYPE_NIF: { /* Requested via enif_select()... */
- struct erts_nif_select_event in = {NIL};
- struct erts_nif_select_event out = {NIL};
+ struct erts_nif_select_event in_ev = {NIL};
+ struct erts_nif_select_event out_ev = {NIL};
+ struct erts_nif_select_event err_ev = {NIL};
- if (revents & (ERTS_POLL_EV_IN|ERTS_POLL_EV_OUT)) {
+ if (revents & (ERTS_POLL_EV_IN | ERTS_POLL_EV_OUT | ERTS_POLL_EV_ERR)) {
if (revents & ERTS_POLL_EV_OUT) {
if (is_not_nil(state->driver.nif->out.pid)) {
- out = state->driver.nif->out;
+ out_ev = state->driver.nif->out;
resource = state->driver.stop.resource;
state->driver.nif->out.pid = NIL;
state->driver.nif->out.mp = NULL;
@@ -1826,12 +1859,20 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
}
if (revents & ERTS_POLL_EV_IN) {
if (is_not_nil(state->driver.nif->in.pid)) {
- in = state->driver.nif->in;
+ in_ev = state->driver.nif->in;
resource = state->driver.stop.resource;
state->driver.nif->in.pid = NIL;
state->driver.nif->in.mp = NULL;
}
}
+ if (revents & ERTS_POLL_EV_ERR) {
+ if (is_not_nil(state->driver.nif->err.pid)) {
+ err_ev = state->driver.nif->err;
+ resource = state->driver.stop.resource;
+ state->driver.nif->err.pid = NIL;
+ state->driver.nif->err.mp = NULL;
+ }
+ }
state->events &= ~revents;
}
else if (revents & ERTS_POLL_EV_NVAL) {
@@ -1841,11 +1882,14 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only
erts_mtx_unlock(fd_mtx(fd));
- if (is_not_nil(in.pid)) {
- send_select_msg(&in);
+ if (is_not_nil(in_ev.pid)) {
+ send_select_msg(&in_ev);
+ }
+ if (is_not_nil(out_ev.pid)) {
+ send_select_msg(&out_ev);
}
- if (is_not_nil(out.pid)) {
- send_select_msg(&out);
+ if (is_not_nil(err_ev.pid)) {
+ send_select_msg(&err_ev);
}
continue;
}
@@ -2724,6 +2768,7 @@ static int erts_debug_print_checkio_state(erts_dsprintf_buf_t *dsbufp,
#endif
erts_dsprintf(dsbufp, " inpid=%T", state->driver.nif->in.pid);
erts_dsprintf(dsbufp, " outpid=%T", state->driver.nif->out.pid);
+ erts_dsprintf(dsbufp, " errpid=%T", state->driver.nif->err.pid);
r = state->driver.stop.resource;
erts_dsprintf(dsbufp, " resource=%p(%T:%T)", r, r->type->module, r->type->name);
}
diff --git a/erts/emulator/sys/common/erl_check_io.h b/erts/emulator/sys/common/erl_check_io.h
index ac285119bc..b96f4f9609 100644
--- a/erts/emulator/sys/common/erl_check_io.h
+++ b/erts/emulator/sys/common/erl_check_io.h
@@ -148,6 +148,7 @@ struct erts_nif_select_event {
typedef struct {
struct erts_nif_select_event in;
struct erts_nif_select_event out;
+ struct erts_nif_select_event err;
} ErtsNifSelectDataState;
#endif /* #ifndef ERL_CHECK_IO_INTERNAL__ */
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index df4876a294..147b15bb59 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -37,6 +37,7 @@
t_call_nif_early/1,
load_traced_nif/1,
select/1, select_steal/1,
+ select_error/1,
monitor_process_a/1,
monitor_process_b/1,
monitor_process_c/1,
@@ -91,7 +92,7 @@ all() ->
[{group, G} || G <- api_groups()]
++
[reload_error, heap_frag, types, many_args,
- select, select_steal,
+ {group, select},
{group, monitor},
monitor_frenzy,
hipe,
@@ -135,7 +136,10 @@ groups() ->
monitor_process_c,
monitor_process_d,
monitor_process_purge,
- demonitor_process]}].
+ demonitor_process]},
+ {select, [], [select,
+ select_error,
+ select_steal]}].
api_groups() -> [api_latest, api_2_4, api_2_0].
@@ -639,6 +643,7 @@ load_traced_nif(Config) when is_list(Config) ->
-define(ERL_NIF_SELECT_STOP, (1 bsl 2)).
-define(ERL_NIF_SELECT_CANCEL, (1 bsl 3)).
-define(ERL_NIF_SELECT_CUSTOM_MSG, (1 bsl 4)).
+-define(ERL_NIF_SELECT_ERROR, (1 bsl 5)).
-define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 0)).
-define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 1)).
@@ -646,6 +651,8 @@ load_traced_nif(Config) when is_list(Config) ->
-define(ERL_NIF_SELECT_FAILED, (1 bsl 3)).
-define(ERL_NIF_SELECT_READ_CANCELLED, (1 bsl 4)).
-define(ERL_NIF_SELECT_WRITE_CANCELLED, (1 bsl 5)).
+-define(ERL_NIF_SELECT_ERROR_CANCELLED, (1 bsl 6)).
+-define(ERL_NIF_SELECT_NOTSUP, (1 bsl 7)).
select(Config) when is_list(Config) ->
ensure_lib_loaded(Config),
@@ -781,6 +788,79 @@ receive_ready(_, Msg, _) ->
[Got] = flush(),
{true,_,_} = {Got=:=Msg, Got, Msg}.
+
+select_error(Config) when is_list(Config) ->
+ ensure_lib_loaded(Config),
+
+ case os:type() of
+ {unix,linux} ->
+ select_error_do();
+ _ ->
+ {skipped, "not Linux"}
+ end.
+
+select_error_do() ->
+ RefBin = list_to_binary(lists:duplicate(100, $x)),
+
+ select_error_do1(0, make_ref(), null),
+ select_error_do1(?ERL_NIF_SELECT_CUSTOM_MSG, [a, "list", RefBin], null),
+ select_error_do1(?ERL_NIF_SELECT_CUSTOM_MSG, [a, "list", RefBin], alloc_env),
+ ok.
+
+select_error_do1(Flag, Ref, MsgEnv) ->
+ {{R, _R_ptr}, {W, W_ptr}} = pipe_nif(),
+ ok = write_nif(W, <<"hej">>),
+ <<"hej">> = read_nif(R, 3),
+
+ %% Wait for error on write end when read end is closed
+ 0 = select_nif(W, ?ERL_NIF_SELECT_ERROR bor Flag, W, null, Ref, MsgEnv),
+ [] = flush(0),
+ 0 = close_nif(R),
+ receive_ready(W, Ref, ready_error),
+
+ check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
+ [{fd_resource_stop, W_ptr, _}] = flush(),
+ {1, {W_ptr,_}} = last_fd_stop_call(),
+ true = is_closed_nif(W),
+ select_error_do2(Flag, Ref, MsgEnv).
+
+select_error_do2(Flag, Ref, MsgEnv) ->
+ {{R, _R_ptr}, {W, W_ptr}} = pipe_nif(),
+ ok = write_nif(W, <<"hej">>),
+ <<"hej">> = read_nif(R, 3),
+
+ %% Same again but test cancel of error works
+ 0 = select_nif(W, ?ERL_NIF_SELECT_ERROR bor Flag, W, null, Ref, MsgEnv),
+ ?ERL_NIF_SELECT_ERROR_CANCELLED =
+ select_nif(W, ?ERL_NIF_SELECT_ERROR bor ?ERL_NIF_SELECT_CANCEL, W, null, Ref, null),
+ 0 = close_nif(R),
+ [] = flush(0),
+ 0 = select_nif(W, ?ERL_NIF_SELECT_ERROR bor Flag, W, null, Ref, MsgEnv),
+ receive_ready(W, Ref, ready_error),
+
+ check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
+ [{fd_resource_stop, W_ptr, _}] = flush(),
+ {1, {W_ptr,_}} = last_fd_stop_call(),
+ true = is_closed_nif(W),
+ ok.
+
+-ifdef(NOT_DEFINED).
+check_select_error_supported() ->
+ {{_R, _R_ptr}, {W, W_ptr}} = pipe_nif(),
+ Ref = make_ref(),
+ case select_nif(W, ?ERL_NIF_SELECT_ERROR, W, null, Ref, null) of
+ 0 ->
+ check_stop_ret(select_nif(W, ?ERL_NIF_SELECT_STOP, W, null, Ref, null)),
+ [{fd_resource_stop, W_ptr, _}] = flush(),
+ {1, {W_ptr,_}} = last_fd_stop_call(),
+ true = is_closed_nif(W),
+ true;
+
+ Err when Err < 0, (Err band ?ERL_NIF_SELECT_NOTSUP) =/= 0 ->
+ false
+ end.
+-endif.
+
%% @doc The stealing child process for the select_steal test. Duplicates given
%% W/RFds and runs select on them to steal
select_steal_child_process(Parent, RFd) ->
@@ -3730,6 +3810,7 @@ dupe_resource_nif(_) -> ?nif_stub.
pipe_nif() -> ?nif_stub.
write_nif(_,_) -> ?nif_stub.
read_nif(_,_) -> ?nif_stub.
+close_nif(_) -> ?nif_stub.
is_closed_nif(_) -> ?nif_stub.
clear_select_nif(_) -> ?nif_stub.
last_fd_stop_call() -> ?nif_stub.
diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
index 93708fa99c..2c089b430c 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -2550,7 +2550,6 @@ static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
ref_or_msg = enif_make_copy(msg_env, ref_or_msg);
}
- fdr->was_selected = 1;
enif_self(env, &fdr->pid);
switch (mode) {
case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_READ:
@@ -2559,10 +2558,17 @@ static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_WRITE:
retval = enif_select_write(env, fdr->fd, obj, pid, ref_or_msg, msg_env);
break;
+ case ERL_NIF_SELECT_CUSTOM_MSG | ERL_NIF_SELECT_ERROR:
+ retval = enif_select_error(env, fdr->fd, obj, pid, ref_or_msg, msg_env);
+ break;
default:
retval = enif_select(env, fdr->fd, mode, obj, pid, ref_or_msg);
}
+ if (retval >= 0) {
+ fdr->was_selected = 1;
+ }
+
if (msg_env)
enif_free_env(msg_env);
@@ -2698,6 +2704,23 @@ static ERL_NIF_TERM read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
}
}
+static ERL_NIF_TERM close_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ struct fd_resource* fdr;
+ int ret;
+
+ if (!get_fd(env, argv[0], &fdr))
+ return enif_make_badarg(env);
+
+ assert(fdr->fd > 0);
+ assert(!fdr->was_selected);
+
+ ret = close(fdr->fd);
+ fdr->fd = -1;
+
+ return enif_make_int(env, ret);
+}
+
static ERL_NIF_TERM is_closed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
struct fd_resource* fdr;
@@ -3729,6 +3752,7 @@ static ErlNifFunc nif_funcs[] =
{"write_nif", 2, write_nif},
{"dupe_resource_nif", 1, dupe_resource_nif},
{"read_nif", 2, read_nif},
+ {"close_nif", 1, close_nif},
{"is_closed_nif", 1, is_closed_nif},
{"clear_select_nif", 1, clear_select_nif},
#endif
--
2.26.2