It would be indeed a reimplementation of what OS does, but note that such a reimplementation is the very reason why quite straightforward Erlang code can easily handle hundreds of thousands active connections on modest hardware.
It would be indeed a reimplementation of what OS does, but note that such a reimplementation is the very reason why quite straightforward Erlang code can easily handle hundreds of thousands active connections on modest hardware.
Preemption is good because it prevents bad processes from starving the rest of the system but it's far from efficient.
Have they changed it? Last I knew, processes get a "reduction count", which is basically "the number of opcodes in the VM this process gets to run before it gets suspended". Technically, this is exactly like what Go does, except instead of sticking pre-emption points at function calls, it places them at every opcode, which is to say, quite often multiple times per line of code. Technically, you can still freeze the interpreter (or a thread of it) by calling into some C code that counts as one "reduction" but never returns. In practice, this causes about as much trouble for Erlang programmers as it does Go programmers, namely, "it's something you should know but can literally go your entire career without hitting". (Depends on what kind of code you're writing. Someone trying to use Erlang or Go for heavy numerical processing has a good chance of hitting it, but if you're writing network code, you'd have to reeeaallly work at it in either environment.)
> Technically, you can still freeze the interpreter (or a thread of it) by calling into some C code that counts as one "reduction" but never returns.
Actually, you can't! (Or, well, at least as long as you've given a moment of thought while coding your NIF you can't.)
Avoiding this used to require explicit calls to the reduction budgeting functions in your native code. But as of recent ERTS releases, you just have to mark your NIF as "dirty" and it'll get offloaded to a separate scheduler-thread-pool that exists just to run dirty (i.e. long CPU-time) NIFs. There's no reason, today, to not just mark your NIF as dirty when you start writing it, and then take the dirty flag off only when you're ready to add the calls to the reduction-budgeting logic.
(If you flip it around in your head, having a dirty flag is essentially the same as having a "clean" flag—or, in other words, an "I know what I'm doing, I've guaranteed this has reduction-budgeting" flag. Which is no more and no less than something like Rust's unsafe{} blocks. To say you can freeze a runtime with unsafe{} code shouldn't be surprising ;) What's more surprising is that you can run native code within ERTS that's not unsafe{} in this sense! ...though, as every Erlang maintainer will tell you, "it's a last resort and you would probably have been fine with a port program, which provides much better isolation.")
But this means that the VM is entirely allowed to interrupt you halfway through a call to sorting a list obtained through a message. No qualms about it. You reach you function call limit, you're phased out and put back in the queue.
The three ways I know about to hog a scheduler for yourself are:
- write C code that misbehaves and stalls the scheduler (there is now a dirty scheduler if you want the code to run there)
- hardcode a sequence of arithmetic operations (which are not subject to reduction counts) that can take a very long time to execute -- something that never happens naturally in code
- set your process to the max priority and do busy looping while the rest of the system is at a lower priority. Your process will now have a higher priority and prevent others from running
That's about what you can do. It requires a kind of explicit effort to do and is very easy to spot from afar.