Creating long running NIFs (or something else)

That was a great example. Zig looks nice and zigler seems like a perfect bridge. I will check the details of what you did when I have some more time. :+1:

There is a way to write programs in Rust that would make panics 99% less likely, namely using functions that return Result<T> and not using functions that can panic – this is very similar to Elixir APIs that return :ok / :error tuples vs. those whose names end with ! and can raise an exception. I don’t have huge production Rust experience but practically every Rust dev I was working with was following the practice of matching on returned errors.

Speaking of those “ton of other things”, we must be realistic that most programming languages and runtimes can’t recover from an out-of-memory condition or a kernel error so I don’t think that’s a fair criticism to Rust – or any other language. Operating under the assumption that you do in fact have some free memory and your physical devices aren’t bombarding the kernel with fatal errors is reasonable.

Answering @amnu3387 here, I believe that what you are looking for here is a close tie between Rust and Zig. The answer is 99% in the “pick your poison” territory. Haskell could work too if that’s your jam.

beware that “yielding nif” API has changed a bit (it’s in the docs under guides) and there are several conveniences that people have asked for that I’m punting to 0.8.0 release, which will happen this summer, probably sometime in july.

1 Like

I’m very interested in how you do yielding, when you release it.

yielding currently works as of 0.7.2, . the api changes are between 0.7.0 and 0.7.1, and the updated niceties coming in 0.8.0 are separate things. I don’t expect the yielding API to change moving forward, except to support interrupting threaded nifs too. If you’re curious as to how it works:

The entrypoint functions start at line 105, the async function is line 159

Ability to seamlessly swap between yielding, sync, and threaded depends on the fact that zig has colorless async functions.

beam.yield function is here: zigler/beam.zig at master · ityonemo/zigler · GitHub

tl;dr - launcher function sets up a frame (heap-allocated memory space for an async function + its stack) inside of a BEAM resource, then it launches the function, inside of an event loop, watching for the 1 ms time limit to elapse. If you get close to exhausting the event loop’s 1ms requirement, intead of resuming the async function, it calls the BEAM nif ABI “reschedule a tail-call” function, passing the resource along as an artifact. On reentry, it accesses the resource and obtains the frame pointer and is able to resume the async function. If you kill the parent process, it triggers garbage collection of the resource, which is set up with a hook to resume at the beam.yield point, except signalling an error, which triggers any defer statements in the zig as usual.

Also importantly, strategic use of threadlocal variables are necessary to allow for ‘global’ information exchange.

2 Likes