spindle

package module
v0.1.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 26, 2025 License: Apache-2.0 Imports: 14 Imported by: 1

README

main Go Reference

[!IMPORTANT]
This library is still using the main-2.0 branch of the ClockBound daemon.

spindle-cb

A distributed locking library (port of spindle) built on aws/clock-bound, and PostgreSQL (storage).

Using this library requires the ClockBound daemon (making it AWS-only), and CGO, due to its dependency on the clockbound-ffi-go library.

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 library. 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-cb, which is a memberlist tracking library built on top of this library.

Usage

At the moment, the table needs to be created beforehand (spindle and locktable are just examples):

-- create the database:
CREATE DATABASE spindle;

-- create the table:
CREATE TABLE locktable (
    name TEXT PRIMARY KEY,
    heartbeat TIMESTAMP,
    token TIMESTAMP,
    writer TEXT
);

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) until cancelled. A HasLock() function is provided which returns true (along with the lock token) if the lock is successfully acquired. Something like:

import (
    ...
    "github.com/flowerinthenight/spindle-cb"
    _ "github.com/jackc/pgx/v5/stdlib"
)

func main() {
    // error checks redacted
    db, _ := sql.Open("pgx", *dbstr)
    defer db.Close()

    done := make(chan error, 1) // notify me when done (optional)
    quit, cancel := context.WithCancel(context.Background()) // for cancel

    // Create the lock object using a 5s lease duration using 'locktable' above.
    lock := spindle.New(db, "locktable", "mylock", spindle.WithDuration(5000))

    lock.Run(quit, done) // start the main loop, async

    time.Sleep(time.Second * 20)
    locked, token := lock.HasLock()
    log.Println("HasLock:", locked, token)
    time.Sleep(time.Second * 20)

    cancel()
    <-done
}

Running the sample

A sample cloud-init startup script is provided for spinning up an Auto Scaling Group with the ClockBound daemon already setup and running.

# Create a launch template. ImageId here is Amazon Linux, default VPC.
# You can remove the "KeyName" line if SSH access is not needed.
# (Added newlines for readability. Might not run when copied as is.)
$ aws ec2 create-launch-template \
  --launch-template-name spindle-lt \
  --version-description version1 \
  --launch-template-data '
  {
    "UserData":"'"$(cat startup-aws-asg.sh | base64 -w 0)"'",
    "ImageId":"ami-0fe289b44779ce58a",
    "InstanceType":"t3.medium",
    "KeyName":"keyName"
  }'

# Create the ASG; update {target-zone} with actual value:
$ aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name spindle-asg \
  --launch-template LaunchTemplateName=spindle-lt,Version='1' \
  --min-size 1 \
  --max-size 1 \
  --tags Key=Name,Value=spindle-asg \
  --availability-zones {target-zone}

# You can now SSH to the instance. Note that it might take some time before
# ClockBound is running due to the need to build it in Rust. You can wait
# for the `clockbound` process, or tail the startup script output, like so:
$ tail -f /var/log/cloud-init-output.log

# Run the sample code:
# Download the latest release sample from GitHub.
$ tar xvzf spindle-{version}-x86_64-linux.tar.gz

# Run multiple instances of `example` to see locking taking place:
$ ./example -db postgres://postgres:pass@loc.rds.amazonaws.com:5432/spindle

License

This library is licensed under the Apache 2.0 License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotRunning = fmt.Errorf("spindle: not running")
)

Functions

This section is empty.

Types

type FnLeaderCallback

type FnLeaderCallback func(data any, msg []byte)

type Lock

type Lock struct {
	// contains filtered or unexported fields
}

func New

func New(db *sql.DB, table, name string, o ...Option) *Lock

New returns a lock object with a default of 10s lease duration.

func (*Lock) Client

func (l *Lock) Client() *sql.DB

Client returns the SQL client.

func (*Lock) Duration

func (l *Lock) Duration() int64

Duration returns the duration in main loop in milliseconds.

func (*Lock) HasLock

func (l *Lock) HasLock() (bool, uint64)

HasLock returns true if this instance got the lock, together with the lock token.

func (*Lock) HasLock2

func (l *Lock) HasLock2() (bool, string, uint64)

HasLock2 is the same as HasLock but returns the leader id as well. Recommended instead of calling both HasLock() and Leader() functions.

func (*Lock) Iterations

func (l *Lock) Iterations() int64

Iterations returns the number of iterations done by the main loop.

func (*Lock) Leader

func (l *Lock) Leader() (string, error)

Leader returns the current leader id.

func (*Lock) Run

func (l *Lock) Run(ctx context.Context, done ...chan error) error

Run starts the main lock loop which can be canceled using the input context. You can provide an optional done channel if you want to be notified when the loop is done.

type Option

type Option interface {
	Apply(*Lock)
}

func WithDuration

func WithDuration(v int64) Option

WithDuration sets the locker's lease duration in ms. Minimum is 1000ms.

func WithId

func WithId(v string) Option

WithId sets this instance's unique id.

func WithLeaderCallback

func WithLeaderCallback(d any, h FnLeaderCallback) Option

WithLeaderCallback sets the node's callback function when it a leader is selected (or deselected). The msg arg for h will be set to either 0 or 1.

func WithLogger

func WithLogger(v *log.Logger) Option

WithLogger sets the locker's logger object.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL