Hacker News new | ask | show | jobs
by pawelduda 1160 days ago
I used this in past, but there were a lot of people saying that NIFs/ports are dangerous to use. Did any of this change or are there tried and tested practices that can be applied? What I used this for wasn't critical app so I didn't mind it going down.

IIRC one threat was Rust sharing memory with BEAM which could exhaust it and cause OOM crash?

3 comments

I wouldn't use the word dangerous. NIFs and (linked in) ports are a risk, yes. You're loading native code, and you don't have intrinsic protection against it doing naughty things that alter the underlying shared state in unapproved ways. Things like writing to improper addresses, closing improper filehandles, opening filehandles and leaking them, leaking memory, other improper use of syscalls, or just running for too long.

The only proper solution is to audit and understand the code you're running, but hoping it's fine often works too; maybe formal methods, but to a first approximation, nobody uses those. Did you audit all of ERTS and/or OTP? I'm guessing probably not, but it's there to review if you run into a problem.

IMHO, it's not worrying about if BEAM will crash; worry about it not crashing instead. If your Rust NIF ties up a scheduler with an infinite loop, that has the potentially to lock up the whole BEAM once another scheduler needs to do something that requires full cross scheduler coordination.

BEAM can certainly crash on OOM; although I recommend setting a ulimit to ensure it will, because when it crashes, you can recover. I've also run into situations where instead of crashing or being killed by the OS OOM killer (which is close enough to crashing), the OS gets into some tricky to debug state where your application is neither functioning nor killed. Sometimes, you even get into a state where BEAM is making progress, but very slowly; that's a fate worse than death.

If you follow the Erlang philosophy, you'll have a recovery strategy from crashes or other deaths. Heart can be used to turn completely blocked into death, although I never used it professionally. But you've still got to worry about working but not well.

Nif's are always a risk to deploy cause they run embedded in the Beam runtime. That said we all use many nif's to do all kinds of stuff and its fine.

I go into this in the article a little but but the ruslter team has made dirtycpu and dirtyio macro's to help reduce the risks.

NIFs are dangerous, ports are not. Basically a NIF runs in the same memory space as the BEAM, so misbehavior of the NIF can crash the entire application. On the other hand, they have wildly better performance than ports, which have to operate through STDI/O.

The Rust / BEAM memory sharing problem does exist, but it's not nearly as bad as in more traditional C NIFs, because almost all C programs leak memory due to bad manual memory management. Hence all the buzz about Elixir+Rust.

I personally include port drivers in with ports, which gives you the same level of shared runtime environment as NIFs, with the benefits and drawbacks. Sometimes it makes more sense to interface as a port rather than as a function. Of course, sometimes it makes more sense to interface as a C-node rather than either; then you can be on a totally isolated machine, and the C-node can even crash or otherwise disable the whole OS and your Erlang node will be fine.
What's the benefit of a port driver over a NIF?
A port driver can participate in the BEAM event loop, adding filehandles that get called back when they're ready.

It's a more appropriate choice for something that's asynchronous, although NIFs do have ways to fill the same role. A port driver would probably be a better choice for specalty networking that ERTS doesn't provide (raw packets? netgraph, etc).

ports can be dangerous! One time my server lost performance because of a lot of zombie processes.