![]() |
|
![]() |
| I’ve been eyeing gleam as my next language to learn. Lots to like about it for sure, and I have always like the idea of OTO but never had an opportunity to tinker with it. |
![]() |
| That's the thing though: a NIF execution isn't confined to the the BEAM process by its nature. From the Erlang docs:
> As a NIF library is dynamically linked into the emulator process, this is the fastest way of calling C-code from Erlang (alongside port drivers). Calling NIFs requires no context switches. But it is also the least safe, because a crash in a NIF brings the emulator down too. (https://www.erlang.org/doc/system/nif.html) The emulator in this context is the BEAM VM that is running the whole application (including the supervisors). Apparently Rustler has a way of running Rust NIFs but capturing Rust panics before they trickle down and crash the whole BEAM VM, but that seems like more of a Rust trick that Pythonx likely doesn't have. The tl;dr is that NIFs are risky by default, and not really... Elixironic? |
![]() |
| I love to see "well-known" people in the Elixir community endorsing and actively developing that kind of approach. Our VM and runtime does so much and is so well suited to orchestrating other languages and tech that it sometimes feels there's a standard track and an off-road track.
The difference between an off-road "sounds dangerous" idea and its safe execution is often only the quantity of work but our runtime encourages that. Here, it's a NIF so there's still a bit of risk, but it's always possible to spawn a separate BEAM instance and distribute yourself with it. Toy example that illustrates it, first crashing with a NIF that is made to segfault :
In the second example, we have a "SafeNif" module that spawns another elixir node, connects to it, and runs the unsafe operation on it.
Thankfully Python, Zig and Rust should be good to go without that kind of dance :) . |
![]() |
| Its a neat way to do it - spin a temporary one, which can crash all it wants without affecting the other nodes. Fits like a glove to BEAM. |
![]() |
| Sorry, I should have been more explicit: better for on the user facing implementation side (concurrency, streaming data, molding agent state, etc) vs the training side of things. If that makes sense. |
![]() |
| Ah, fair enough. I've not done much with Elixir but I have done a fair amount with Erlang and you certainly don't need to sell me on how great it is for concurrency and distributed stuff. |
![]() |
| Yeah, looks like it works fine, here's an example: https://pastebin.pl/view/a10aea3d
I'll add: FLAME is probably a great addition to pythonx. While a NIF can crash the node it is executed on, FLAME calls are executed on other nodes by default. So a crash here would only hard-crash processes on the same node (FLAME lets you group calls so that a flame node can have many being executed on it at any time). Errors bubble back up to the calling process (and crash it by default but can be handled explicitly), so managing and retrying failed calls is easy. |
![]() |
| uv for Python is a game changer, better than anything else out there and solves a lot of the core problems with pip/venv/poetry/pyenv (the list goes on). |
![]() |
| I was super excited until I read:
"...if you are using this library to integrate with Python, make sure it happens in a single Elixir process..." |
![]() |
| I'm using Python script in my Phoenix app (not Livebook). I hoped Pythonx would solve all the issues associated with System.cmd, but as you can imagine, I have more than one user. |
![]() |
| “Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should”
just kidding, this is pretty cool. |
![]() |
| In a way Python is a bad Lisp, still looking forward that catches up in native code compilation and multiline lambdas.
Could be better, but that is what mainstream gets. |
For production servers, Pythonx is a bit more risky (and the developers aren't claiming it's the right tool for this use case). Because it's running on the same OS process as your Elixir app, you bypass the failure recovery that makes an Elixir/BEAM application so powerful.
Normally, an Elixir app has a supervision tree that can gracefully handle failures of its own BEAM processes (an internal concurrency unit -- kind like a synthetic OS process) and keep the rest of the app's processes running. That's one of the big selling points of languages like Elixir, Erlang, and Gleam that build upon the BEAM architecture.
Because it uses NIFs (natively-implemented functions), an unhandled exception in Pythonx would take down your whole OS process along with all other BEAM processes, making your supervision tree a bit worthless in that regard.
There are cases when NIFs are super helpful (for instance, Rustler is a popular NIF wrapper for Rust in Elixir), but you have to architect around the fact that it could take down the whole app. Using Ports (Erlang and Elixir's long-standing external execution handler) to run other native code like Python or Rust is less risky in this respect because the non-Elixir code it's still running in a separate OS process.