aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/test
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/test')
-rw-r--r--erts/emulator/test/nif_SUITE.erl140
-rw-r--r--erts/emulator/test/nif_SUITE_data/nif_SUITE.c254
2 files changed, 383 insertions, 11 deletions
diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl
index 36d512e388..27276e6646 100644
--- a/erts/emulator/test/nif_SUITE.erl
+++ b/erts/emulator/test/nif_SUITE.erl
@@ -31,6 +31,7 @@
init_per_testcase/2, end_per_testcase/2,
basic/1, reload_error/1, upgrade/1, heap_frag/1,
t_on_load/1,
+ select/1,
hipe/1,
types/1, many_args/1, binaries/1, get_string/1, get_atom/1,
maps/1,
@@ -64,6 +65,7 @@ all() ->
[{group, G} || G <- api_groups()]
++
[reload_error, heap_frag, types, many_args,
+ select,
hipe,
binaries, get_string, get_atom, maps, api_macros, from_array,
iolist_as_binary, resource, resource_binary,
@@ -434,6 +436,129 @@ t_on_load(Config) when is_list(Config) ->
verify_tmpmem(TmpMem),
ok.
+-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_ERROR, (1 bsl 0)).
+-define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 1)).
+-define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 2)).
+-define(ERL_NIF_SELECT_INVALID_EVENT, (1 bsl 3)).
+-define(ERL_NIF_SELECT_FAILED, (1 bsl 4)).
+
+
+select(Config) when is_list(Config) ->
+ ensure_lib_loaded(Config),
+
+ Ref = make_ref(),
+ {{R, R_ptr}, {W, W_ptr}} = pipe_nif(),
+ ok = write_nif(W, <<"hej">>),
+ <<"hej">> = read_nif(R, 3),
+
+ %% Wait for read
+ eagain = read_nif(R, 3),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Ref),
+ [] = flush(),
+ ok = write_nif(W, <<"hej">>),
+ [{select, R, Ref, ready_input}] = flush(),
+ <<"hej">> = read_nif(R, 3),
+
+ %% Wait for write
+ Written = write_full(W, $a),
+ 0 = select_nif(W,?ERL_NIF_SELECT_WRITE,W,Ref),
+ [] = flush(),
+ Half = byte_size(Written) div 2,
+ <<First:Half/binary,Second/binary>> = Written,
+ First = read_nif(R,Half),
+ [{select, W, Ref, ready_output}] = flush(),
+ Third = write_full(W, $A),
+ Half2 = byte_size(Second),
+ <<Second:Half2/binary, Third/binary>> = read_nif(R, byte_size(Written)),
+
+ %% Close write and wait for EOF
+ eagain = read_nif(R, 1),
+ check_stop_ret(select_nif(W,?ERL_NIF_SELECT_STOP,W,Ref)),
+ [{fd_resource_stop, W_ptr, _}] = flush(),
+ {1, {W_ptr,_}} = last_fd_stop_call(),
+ true = is_closed_nif(W),
+ [] = flush(),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Ref),
+ [{select, R, Ref, ready_input}] = flush(),
+ eof = read_nif(R,1),
+
+ check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,Ref)),
+ [{fd_resource_stop, R_ptr, _}] = flush(),
+ {1, {R_ptr,_}} = last_fd_stop_call(),
+ true = is_closed_nif(R),
+
+ select_2(Config).
+
+select_2(Config) ->
+ erlang:garbage_collect(),
+ {_,_,2} = last_resource_dtor_call(),
+
+ Ref1 = make_ref(),
+ Ref2 = make_ref(),
+ {{R, R_ptr}, {W, W_ptr}} = pipe_nif(),
+
+ %% Change ref
+ eagain = read_nif(R, 1),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Ref1),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Ref2),
+
+ [] = flush(),
+ ok = write_nif(W, <<"hej">>),
+ [{select, R, Ref2, ready_input}] = flush(),
+ <<"hej">> = read_nif(R, 3),
+
+ %% Change pid
+ eagain = read_nif(R, 1),
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Ref1),
+ Papa = self(),
+ Pid2 = spawn_link(fun() ->
+ 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Ref1),
+ [] = flush(),
+ Papa ! sync,
+ [{select, R, Ref1, ready_input}] = flush(),
+ <<"hej">> = read_nif(R, 3),
+ Papa ! done
+ end),
+ sync = receive_any(),
+ ok = write_nif(W, <<"hej">>),
+ done = receive_any(),
+ [] = flush(),
+
+ check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,Ref1)),
+ [{fd_resource_stop, R_ptr, _}] = flush(),
+ {1, {R_ptr,_}} = last_fd_stop_call(),
+ true = is_closed_nif(R),
+
+ %% Stop without previous read/write select
+ ?ERL_NIF_SELECT_STOP_CALLED = select_nif(W,?ERL_NIF_SELECT_STOP,W,Ref1),
+ [{fd_resource_stop, W_ptr, 1}] = flush(),
+ {1, {W_ptr,1}} = last_fd_stop_call(),
+ true = is_closed_nif(W),
+
+ select_3(Config).
+
+select_3(Config) ->
+ erlang:garbage_collect(),
+ {_,_,2} = last_resource_dtor_call(),
+ ok.
+
+check_stop_ret(?ERL_NIF_SELECT_STOP_CALLED) -> ok;
+check_stop_ret(?ERL_NIF_SELECT_STOP_SCHEDULED) -> ok.
+
+write_full(W, C) ->
+ write_full(W, C, <<>>).
+write_full(W, C, Acc) ->
+ case write_nif(W, <<C>>) of
+ ok ->
+ write_full(W, (C+1) band 255, <<Acc/binary, C>>);
+ {eagain,0} ->
+ Acc
+ end.
+
hipe(Config) when is_list(Config) ->
Data = proplists:get_value(data_dir, Config),
Priv = proplists:get_value(priv_dir, Config),
@@ -1910,6 +2035,15 @@ call(Pid,Cmd) ->
receive_any() ->
receive M -> M end.
+flush() ->
+ flush(10).
+flush(Timeout) ->
+ receive M ->
+ [M | flush(Timeout)]
+ after Timeout ->
+ []
+ end.
+
repeat(0, _, Arg) ->
Arg;
repeat(N, Fun, Arg0) ->
@@ -2273,6 +2407,12 @@ term_to_binary_nif(_, _) -> ?nif_stub.
binary_to_term_nif(_, _, _) -> ?nif_stub.
port_command_nif(_, _) -> ?nif_stub.
format_term_nif(_,_) -> ?nif_stub.
+select_nif(_,_,_,_) -> ?nif_stub.
+pipe_nif() -> ?nif_stub.
+write_nif(_,_) -> ?nif_stub.
+read_nif(_,_) -> ?nif_stub.
+is_closed_nif(_) -> ?nif_stub.
+last_fd_stop_call() -> ?nif_stub.
%% maps
is_map_nif(_) -> ?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 2c93891852..c4f9611ec8 100644
--- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
+++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c
@@ -25,6 +25,8 @@
#include <limits.h>
#ifndef __WIN32__
#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
#endif
#include "nif_mod.h"
@@ -42,7 +44,10 @@ static ERL_NIF_TERM atom_second;
static ERL_NIF_TERM atom_millisecond;
static ERL_NIF_TERM atom_microsecond;
static ERL_NIF_TERM atom_nanosecond;
-
+static ERL_NIF_TERM atom_eagain;
+static ERL_NIF_TERM atom_eof;
+static ERL_NIF_TERM atom_error;
+static ERL_NIF_TERM atom_fd_resource_stop;
typedef struct
{
@@ -102,6 +107,20 @@ struct binary_resource {
unsigned size;
};
+static ErlNifResourceType* fd_resource_type;
+static void fd_resource_dtor(ErlNifEnv* env, void* obj);
+static void fd_resource_stop(ErlNifEnv* env, void* obj, ErlNifEvent, int);
+static ErlNifResourceTypeInit fd_rt_init = {
+ fd_resource_dtor,
+ fd_resource_stop
+};
+struct fd_resource {
+ int fd;
+ int was_selected;
+ ErlNifPid pid;
+};
+
+
static int get_pointer(ErlNifEnv* env, ERL_NIF_TERM term, void** pp)
{
ErlNifBinary bin;
@@ -116,7 +135,6 @@ static ERL_NIF_TERM make_pointer(ErlNifEnv* env, void* p)
{
void** bin_data;
ERL_NIF_TERM res;
- ADD_CALL("get_priv_data_ptr");
bin_data = (void**)enif_make_new_binary(env, sizeof(void*), &res);
*bin_data = p;
return res;
@@ -144,6 +162,9 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
msgenv_resource_type = enif_open_resource_type(env,NULL,"nif_SUITE.msgenv",
msgenv_dtor,
ERL_NIF_RT_CREATE, NULL);
+ fd_resource_type = enif_open_resource_type_x(env, "nif_SUITE.fd",
+ &fd_rt_init,
+ ERL_NIF_RT_CREATE, NULL);
atom_false = enif_make_atom(env,"false");
atom_true = enif_make_atom(env,"true");
atom_self = enif_make_atom(env,"self");
@@ -154,6 +175,10 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
atom_millisecond = enif_make_atom(env,"millisecond");
atom_microsecond = enif_make_atom(env,"microsecond");
atom_nanosecond = enif_make_atom(env,"nanosecond");
+ atom_eagain = enif_make_atom(env, "eagain");
+ atom_eof = enif_make_atom(env, "eof");
+ atom_error = enif_make_atom(env, "error");
+ atom_fd_resource_stop = enif_make_atom(env, "fd_resource_stop");
*priv_data = data;
return 0;
@@ -389,8 +414,7 @@ static ERL_NIF_TERM type_test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
ErlNifSInt64 sint64;
ErlNifUInt64 uint64;
double d;
- ERL_NIF_TERM atom, ref1, ref2, term;
- size_t len;
+ ERL_NIF_TERM atom, ref1, ref2;
sint = INT_MIN;
do {
@@ -1024,6 +1048,7 @@ struct make_term_info
{
ErlNifEnv* caller_env;
ErlNifEnv* dst_env;
+ int dst_env_valid;
ERL_NIF_TERM reuse[MAKE_TERM_REUSE_LEN];
unsigned reuse_push;
unsigned reuse_pull;
@@ -1053,6 +1078,7 @@ static ERL_NIF_TERM pull_term(struct make_term_info* mti)
mti->reuse_push < MAKE_TERM_REUSE_LEN) {
mti->reuse_pull = 0;
if (mti->reuse_push == 0) {
+ assert(mti->dst_env_valid);
mti->reuse[0] = enif_make_list(mti->dst_env, 0);
}
}
@@ -1106,10 +1132,6 @@ static ERL_NIF_TERM make_term_string(struct make_term_info* mti, int n)
{
return enif_make_string(mti->dst_env, "Hello!", ERL_NIF_LATIN1);
}
-static ERL_NIF_TERM make_term_ref(struct make_term_info* mti, int n)
-{
- return enif_make_ref(mti->dst_env);
-}
static ERL_NIF_TERM make_term_sub_binary(struct make_term_info* mti, int n)
{
ERL_NIF_TERM orig;
@@ -1217,7 +1239,6 @@ static Make_term_Func* make_funcs[] = {
make_term_atom,
make_term_existing_atom,
make_term_string,
- //make_term_ref,
make_term_sub_binary,
make_term_uint,
make_term_long,
@@ -1241,6 +1262,7 @@ static unsigned num_of_make_funcs()
static int make_term_n(struct make_term_info* mti, int n, ERL_NIF_TERM* res)
{
if (n < num_of_make_funcs()) {
+ assert(mti->dst_env_valid);
*res = make_funcs[n](mti, n);
push_term(mti, *res);
return 1;
@@ -1257,6 +1279,7 @@ static ERL_NIF_TERM make_blob(ErlNifEnv* caller_env, ErlNifEnv* dst_env,
struct make_term_info mti;
mti.caller_env = caller_env;
mti.dst_env = dst_env;
+ mti.dst_env_valid = 1;
mti.reuse_push = 0;
mti.reuse_pull = 0;
mti.resource_type = priv->rt_arr[0].t;
@@ -1297,6 +1320,7 @@ static ERL_NIF_TERM alloc_msgenv(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
sizeof(*mti));
mti->caller_env = NULL;
mti->dst_env = enif_alloc_env();
+ mti->dst_env_valid = 1;
mti->reuse_push = 0;
mti->reuse_pull = 0;
mti->resource_type = priv->rt_arr[0].t;
@@ -1328,6 +1352,7 @@ static ERL_NIF_TERM clear_msgenv(ErlNifEnv* env, int argc, const ERL_NIF_TERM ar
return enif_make_badarg(env);
}
enif_clear_env(mti.p->dst_env);
+ mti.p->dst_env_valid = 1;
mti.p->reuse_pull = 0;
mti.p->reuse_push = 0;
mti.p->blob = enif_make_list(mti.p->dst_env, 0);
@@ -1362,6 +1387,8 @@ static ERL_NIF_TERM send_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
}
copy = enif_make_copy(env, mti.p->blob);
res = enif_send(env, &to, mti.p->dst_env, mti.p->blob);
+ if (res)
+ mti.p->dst_env_valid = 0;
return enif_make_tuple3(env, atom_ok, enif_make_int(env,res), copy);
}
@@ -1369,7 +1396,6 @@ static ERL_NIF_TERM send3_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
{
mti_t mti;
ErlNifPid to;
- ERL_NIF_TERM copy;
int res;
if (!enif_get_resource(env, argv[0], msgenv_resource_type, &mti.vp)
|| !enif_get_local_pid(env, argv[1], &to)) {
@@ -1379,6 +1405,8 @@ static ERL_NIF_TERM send3_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv
enif_make_copy(mti.p->dst_env, argv[2]),
mti.p->blob);
res = enif_send(env, &to, mti.p->dst_env, mti.p->blob);
+ if (res)
+ mti.p->dst_env_valid = 0;
return enif_make_int(env,res);
}
@@ -1395,6 +1423,8 @@ void* threaded_sender(void *arg)
mti.p->send_it = 0;
enif_mutex_unlock(mti.p->mtx);
mti.p->send_res = enif_send(NULL, &mti.p->to_pid, mti.p->dst_env, mti.p->blob);
+ if (mti.p->send_res)
+ mti.p->dst_env_valid = 0;
return NULL;
}
@@ -2010,6 +2040,202 @@ static ERL_NIF_TERM format_term(ErlNifEnv* env, int argc, const ERL_NIF_TERM arg
}
+static int get_fd(ErlNifEnv* env, ERL_NIF_TERM term, struct fd_resource** rsrc)
+{
+ if (!enif_get_resource(env, term, fd_resource_type, (void**)rsrc)) {
+ return 0;
+ }
+ return 1;
+}
+
+static ERL_NIF_TERM select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ struct fd_resource* fdr;
+ enum ErlNifSelectFlags mode;
+ void* obj;
+ ERL_NIF_TERM ref;
+ enum ErlNifSelectReturn retval;
+
+ if (!get_fd(env, argv[0], &fdr)
+ || !enif_get_uint(env, argv[1], &mode)
+ || !enif_get_resource(env, argv[2], fd_resource_type, &obj))
+ {
+ return enif_make_badarg(env);
+ }
+
+ ref = argv[3];
+
+ fdr->was_selected = 1;
+ enif_self(env, &fdr->pid);
+ retval = enif_select(env, fdr->fd, mode, obj, ref);
+
+ return enif_make_int(env, (int)retval);
+}
+
+static ERL_NIF_TERM pipe_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ struct fd_resource* read_rsrc;
+ struct fd_resource* write_rsrc;
+ ERL_NIF_TERM read_fd, write_fd;
+ int fds[2], flags;
+
+ if (pipe(fds) < 0)
+ return enif_make_string(env, "pipe failed", ERL_NIF_LATIN1);
+
+ if ((flags = fcntl(fds[0], F_GETFL, 0)) < 0
+ || fcntl(fds[0], F_SETFL, flags|O_NONBLOCK) < 0
+ || (flags = fcntl(fds[1], F_GETFL, 0)) < 0
+ || fcntl(fds[1], F_SETFL, flags|O_NONBLOCK) < 0) {
+ close(fds[0]);
+ close(fds[1]);
+ return enif_make_string(env, "fcntl failed on pipe", ERL_NIF_LATIN1);
+ }
+
+ read_rsrc = enif_alloc_resource(fd_resource_type, sizeof(struct fd_resource));
+ write_rsrc = enif_alloc_resource(fd_resource_type, sizeof(struct fd_resource));
+ read_rsrc->fd = fds[0];
+ write_rsrc->fd = fds[1];
+ read_fd = enif_make_resource(env, read_rsrc);
+ write_fd = enif_make_resource(env, write_rsrc);
+ enif_release_resource(read_rsrc);
+ enif_release_resource(write_rsrc);
+
+ return enif_make_tuple2(env,
+ enif_make_tuple2(env, read_fd, make_pointer(env, read_rsrc)),
+ enif_make_tuple2(env, write_fd, make_pointer(env, write_rsrc)));
+}
+
+static ERL_NIF_TERM write_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ struct fd_resource* fdr;
+ ErlNifBinary bin;
+ int n, written = 0;
+
+ if (!get_fd(env, argv[0], &fdr)
+ || !enif_inspect_binary(env, argv[1], &bin))
+ return enif_make_badarg(env);
+
+ for (;;) {
+ n = write(fdr->fd, bin.data + written, bin.size - written);
+ if (n >= 0) {
+ written += n;
+ if (written == bin.size) {
+ return atom_ok;
+ }
+ }
+ else if (errno == EAGAIN) {
+ return enif_make_tuple2(env, atom_eagain, enif_make_int(env, written));
+ }
+ else if (errno == EINTR) {
+ continue;
+ }
+ else {
+ return enif_make_tuple2(env, atom_error, enif_make_int(env, errno));
+ }
+ }
+}
+
+static ERL_NIF_TERM read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ struct fd_resource* fdr;
+ unsigned char* buf;
+ int n, count;
+ ERL_NIF_TERM res;
+
+ if (!get_fd(env, argv[0], &fdr)
+ || !enif_get_int(env, argv[1], &count) || count < 1)
+ return enif_make_badarg(env);
+
+ buf = enif_make_new_binary(env, count, &res);
+
+ for (;;) {
+ n = read(fdr->fd, buf, count);
+ if (n > 0) {
+ if (n < count) {
+ res = enif_make_sub_binary(env, res, 0, n);
+ }
+ return res;
+ }
+ else if (n == 0) {
+ return atom_eof;
+ }
+ else if (errno == EAGAIN) {
+ return atom_eagain;
+ }
+ else if (errno == EINTR) {
+ continue;
+ }
+ else {
+ return enif_make_tuple2(env, atom_error, enif_make_int(env, errno));
+ }
+ }
+}
+
+static ERL_NIF_TERM is_closed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ struct fd_resource* fdr;
+
+ if (!get_fd(env, argv[0], &fdr))
+ return enif_make_badarg(env);
+
+ return fdr->fd < 0 ? atom_true : atom_false;
+}
+
+static void fd_resource_dtor(ErlNifEnv* env, void* obj)
+{
+ struct fd_resource* fdr = (struct fd_resource*)obj;
+ resource_dtor(env, obj);
+ if (fdr->fd >= 0) {
+ assert(!fdr->was_selected);
+ close(fdr->fd);
+ }
+}
+
+static struct {
+ void* obj;
+ int was_direct_call;
+}last_fd_stop;
+int fd_stop_cnt = 0;
+
+static void fd_resource_stop(ErlNifEnv* env, void* obj, ErlNifEvent fd,
+ int is_direct_call)
+{
+ struct fd_resource* fdr = (struct fd_resource*)obj;
+ assert(fd == fdr->fd);
+ assert(fd >= 0);
+
+ last_fd_stop.obj = obj;
+ last_fd_stop.was_direct_call = is_direct_call;
+ fd_stop_cnt++;
+
+ close(fd);
+ fdr->fd = -1; /* thread safety ? */
+ fdr->was_selected = 0;
+
+ {
+ ErlNifEnv* msg_env = enif_alloc_env();
+ ERL_NIF_TERM msg;
+ msg = enif_make_tuple3(msg_env,
+ atom_fd_resource_stop,
+ make_pointer(msg_env, obj),
+ enif_make_int(msg_env, is_direct_call));
+
+ enif_send(env, &fdr->pid, msg_env, msg);
+ enif_free_env(msg_env);
+ }
+}
+
+static ERL_NIF_TERM last_fd_stop_call(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM last, ret;
+ last = enif_make_tuple2(env, make_pointer(env, last_fd_stop.obj),
+ enif_make_int(env, last_fd_stop.was_direct_call));
+ ret = enif_make_tuple2(env, enif_make_int(env, fd_stop_cnt), last);
+ fd_stop_cnt = 0;
+ return ret;
+}
+
+
static ErlNifFunc nif_funcs[] =
{
{"lib_version", 0, lib_version},
@@ -2086,7 +2312,13 @@ static ErlNifFunc nif_funcs[] =
{"term_to_binary_nif", 2, term_to_binary},
{"binary_to_term_nif", 3, binary_to_term},
{"port_command_nif", 2, port_command},
- {"format_term_nif", 2, format_term}
+ {"format_term_nif", 2, format_term},
+ {"select_nif", 4, select_nif},
+ {"pipe_nif", 0, pipe_nif},
+ {"write_nif", 2, write_nif},
+ {"read_nif", 2, read_nif},
+ {"is_closed_nif", 1, is_closed_nif},
+ {"last_fd_stop_call", 0, last_fd_stop_call}
};
ERL_NIF_INIT(nif_SUITE,nif_funcs,load,NULL,upgrade,unload)