README.md

# Cnodex

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.

> 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.

Available as a hex-package:

```elixir
{:cnodex, "~> 0.1.0"}
```

## Mount a C-Node inside your supervision tree

Mount Cnodex as a worker, and reference it by name:
```elixir
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:

```elixir
{: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"})
```

## Implementation

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

You can either provide a running C-Node to connect to, but 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 gen\_server that will wait for such a `ready_line` on the stdout of the C-Node it just started.
The C-Node startup and readyness handling is synchronous. After a configurable `spawn_inactive_timeout` the gen\_server crashes the `init` procedure, and again supervision can take care of the failued startup.

When you shutdown the Cnodex gen\_server, it will issue a SIGTERM to the OS-pid of the C-Node, to ensure you have no lingering processes.

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 receive a proper error, like calling an unavailable gen\_server.

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

In case the C-Node becomes unavailable, the gen\_server will be notified due to the node monitor.
The gen\_server exits too, so that supervision logic can restart the gen\_server and it will start a new C-Node.

## Helper modules for testing

`Cnodex.Distributed` can be used to programatically start and stop a distributed Elixir environment.
This is especially helpful, since you can only use C-Nodes if you have **epmd** and **:net_kernel** running.

So in test cases, you can simply use `Cnodex.Distributed.up()` to start **epmd** and **:net_kernel**.

## 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:

```
# 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
    │
    # An example test case on how to test C-Nodes and C-Node communication
    └── cnodex_test.exs
```

In particular `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.

## Performance

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

```console
## 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.

## Documentation

The docs can be found at [https://hexdocs.pm/cnodex](https://hexdocs.pm/cnodex).