#leader-election #distributed-lock #spanner

spindle_rs

A distributed locking crate built on Cloud Spanner and TrueTime

28 releases

Uses new Rust 2024

new 0.2.13 Apr 11, 2025
0.2.12 Apr 10, 2025
0.2.10 Mar 30, 2025
0.1.13 Mar 19, 2025

#372 in Concurrency

Download history 443/week @ 2025-03-10 577/week @ 2025-03-17 883/week @ 2025-03-24 269/week @ 2025-03-31 400/week @ 2025-04-07

2,435 downloads per month
Used in hedge_rs

Apache-2.0

37KB
646 lines

main crates.io docs-rs

Overview

This crate implements distributed locking using Cloud Spanner. It relies on Spanner's TrueTime and transactions support to achieve its locking mechanism. It's a port of the original spindle, which is written in Go.

Use cases

One use case for this library is leader election. If you want one host/node/pod to be the leader within a cluster/group, you can achieve that with this crate. When the leader fails, it will fail over to another host/node/pod within a specific timeout. That said, you might want to check out hedge-rs, which is a memberlist tracking library built on top of this crate.

Usage

At the moment, the table needs to be created beforehand using the following DDL (locktable is just an example):

CREATE TABLE locktable (
    name STRING(MAX) NOT NULL,
    heartbeat TIMESTAMP OPTIONS (allow_commit_timestamp=true),
    token TIMESTAMP OPTIONS (allow_commit_timestamp=true),
    writer STRING(MAX),
) PRIMARY KEY (name)

After creating the lock object, you will call the run() function which will attempt to acquire a named lock at a regular interval (lease duration). A has_lock() function is provided which returns true (along with the lock token) if the lock is successfully acquired. Something like:

use spindle_rs::*;
...

fn main() -> Result<(), Box<dyn Error>> {
    let (tx, rx) = channel();
    ctrlc::set_handler(move || tx.send(()).unwrap())?;
    let mut lock = LockBuilder::new()
        .db("projects/p/instances/i/databases/db".to_string())
        .table("locktable".to_string()) // see CREATE TABLE
        .name("spindle-rs".to_string()) // lock name
        .duration_ms(3000)
        .build();

    lock.run()?;

    // Wait for a bit before calling has_lock().
    thread::sleep(Duration::from_secs(10));
    let (locked, node, token) = lock.has_lock();
    println!("has_lock: {locked}, {node}, {token}");

    // Wait for Ctrl-C.
    rx.recv()?;
    lock.close();

    Ok(())
}

Example

A sample code is provided to demonstrate the mechanism through logs. You can try running multiple processes in multiple terminals.

$ git clone https://github.com/flowerinthenight/spindle-rs
$ cd spindle-rs/
$ cargo build
# Update args with your actual values:
#   args[1] = database string
#   args[2] = table name
$ RUST_LOG=info ./target/debug/example \
  projects/p/instances/i/databases/db \
  locktable

The leader process should output something like leader active (me). You can then try to stop (Ctrl+C) that process and observe another one taking over as leader.

License

This library is licensed under the Apache 2.0 License.

Dependencies

~25–34MB
~506K SLoC