Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Value of binary changing after NIF calls Erlang

I intend to manipulate binaries using NIFs for an app which I'm planning to code in Erlang. The gist links to the cpp file and erl file for the NIF are given below.

[Erl Gist Link] https://gist.github.com/abhijitiitr/3a5bc97184d6dd32f97b

[C++ Gist Link] https://gist.github.com/abhijitiitr/24d2b780f2cdacebfb07

Basically I'm trying to do a simple test. Share binaries across NIF calls and successfully manipulate them with successive NIF calls.

If you test the code in erlang REPL by

c(binary_test).
Ref=binary_test:open(<<1>>).
binary_test:increment(Ref,<<3>>).

The binaries stored changes in between the NIF calls. The REPL output for the third command is

1
 3
  60
    60
      <<"?">>

I passed <<1>> during the initialize phase. Why did it change to <<60>>? I'm unable to figure out whats happening here. Can somebody point out the error?

C++ compile instructions

clang++ -std=c++11 -stdlib=libc++ -undefined dynamic_lookup -O3 -dynamiclib binary_test.cpp -o binary_test.so -I /usr/local/Cellar/erlang/17.0/lib/erlang/erts-6.0/include/ 

on my Mac.

Also I wanted to ask about concurrent processes manipulating a shared resource in NIF. Is that possible or there is a rule that NIFs have to be accessed in a single Erlang process.

like image 852
abips Avatar asked Mar 19 '23 03:03

abips


2 Answers

You're running into problems because you're illegally accessing memory. In your BinaryStore constructor you're attempting to save a binary from the argument list passed to binary_test:open/1, but this doesn't work because those arguments are freed once the NIF call finishes with them. You need to save a copy of the argument to use it later. To do this, first add a new member to your BinaryStore class:

    ErlNifEnv* term_env;

Next, modify your constructor to allocate term_env and then use it to copy the incoming term:

    BinaryStore(ERL_NIF_TERM binary)
    {
        term_env = enif_alloc_env();
        binary_term = enif_make_copy(term_env, binary);
    }

This allocates binary_term in the term_env environment and then copies the incoming term into it. You'll also need a destructor to free term_env:

    ~BinaryStore()
    {
        enif_free_env(term_env);
    }

And finally, you need to pass term_env instead of env when inspecting binary_term in the increment_binary function:

    nifpp::get_throws(term_env, binary_term, ibin);

With these modifications in place, I get the following results from running the code:

1> Ref=binary_test:open(<<1>>).
Reading symbols for shared libraries . done
<<>>
2> binary_test:increment(Ref,<<3>>).
1
 3
  1
   1
    <<4>>

(By the way, you should use "\r\n" line endings rather than just "\n" when printing from inside the Erlang emulator so that newlines always return to the leftmost column.)

You still have one remaining problem, which is that you leak the memory allocated for new_bin2.

My advice for learning the details of NIFs is to avoid using packages like nifpp at first, so you can learn the NIF API and all the details regarding memory ownership, resource allocation and deallocation, and argument conversions. Once you understand them, using packages like nifpp becomes much easier and more fruitful.

like image 93
Steve Vinoski Avatar answered Mar 24 '23 10:03

Steve Vinoski


ERL_NIF_TERMs must be associated with an ErlNifEnv, and the env that is passed to your nif functions is only valid for the duration of that function call. You are breaking this rule when you store a term into a BinaryStore object and then later use it from another nif call. Your options:

  1. Create a new ErlNifEnv for your binary store and copy terms from the nif call into this new env.

  2. Use C++ data structures (such as std::vector<unsigned char>) to store your binary data. I think this will be simpler for your situation.

like image 25
goertzenator Avatar answered Mar 24 '23 10:03

goertzenator