From 31d26cfc44e0b0bb84879e7043db4dcf4e0e66cc Mon Sep 17 00:00:00 2001 From: Sverker Eriksson Date: Fri, 20 Jul 2018 15:43:14 +0200 Subject: erts: Add ERL_NIF_SELECT_CANCEL flag for enif_select to deselect read and/or writes without stop callback. --- erts/doc/src/erl_nif.xml | 30 +++++++++++-- erts/emulator/beam/erl_drv_nif.h | 3 +- erts/emulator/beam/erl_nif.h | 2 + erts/emulator/sys/common/erl_check_io.c | 78 +++++++++++++++++++++------------ erts/emulator/test/nif_SUITE.erl | 22 +++++++++- 5 files changed, 100 insertions(+), 35 deletions(-) diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index a20b8ee884..a5826307f7 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -3037,7 +3037,8 @@ enif_map_iterator_destroy(env, &iter);

Argument mode describes the type of events to wait for. It can be ERL_NIF_SELECT_READ, ERL_NIF_SELECT_WRITE or a bitwise OR combination to wait for both. It can also be ERL_NIF_SELECT_STOP - which is described further below. When a read or write event is triggered, + or ERL_NIF_SELECT_CANCEL which are described further + below. When a read or write event is triggered, a notification message like this is sent to the process identified by pid:

{select, Obj, Ref, ready_input | ready_output} @@ -3058,13 +3059,21 @@ enif_map_iterator_destroy(env, &iter);

The notifications are one-shot only. To receive further notifications of the same type (read or write), repeated calls to enif_select must be made after receiving each notification.

+

ERL_NIF_SELECT_CANCEL can be used to cancel previously + selected events. It must be used in a bitwise OR combination with + ERL_NIF_SELECT_READ and/or ERL_NIF_SELECT_WRITE to + indicate which type of event to cancel. The return value will + tell if the event was actualy cancelled or if a notification may + already have been sent.

Use ERL_NIF_SELECT_STOP as mode in order to safely close an event object that has been passed to enif_select. The stop callback of the resource obj will be called when it is safe to close the event object. This safe way of closing event objects must be used - even if all notifications have been received and no further calls to - enif_select have been made.

+ even if all notifications have been received (or cancelled) and no + further calls to enif_select have been made. + ERL_NIF_SELECT_STOP will first cancel any selected events + before it calls or schedules the stop callback.

The first call to enif_select for a specific OS event will establish a relation between the event object and the containing resource. All subsequent calls for an event must pass its containing resource as argument @@ -3086,7 +3095,15 @@ enif_map_iterator_destroy(env, &iter); The stop callback was called directly by enif_select. ERL_NIF_SELECT_STOP_SCHEDULED The stop callback was scheduled to run on some other thread - or later by this thread. + or later by this thread. + ERL_NIF_SELECT_READ_CANCELLED + A read event was cancelled by ERL_NIF_SELECT_CANCEL or + ERL_NIF_SELECT_STOP and is guaranteed not to generate a + ready_input notification message. + ERL_NIF_SELECT_WRITE_CANCELLED + A write event was cancelled by ERL_NIF_SELECT_CANCEL or + ERL_NIF_SELECT_STOP and is guaranteed not to generate a + ready_output notification message.

Returns a negative value if the call failed where the following bits can be set:

@@ -3112,6 +3129,11 @@ if (retval & ERL_NIF_SELECT_STOP_CALLED) { } +

The mode flag ERL_NIF_SELECT_CANCEL and the return flags + ERL_NIF_SELECT_READ_CANCELLED and + ERL_NIF_SELECT_WRITE_CANCELLED were introduced in erts-11.0 + (OTP-22.0).

+
diff --git a/erts/emulator/beam/erl_drv_nif.h b/erts/emulator/beam/erl_drv_nif.h index 31b4817fb1..9ef7c39d41 100644 --- a/erts/emulator/beam/erl_drv_nif.h +++ b/erts/emulator/beam/erl_drv_nif.h @@ -53,7 +53,8 @@ typedef enum { enum ErlNifSelectFlags { ERL_NIF_SELECT_READ = (1 << 0), ERL_NIF_SELECT_WRITE = (1 << 1), - ERL_NIF_SELECT_STOP = (1 << 2) + ERL_NIF_SELECT_STOP = (1 << 2), + ERL_NIF_SELECT_CANCEL = (1 << 3) }; /* diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h index 4c09496ef1..c5227a0c23 100644 --- a/erts/emulator/beam/erl_nif.h +++ b/erts/emulator/beam/erl_nif.h @@ -160,6 +160,8 @@ typedef int ErlNifEvent; #define ERL_NIF_SELECT_STOP_SCHEDULED (1 << 1) #define ERL_NIF_SELECT_INVALID_EVENT (1 << 2) #define ERL_NIF_SELECT_FAILED (1 << 3) +#define ERL_NIF_SELECT_READ_CANCELLED (1 << 4) +#define ERL_NIF_SELECT_WRITE_CANCELLED (1 << 5) typedef enum { diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c index 3e77dce1cd..b03e19ee71 100644 --- a/erts/emulator/sys/common/erl_check_io.c +++ b/erts/emulator/sys/common/erl_check_io.c @@ -916,7 +916,7 @@ enif_select(ErlNifEnv* env, ctl_op = ERTS_POLL_OP_DEL; } else { - on = 1; + on = !(mode & ERL_NIF_SELECT_CANCEL); ASSERT(mode); if (mode & ERL_DRV_READ) { ctl_events |= ERTS_POLL_EV_IN; @@ -1042,38 +1042,51 @@ enif_select(ErlNifEnv* env, ret = 0; } else { /* off */ + ret = 0; if (state->type == ERTS_EV_TYPE_NIF) { - state->driver.nif->in.pid = NIL; - state->driver.nif->out.pid = NIL; - } - ASSERT(state->events==0); - if (!wake_poller) { - /* - * Safe to close fd now as it is not in pollset - * or there was no need to eject fd (kernel poll) - */ - if (state->type == ERTS_EV_TYPE_NIF) { - ASSERT(state->driver.stop.resource == resource); - call_stop = CALL_STOP_AND_RELEASE; - state->driver.stop.resource = NULL; + if (mode & ERL_NIF_SELECT_READ + && is_not_nil(state->driver.nif->in.pid)) { + state->driver.nif->in.pid = NIL; + ret |= ERL_NIF_SELECT_READ_CANCELLED; } - else { - ASSERT(!state->driver.stop.resource); - call_stop = CALL_STOP; + if (mode & ERL_NIF_SELECT_WRITE + && is_not_nil(state->driver.nif->out.pid)) { + state->driver.nif->out.pid = NIL; + ret |= ERL_NIF_SELECT_WRITE_CANCELLED; } - state->type = ERTS_EV_TYPE_NONE; - ret = ERL_NIF_SELECT_STOP_CALLED; } - else { - /* Not safe to close fd, postpone stop_select callback. */ - if (state->type == ERTS_EV_TYPE_NONE) { - ASSERT(!state->driver.stop.resource); - state->driver.stop.resource = resource; - enif_keep_resource(resource); + if (mode & ERL_NIF_SELECT_STOP) { + ASSERT(state->events==0); + if (!wake_poller) { + /* + * Safe to close fd now as it is not in pollset + * or there was no need to eject fd (kernel poll) + */ + if (state->type == ERTS_EV_TYPE_NIF) { + ASSERT(state->driver.stop.resource == resource); + call_stop = CALL_STOP_AND_RELEASE; + state->driver.stop.resource = NULL; + } + else { + ASSERT(!state->driver.stop.resource); + call_stop = CALL_STOP; + } + state->type = ERTS_EV_TYPE_NONE; + ret |= ERL_NIF_SELECT_STOP_CALLED; + } + else { + /* Not safe to close fd, postpone stop_select callback. */ + if (state->type == ERTS_EV_TYPE_NONE) { + ASSERT(!state->driver.stop.resource); + state->driver.stop.resource = resource; + enif_keep_resource(resource); + } + state->type = ERTS_EV_TYPE_STOP_NIF; + ret |= ERL_NIF_SELECT_STOP_SCHEDULED; } - state->type = ERTS_EV_TYPE_STOP_NIF; - ret = ERL_NIF_SELECT_STOP_SCHEDULED; } + else + ASSERT(mode & ERL_NIF_SELECT_CANCEL); } done: @@ -1251,7 +1264,8 @@ print_nif_select_op(erts_dsprintf_buf_t *dsbufp, (int) fd, mode & ERL_NIF_SELECT_READ ? " READ" : "", mode & ERL_NIF_SELECT_WRITE ? " WRITE" : "", - mode & ERL_NIF_SELECT_STOP ? " STOP" : "", + (mode & ERL_NIF_SELECT_STOP ? " STOP" + : (mode & ERL_NIF_SELECT_CANCEL ? " CANCEL" : "")), resource->type->module, resource->type->name, ref); @@ -2333,10 +2347,16 @@ drvmode2str(int mode) { static ERTS_INLINE char * nifmode2str(enum ErlNifSelectFlags mode) { + if (mode & ERL_NIF_SELECT_STOP) + return "STOP"; switch (mode) { case ERL_NIF_SELECT_READ: return "READ"; case ERL_NIF_SELECT_WRITE: return "WRITE"; - case ERL_NIF_SELECT_STOP: return "STOP"; + case ERL_NIF_SELECT_READ|ERL_NIF_SELECT_WRITE: return "READ|WRITE"; + case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_READ: return "CANCEL|READ"; + case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_WRITE: return "CANCEL|WRITE"; + case ERL_NIF_SELECT_CANCEL|ERL_NIF_SELECT_READ|ERL_NIF_SELECT_WRITE: + return "CANCEL|READ|WRITE"; default: return "UNKNOWN"; } } diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index 7c85cf2259..87cf7bf81d 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -484,12 +484,14 @@ t_on_load(Config) when is_list(Config) -> -define(ERL_NIF_SELECT_READ, (1 bsl 0)). -define(ERL_NIF_SELECT_WRITE, (1 bsl 1)). -define(ERL_NIF_SELECT_STOP, (1 bsl 2)). +-define(ERL_NIF_SELECT_CANCEL, (1 bsl 3)). -define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 0)). -define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 1)). -define(ERL_NIF_SELECT_INVALID_EVENT, (1 bsl 2)). -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)). select(Config) when is_list(Config) -> ensure_lib_loaded(Config), @@ -515,7 +517,16 @@ select(Config) when is_list(Config) -> end), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Pid,Ref), {Pid, done} = receive_any(1000), + + %% Cancel read + 0 = select_nif(R,?ERL_NIF_SELECT_READ bor ?ERL_NIF_SELECT_CANCEL,R,null,Ref), <<"hej">> = read_nif(R, 3), + 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref), + ?ERL_NIF_SELECT_READ_CANCELLED = + select_nif(R,?ERL_NIF_SELECT_READ bor ?ERL_NIF_SELECT_CANCEL,R,null,Ref), + ok = write_nif(W, <<"hej again">>), + [] = flush(0), + <<"hej again">> = read_nif(R, 9), %% Wait for write Written = write_full(W, $a), @@ -524,6 +535,15 @@ select(Config) when is_list(Config) -> Written = read_nif(R,byte_size(Written)), [{select, W, Ref, ready_output}] = flush(), + %% Cancel write + 0 = select_nif(W,?ERL_NIF_SELECT_WRITE bor ?ERL_NIF_SELECT_CANCEL,W,null,Ref), + Written2 = write_full(W, $b), + 0 = select_nif(W,?ERL_NIF_SELECT_WRITE,W,null,Ref), + ?ERL_NIF_SELECT_WRITE_CANCELLED = + select_nif(W,?ERL_NIF_SELECT_WRITE bor ?ERL_NIF_SELECT_CANCEL,W,null,Ref), + Written2 = read_nif(R,byte_size(Written2)), + [] = flush(0), + %% Close write and wait for EOF eagain = read_nif(R, 1), check_stop_ret(select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref)), -- cgit v1.2.3