question about NIF resources

Michael Santos michael.santos@REDACTED
Sun Jan 2 22:20:39 CET 2011


The documentation is quite explicit about the behaviour of
enif_make_resource():

"The term returned by enif_make_resource is totally opaque in nature. It
can be stored and passed between processses on the same node, but the
only real end usage is to pass it back as argument to a NIF.
<...>
Other operations such as matching or term_to_binary will have
unpredictable (but harmless) results."

That's clear enough but I wonder if unpredictable behaviour is really
harmless or if I'm confused about how it works.  For example, with an
NIF wrapping malloc() [1] such as:

    static int
load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
{
    atom_ok = enif_make_atom(env, "ok");
    atom_error = enif_make_atom(env, "error");

    if ( (MEM_RESOURCE = enif_open_resource_type(env, NULL,
            "mem_resource", cleanup,
            ERL_NIF_RT_CREATE, NULL)) == NULL)
        return -1;

    return 0;
}

    static ERL_NIF_TERM
nif_malloc(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    size_t size = 0;
    void **p = NULL;
    ERL_NIF_TERM res = {0};

    if (!enif_get_ulong(env, argv[0], (ulong *)&size))
        return enif_make_badarg(env);

    p = enif_alloc_resource(MEM_RESOURCE, sizeof(char *));

    if (p == NULL)
        return atom_error;

    *p = (char *)malloc(size);

    if (*p == NULL) {
        enif_release_resource(p);
        return atom_error;
    }

    (void)fprintf(stderr, "alloc: p=%p/%p\n", *p, p);

    res = enif_make_resource(env, p);
    enif_release_resource(p);

    return enif_make_tuple(env, 2,
        atom_ok,
        res);
}

    static ERL_NIF_TERM
nif_free(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    void **p = NULL;

    if (!enif_get_resource(env, argv[0], MEM_RESOURCE, (void **)&p))
        return enif_make_badarg(env);

    (void)fprintf(stderr, "free: p=%p/%p\n", *p, p);

    free(*p);
    *p = NULL;

    return atom_ok;
}


Running it:

Erlang R14B02 (erts-5.8.3) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.3  (abort with ^G)
1> {ok,P} = mem:alloc(10).
alloc: p=0x92caa30/0x925ed58
{ok,<<>>}
2> {ok,P1} = mem:alloc(100).
alloc: p=0x92cb008/0x925ecd0
{ok,<<>>}
3> P = P1.
<<>>
4> {ok, P} = mem:alloc(20).
alloc: p=0x92caa40/0x9263560
{ok,<<>>}

I would expect P /= P1 and a second invocation of mem:alloc/1 with a
bound variable to fail. As it is, the NIF will leak resources:

5> mem:free(P). % P really is bound during the first operation
free: p=0x92caa30/0x925ed58
ok
6> mem:free(P).
free: p=(nil)/0x925ed58

Presumably the memory will be freed when the process dies and the
dtor runs.

But there is pattern matching behaviour, the caller just never knows
what to expect since the behaviour is undefined. At least at this time
erl_make_resource returns something that looks like an empty binary:

1> spawn(fun() -> {ok, foo} = mem:alloc(10) end).
alloc: p=0xb53055a0/0xb4cc7270
cleanup: *p=0xb53055a0/0xb4cc7270

=ERROR REPORT==== 2-Jan-2011::15:29:56 ===
Error in process <0.38.0> with exit value: {{badmatch,{ok,<<0 bytes>>}},[{erl_eval,expr,3}]}

<0.38.0>

I worked around this behaviour by returning a tagged tuple containing the
resource and a reference. But would it make sense for erl_make_resource()
to return a defined type with some unique value (even if it's thrown
away)?


[1]
https://gist.github.com/762795
https://gist.github.com/762796



More information about the erlang-questions mailing list