Barnacle

Package Version Hex Docs

Self-healing clusters for Gleam applications on the BEAM!

Connect to other BEAM nodes, and automatically reconnect when they go down.

Features

Getting Started

gleam add barnacle

You’ll also need to start your Gleam process with a node name, and set the cookie. You can do this using the ERL_FLAGS environment variable.

There’s a Fly.io example coming soon.

ERL_FLAGS="-name my_app@127.0.0.1 -setcookie my_cookie" gleam run

Start Barnacle under a Supervisor (recommended)

import barnacle
import gleam/erlang/process
import gleam/otp/supervisor

pub fn main() {
  // Configure your Barnacle
  let barnacle =
    barnacle.local_epmd()
    |> barnacle.with_poll_interval(15_000)
    |> barnacle.with_name("my_barnacle")

  // Create a process to receive the child process later
  let self = process.new_subject()

  // Start the child process under a supervisor
  let barnacle_worker = barnacle.child_spec(barnacle, None)
  let assert Ok(_) = supervisor.start(supervisor.add(_, barnacle_worker))

  // Get a subject to send messages to the child process
  let assert Ok(barnacle_subject) = process.receive(self, 10_000)

  // Continue your setup...

  process.sleep_forever()
}

Start Barnacle as a standalone actor

This is not recommended as your barnacle won’t be restarted if it crashes for any reason.

import barnacle
import gleam/erlang/process

pub fn main() {
  // Configure your Barnacle
  let barnacle =
    barnacle.local_epmd()
    |> barnacle.with_poll_interval(15_000)
    |> barnacle.with_name("my_barnacle")

  // Start the actor
  let barnacle_subject = barnacle.start(barnacle)

  // Continue your setup...

  process.sleep_forever()
}

Run the refresh function once

If you don’t need any sort of polling, or want to manage your the refresh lifecycle yourself, you can call the run_once function to run the refresh function a single time.

In this case, most of the configuration options will be ignored.

import barnacle

pub fn main() {
  // Configure your Barnacle
  let barnacle =
    barnacle.local_epmd()

  // Run the refresh function once
  barnacle.run_once(barnacle)

  // Continue your program...
}

Strategies

Barnacle ships with a few built-in strategies, but you can also supply your own by providing a single callback function.

Local EPMD

Discover nodes using the local EPMD. This will automatically attempt to connect to nodes that are running on the same machine.

import barnacle

pub fn main() {
  barnacle.local_epmd()
  |> barnacle.start
}

Remote EPMD

Connect to nodes using a known list.

import barnacle
import gleam/erlang/atom

pub fn main() {
  barnacle.epmd(
    ["node1@192.168.1.1", "node2@192.168.1.2"]
    |> list.map(atom.create_from_string),
  )
  |> barnacle.start
}

DNS

Discover nodes using DNS.

The first argument is the basename of the node. Currently, Barnacle only supports connecting to nodes with the same basename.

Barnacle provides a helper function to get the basename of the current node.

import barnacle

pub fn main() {
  let assert Ok(basename) = barnacle.get_node_basename(node.self())
  barnacle.dns(
    basename,
    // The hostname to query against
    "my_app.example.com",
  )
  |> barnacle.start
}

Creating a custom strategy

You can create your own strategy using the new_strategy function.

import barnacle

pub fn main() {
  barnacle.new_strategy(
    // Discover nodes
    fn() {
      Ok([])
    },
  )
  |> barnacle.custom
  |> barnacle.start
}

You can also supply your own functions for connecting, disconnecting, and listing nodes. If these are not supplied, the built-in functions will be used.

This may be useful if you want to use an alternative to Distributed Erlang, such as Partisan.

import barnacle

pub fn main() {
  barnacle.new_strategy(my_discover_function)
  |> barnacle.with_connect_nodes_function(my_connect_function)
  |> barnacle.with_disconnect_nodes_function(my_disconnect_function)
  |> barnacle.with_list_nodes_function(my_list_function)
  |> barnacle.custom
  |> barnacle.start
}

You can find more information about creating custom strategies in the docs.

Events

You can provide a custom subject to receive events from your barnacle. This will be notified whenever the barnacle refreshes, is paused, or is shutdown.

import barnacle
import gleam/erlang/process

pub fn main() {
  let self = process.new_subject()

  // Configure your Barnacle
  let barnacle =
    barnacle.local_epmd()
      |> barnacle.with_poll_interval(15_000)
      |> barnacle.with_name("my_barnacle")
      |> barnacle.with_listener(self)

  // Start the actor
  barnacle.start(barnacle)

  // Wait for the barnacle to refresh
  process.sleep(10_000)
  let assert Ok(barnacle.RefreshResponse(Ok(new_nodes))) = process.receive(self, 10_000)
}

Manual interactions

You can interact with your barnacle manually by sending messages to it.

import barnacle
import gleam/erlang/process
import gleam/otp/actor

pub fn main() {
  // Start a barnacle
  let barnacle_subject =
    barnacle.local_epmd()
    |> barnacle.start

  let assert Ok(_) = barnacle.pause(barnacle_subject, 1000)
  let assert Ok(_) = barnacle.refresh(barnacle_subject, 10_000)
  let assert Ok(_) = barnacle.shutdown(barnacle_subject, 1000)
}

Further documentation can be found at https://hexdocs.pm/barnacle.

Development

gleam run   # Run the project
gleam test  # Run the tests

If you would like to contribute, please open an issue or a PR. New strategies are welcome, though try to keep dependencies to a minimum.

TODO

With thanks

A lot of inspiration came from the following projects:

Thanks!

Search Document