Making async code synchronous

I wrote a NIF which have function that can take a long time to run. To prevent the scheduler from being overloaded with long running functions (even if marked at dirty), I made my NIF threaded and it sends message back to erlang using enif_send.

I want to make those call synchronous from a “programmer point of view”. Similar to a Process.sleep() call.

I was wondering what was the best approach. To avoid messing with the caller message inbox and to have an isolated process to receive the message, I thought of using something like this:

task = Task.async(fn -> 
call_nif(self()) 
receive do 
<wait for nif response> 
end 
end)
Task.await(task)

Would that be a good approach?

It could work but in my opinion it’s best to use a yielding NIF or spawn an extra process and use local means of communication.

1 Like

My NIF is blocking and it is hard to make it non blocking, so I wrapped the entire function into a C thread, my NIF now spawn the thread and return immediately, then the thread sends the message. It seems to be a recommenced architecture in the documentation.

Threaded NIF - This is accomplished by dispatching the work to another thread managed by the NIF library, return from the NIF, and wait for the result. The thread can send the result back to the Erlang process using enif_send. Information about thread primitives is provided below.

Now my question is more about the user API which I want to make synchronous for API simplificy sake, so I thought of this task trick (which works fine). But I was wondering it was the right approach or if there was a more idiomatic way to do it.

In fact it is to make an async call synchronous in general, not really tied to a nif.

Why wrap it in a task at all? Can you simply have a function that calls the NIF and then does a receive right after?

What does this NIF do btw? How long does it run?

The calling process might have other messages in its inbox when I call this function, I didn’t want to mess with that. This nif does some GPU inference, it runs for about 5 seconds.

I know it could/should be an external port, but using nif I get all serialize/deserialize for free and it is very practical.

Namespacing and a selective receive would to that.

So I can just do receive do {:my_namespace, …} → <we are done> end and it will not mess with the inbox? I wasn’t sure of that.

I’ll remove the matches message from the inbox – but it’ll leave anything else as is.

1 Like