Then I learned that the Achilles' heel of the BEAM VM is that if native code crashes it takes the whole thing with it, so people have been using Rust for native code (NIFs). So now the plan is Elixir then Rust which is easing my FOMO quite a bit.
Then I learned that the Achilles' heel of the BEAM VM is that if native code crashes it takes the whole thing with it, so people have been using Rust for native code (NIFs). So now the plan is Elixir then Rust which is easing my FOMO quite a bit.
That's not really an 'Achilles heel' any more than most other languages that allow you to load C code that could segfault or - maybe even worse - get stuck in an infinite loop.
Indeed, the fact that BEAM focuses on and acknowledges this is an advantage for people trying to create robust systems. They've had workarounds in place for years, like running stuff in a separate system process.
Not crashing is good, but not sufficient to play nicely with the BEAM scheduler. You need to also ensure you're not using up a lot of time in some long-running operation, unless it's a "dirty NIF".
2) I'm considering taking up Zig as a better (well simpler, because my poor brain can't handle rust) safe language for NIFs. Do you think that zig might make a better fit as it can easily integrate C headers into itself?
3) are dirty nifs not ok?
We use ours for signal processing, not "deep learning" so it may not be completely applicable.
Use this to at least notify the erlang side that it can't do this operation right now, either like send(2) via EAGAIN / EWOULDBLOCK, or, if possible, by implementing a "push-notification" from the CUDA scheduler to the BEAM IO subsystem to try again automatically (sort-of implementing CUDA stream enqueue operations as a custom type of IO) or base a poll(2)-style function (to be called by erlang code that then tries to re-submit or maybe propagate a ressource exhaustion conditon/timeout upstream) on this enqueue-possible notification.
The most important thing to support would be a way to allow the caller to not overfill the queue and allow an erlang thread to efficiently block until woken by a completion event in a CUDA stream.
I'm not sure, but I think the former could be done by manually accounting for queue fill-level in the erlang->CUDA library. The latter should be quite easy with CUDA stream callbacks inserted into a helper stream which got a "block for CUDA stream event $x" inserted right before the stream callback. The benefit would be that it's (mostly?) transparent to potential other Rust/C++ code interacting with the same CUDA streams for e.g. backend-fetching or handling/scheduling expensive CPU processing with tight integration into the CUDA operations.
Last I checked there might not have been an efficient way to syscall-block on CUDA stream events, so that one would have to manually trade between context switch overhead and latency because you'd have (had) to sched_yield(3) in between waking up to check if the stream is done or not. The only blocking wait call was a busy poll that would result in wasting the cpu core and preventing other threads/applications from using it while the GPU was busy going through the queued-up tasks/operations. I didn't try or at least don't remember trying whether stream callbacks could be (ab-)used for delivering progress information without actively/explicitely waiting on any CUDA stream(-event). It might be possible though.
If in fact the fault-tolerant premise is broken as soon as you need something outside the capabilities of the managed language, why learn a weirdo language? You might as well just use C# or Java instead.
It's definitely a more forgiving and powerful model than 'try/catch'.
Yes, I know — so was I:
> … as soon as you need something outside the capabilities of the managed language …
NIFs should be used when ports performance is not acceptable.
About deciding between Elixir and Rust you will benefit from learning both as Elixir/Erlang are more suited for fault tolerant distributed systems and Rust aims at becoming a safer C++. Also, Rust + wasm opens up a great toolset to build faster frontend apps.
If you follow the Elixir path first you'll benefit of learning some Erlang as well.
Hypothetically. It's yet to be seen (or full applications at least).
The current frontend frameworks that exist are very immature and I wouldn't use them for anything other than small personal projects right now.
The current translation layer (going through JS to access the DOM) has an overhead, but is surprisingly small. There are no frameworks yet that really take advantage of this, though.
Things will become really interesting once wasm code can access the DOM directly without going through JS. (See the "Interface Types" wasm proposal)
But then again, the value of writing a frontend in Rust with it's verbosity and strict type system is very questionable when we have Typescript and the incredibly fast JS engines.
It could be valuable for large, long running apps. But those are commercial projects, which won't go for Rust (language complexity, lack of developers, productivity, ...)
1) Tools like wasm-pack already generate typescript definitions for your WASM targeting Rust code - it seems like it shouldn't be too painful to write just a couple of particularly hot loops in Rust even if the bulk of your web app is in Typescript.
2) Multi-threaded JS is a non-starter, but we're already seeing experimental support for multi-threaded WASM. Admittedly, web workers let you write something similar to multi-process JS...
3) Less GC pressure, since WASM memory isn't garbage collected. While the scale of things you'd put in a website or typical web app really don't care about this, GC pauses in the single digits of milliseconds can start causing problems for games and other framerate sensitive applications if they cause vsync to be regularly missed.
4) Sharing code between a web demo and a "proper desktop app" via WASM targeting languages like Rust imposes a lot less constraints than a JS targeting language like Typescript (where you now need a full blown JS engine, if not a full blown browser engine ala electron, for the desktop version)
I wouldn't suprise me if WASM never gains widespread adoption for frontend development. But it also wouldn't suprise me if it gets used by a node module or two that eventually get popular, in an attempt to shave off a couple milliseconds, with end users being none the wiser.
I see both sides, but full unsafe-eval is too much of a wrecking ball when a warded lock[0] on this proverbial door to the WASM kingdom would be sufficient.
People talk about "let it crash" in BEAM world, but advice I got that made understanding things a whole lot easier is to focus on recovering more than preventing crashes. I've found Elixir to be better at both not crashing and better at recovering. The abstractions for both just make more sense to me. That being said, I wouldn't have been able to understand those concepts before learning Elixir. So I say learn Elixir and OTP well, and decide for yourself.
Is this Greenspun’s Eleventh Rule or something?
A bad NIF crashes the entire VM and breaks the Erlang/Elixir model of failure handling, so you really should not put code that crashes in there.
Do they fail the entire VM, too?
As a operator/user, you can certainly write some code to scan for large processes, and kill them; with a fair bit of success, but that's not a great thing for the toolkit to do, it doesn't have a good way to tell what's too big or what's intended.
[1] See max_heap_size under http://erlang.org/documentation/doc-9.0/erts-9.0/doc/html/er...
If you read his 2003[1] thesis, at page 36 he quotes a paper from Jim Gray (who worked at Tandem computers and was a Turing award winner)
Although compiler checking and exception handling provided by programming languages are real assets, history seems to have favored the run-time checks plus the process approach to fault-containment. It has the virtue of simplicity—if a process or its processor misbehaves, stop it
and the on page 37 The idea of “fail-fast” modules is mirrored in our guidelines for programming where we say that processes should only do when they are supposed to do according to the specification, otherwise they should crash.
and then on page 40 Error handling is non-local.
When we make a fault-tolerant system we need at least two physically separated computers. Using a single computer will not work, if it crashes, all is lost. The simplest fault-tolerant system we can imagine has exactly two computers, if one computer crashes, then the other computer should take over what the first computer was doing. In this simple situation even the software for fault-recovery must be non-local; the error occurs on the first machine, but is corrected by software running on the second machine.
So actually the VM crash is a signal: something's gone really bad, don't even try to recover, just let it die and let some other non-local process take over (the OS, some `forever` like script, a process on another machine...).A NIF is basically VM code that undergo some minor limitation and runs at full speed.
BEAM is not a sandbox and will (probably) never be.
The most important lesson Erlang teaches is that there could be no recovery from crashes from the same code that crashed.
That's why in OTP Supervisors are in the shape of a tree, each level is responsible for its children and only its children.
If you need control on some process tree, you start another supervisor to supervise it and so on.
The same goes for the VM: if you need to supervise the VM, you start an external process to check its status.
A crashed process is better than a process that misbehaves.
Non locality of error handling is the only way to be really fault tolerant.
For example: what happens if I run this code?
public class Main {
public static void main(String[] args) {
Object[] o = null;
while (true) {
o = new Object[] {o};
}
}
}
What happens if there is a bug in some JNI code that cause a segfault?What Erlang programming style tries to teach is not to avoid crashes by being very defensive and trying to prevent every possible error condition (it's impossible), but to detect them by being fault tolerant and restart the process from a good state.