Overbryd

Overbryd

Nodex: Helper modules around distributed Elixir and C-Nodes

A set of helper modules that enable you to work with distributed elixir and c-nodes.

Available as a hex-package:

{:nodex, "~> 0.1.0"}

Documentation

The docs can be found at https://hexdocs.pm/nodex.

Nodex.Distributed

A module to setup a distributed environment programmatically.
It takes care of starting epmd, starts :net_kernel for you and can start and maintain child-nodes.

iex> Nodex.Distributed.up
iex> Node.alive?
true
iex> Node.self()
:"master@127.0.0.1"
iex> Nodex.Distributed.spawn_slaves(2)
[:"slave1@127.0.0.1", :"slave2@127.0.0.1"]

Nodex.Cnode

Helper module to simplify working with a C-Node. It is also allows you to start and monitor
an external C-Node process within a supervision tree.

What is a “Cnode”?

C-Nodes are external os-processes that communicate with the Erlang VM through erlang messaging.
That way you can implement native code and call into it from Elixir in a safe predictable way.
The Erlang VM stays unaffected by crashes of the external process.

In my opinion C-Nodes are the best option if you need to call into native code.
The calling overhead is small, and on par with the calling overhead of remote node communication.
And you get monitoring abilities through Node.monitor(:node@host.example).
You can scale your C-Nodes onto multiple machines.

So instead of exposing your application to the risks that come with NIFs, you can enclose them in
an external OS-process. And even better than port drivers, you gain all of the benefits including
scalability.

The repository includes a benchmark comparing local, remote and cnode calling performance:

## VmVsCnodeBench
[20:16:07] 1/4: cnode
[20:16:09] 2/4: cnode direct
[20:16:12] 3/4: local
[20:16:14] 4/4: remote

Finished in 11.49 seconds

## VmVsCnodeBench
benchmark nam iterations   average time 
local            1000000   1.90 µs/op
cnode direct       50000   37.30 µs/op
cnode              50000   41.43 µs/op
remote             50000   48.64 µs/op

Executed on a MacBook Pro 2,5GHz.

Mount a C-Node inside your supervision tree

Mount Cnodex as a worker, and reference it by name:

children = [
  worker(Cnodex, [%{exec_path: "priv/example_client"}], name: :ExampleClient)
]
Supervisor.init(children, strategy: :one_for_one)

Later call into your C-node through Cnodex:

{:ok, reply} = Cnodex.call(:ExampleClient, {:ping, "hello world"})

Start and call into a C-Node

{:ok, pid} = Cnodex.start_link(%{exec_path: "priv/example_client"})
{:ok, reply} = Cnodex.call(pid, {:ping, "hello world"})

Nodex.Cnode Implementation details

Your supervisor spawns a GenServer worker process that is using a node monitor on the C-Node.

You can provide a running C-Node to connect to. You must provide an executable with startup arguments that implement a suitable C-Node.

A suitable C-Node prints a ready_line to stdout when it is ready to accept messages.
This mechanism signals readyness to the monitoring process.
The C-Node startup and ready-handling is synchronous. After a configurable spawn_inactive_timeout the init procedure is interrupted, and again supervision can take care of the failued startup.

When you shutdown Nodex.Cnode, it will issue a SIGTERM to the os-pid of the C-Node, to ensure you have no lingering processes.
Although it is even better if you implement some mechanism in the C-Node to ensure it exits properly after it looses the connection to Erlang.

The safest option to call into the C-Node is going through the Cnodex.call/2 or Cnodex.call/3 function.
It will, using a configurable timeout, await a response from the C-Node within the calling process.
In case the C-Node is unavailable, you will be thrown a proper exception, because you are calling an unavailable GenServer.

If you don’t want your process inbox to be hijacked waiting for the C-Node response, use a Task in combination with Cnodex.call/2.

In case the C-Node becomes unavailable, the Cnode GenServer terminates too.
Your supervisor can then take care starting a new C-Node.

Writing a C-Node

This repository provides you with some good starting points for writing a project or package that
encloses a C-Node.

Checkout the following files of this repository:

# A proper Makefile is a great base for building C
├── Makefile
│
├── bench
│   │
│   # How to benchmark a C-Node
│   └── vm_vs_cnode_bench.exs
│
# Directory with C source files
├── c_src
│   │
│   # An example C-Node client that connects back to your node
│   └── example_client.c
│
# A mix file that defines a custom task for handling Makefile builds
├── mix.exs
│
# The priv directory should contain your C build artifacts
├── priv
│   │
│   # This is the example C-Node client that gets build
│   └── example_client
│
└── test
    └── nodex
            │
            # An example test case on how to test C-Nodes and C-Node communication
            └── cnode_test.exs

In particular c_src/example_client.c contains a nice boilerplate for writing your own C-Node client that
connects back to your node. It is fully annotated, so have a look to find out what is going on there.

The general idea behind this is, that you start up a C-program, and in the startup arguments you provide the
connection information to your Elixir node. If everything goes well, your C-program prints a shared message
to STDOUT.
The Elixir side that started the C-program will read all the STDOUT lines written by the C-program.
If the correct shared message appears, it assumes the C-program is now ready to accept messages.
And it will establish a node monitor on the C-program, so that if the connection goes down, the Elixir side
is notified of the loss.

It is best to write the C-program in a way that is exits cleanly as soon as it looses the connection or something goes wrong.
That aligns the C-program with the idea that it can be controlled by a supervisor.

First Post! Switch mode

OvermindDL1

OvermindDL1

Oooo very nice!

This whole thing looks awesome! :slight_smile:

— All posts loaded —

Where Next?

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 91561 914
New
byu
@chrismccord : I just saw the Extract AGENTS.md from Phoenix.new into phx.new generator commit to the phoenix project. My initial shotgu...
New
arcanemachine
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
AstonJ
Just a general thread to post chat/news/info relating to AI/ML stuff that may be relevant for Nx now or in the future. Got anything to sh...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New

We're in Beta

About us Mission Statement