distribute

distribute logo

Package Version Hex Docs

Distribute brings the full power of Erlang’s distributed computing to Gleam, with a type-safe and idiomatic API.

While Gleam runs on the BEAM, accessing distributed primitives (like connecting nodes, global registration, or RPC) often requires writing raw Erlang FFI or dealing with untyped atoms. Distribute solves this by providing a robust, type-safe layer over these primitives, making it easy to build distributed systems without leaving Gleam.

Features

Installation

gleam add distribute

Documentation

Quick Start

1. Start a Distributed Node

import distribute/cluster
import gleam/io
import gleam/string

pub fn main() {
  // Start this node as "app@127.0.0.1" with cookie "secret"
  case cluster.start_node("app@127.0.0.1", "secret") {
    Ok(Nil) -> io.println("Node started successfully!")
    Error(err) -> io.println("Failed to start: " <> string.inspect(err))
  }
}

2. Connect to Another Node

import distribute/cluster
import gleam/io
import gleam/string

pub fn connect_peer() {
  case cluster.connect("other@127.0.0.1") {
    Ok(_) -> io.println("Connected to peer!")
    Error(err) -> io.println("Connection failed: " <> string.inspect(err))
  }
}

3. Register and Send Messages Globally

import distribute/registry
import distribute/messaging
import distribute/monitor

pub fn register_and_send() {
  // Register current process globally
  let pid = monitor.self()
  let _ = registry.register("my_service", pid)

  // Send a message to a globally registered name
  let _ = messaging.send_global("my_service", "Hello, world!")
}

4. Process Groups

import distribute/groups
import distribute/monitor

pub fn use_groups() {
  let pid = monitor.self()

  // Join a group named "workers"
  let _ = groups.join("workers", pid)

  // Broadcast a message to all members of "workers"
  let _ = groups.broadcast("workers", #("task", 42))

  // Get a list of all members in the group
  let members = groups.members("workers")
}

5. SWIM Membership Service

import distribute/cluster/membership

pub fn use_membership() {
  // Start the background membership service (probe every 500ms)
  membership.start_service(500)

  // Get a list of alive nodes
  let alive = membership.alive()

  // Get nodes with full status details
  let nodes = membership.members_with_status()
  // Returns: [#("node@host", Alive, 0), ...]

  // Get current leader (lexicographically largest alive node)
  let leader = membership.current_leader()

  // Stop the service when done
  membership.stop_service()
}

6. Leader Election (Raft-lite)

import distribute/election/raft_lite
import gleam/io

pub fn elect_leader() {
  case raft_lite.elect() {
    raft_lite.Leader(name) -> io.println("Current Leader: " <> name)
    raft_lite.NoLeader -> io.println("No leader available yet")
  }
}

7. Remote Procedure Calls

import distribute/remote_call
import gleam/io
import gleam/string

pub fn call_remote() {
  // Call erlang:node() on a remote node
  case remote_call.call("other@host", "erlang", "node", [], []) {
    Ok(result) -> io.println("Remote node name: " <> string.inspect(result))
    Error(remote_call.RpcBadRpc(reason)) -> io.println("RPC failed: " <> reason)
    Error(_) -> io.println("RPC error")
  }
}

Module Reference

ModuleDescription
distribute/clusterNode management: start, connect, ping, list nodes
distribute/registryGlobal process registration (uses :global)
distribute/messagingSend messages to PIDs or global names
distribute/groupsProcess groups with join/leave/broadcast (uses :pg)
distribute/monitorMonitor processes and nodes
distribute/remote_callRPC to remote nodes
distribute/cluster/membershipSWIM-like membership with gossip and failure detection
distribute/cluster/gossipGossip protocol for membership state propagation
distribute/cluster/healthHealth checks for nodes and cluster
distribute/election/raft_liteLightweight leader election with term-based voting

Note: cluster.connect_bool is kept for backward compatibility but is deprecated. Please prefer the Result-returning cluster.connect which provides structured error handling.

Running Tests

gleam test

Integration Tests

You can run a multi-node SWIM integration test using the provided script:

./examples/two_nodes/swim_integration.sh

This script starts 3 local Erlang nodes, runs the membership service, and verifies gossip convergence.

Note: Scripts under examples/ are manual integration demos. They rely on local node naming (-sname + hostname) and are not intended to be a CI hard requirement.

Architecture

src/
├── distribute.gleam        # Top-level module
├── distribute/
│   ├── cluster.gleam       # Node management
│   ├── registry.gleam      # Global registry
│   ├── messaging.gleam     # Cross-node messaging
│   ├── groups.gleam        # Process groups
│   ├── monitor.gleam       # Process/node monitoring
│   ├── remote_call.gleam   # RPC
│   ├── cluster/
│   │   ├── membership.gleam # SWIM-like membership
│   │   ├── gossip.gleam     # Gossip protocol
│   │   └── health.gleam     # Health checks
│   └── election/
│       └── raft_lite.gleam  # Leader election
└── *_ffi.erl               # Erlang FFI files (in src root)

Design Philosophy

  1. No magic — Everything is explicit.
  2. Zero custom runtime — Only wraps standard BEAM features.
  3. Small but complete APIs — Just what you need for clustering.
  4. Type-safe — Gleam’s type system prevents common errors.
  5. Compatible with gleam/otp — Works alongside standard OTP patterns.

Safety

Settings

This library exposes a small settings API which controls behaviour that impacts safety and logging.

Example:

import settings

pub fn main() {
  // Secure default — don't allow uncontrolled atom creation
  settings.set_allow_atom_creation(False)

  // Prdistribute/settings

pub fn main() {
  // Secure default — don't allow uncontrolled atom creation
  settings.set_allow_atom_creation(False)

  // Prefer crypto-based correlation ids
  settings.set_use_crypto_ids(True)
}

Logging & Correlation IDs

This library provides a structured logging helper log with metadata and correlation ids.

Example:

import distribute/log
import distribute/settings

pub fn main() {
  settings.set_use_crypto_ids(True)
  log.set_backend("erlang_logger")
  let id = log.generate_correlation_id()
  log.info_with_correlation("Starting operation", [#("user","alice")], id)
}

RPC Timeouts & Error Handling

Remote calls use a default timeout of 5000ms. Use remote_call.call_with_timeout to customise the timeout for long-running calls.

Example with a custom timeout:

import gleam/io
import gleam/string
import distribute/
}

Examples

Contributing

Contributions welcome! Please open an issue or PR on GitHub.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Search Document