Skip to content

Quick start

  1. Start the server.

    Terminal window
    docker run -p 7070:7070 ghcr.io/monolock-dev/monolock

    This is a complete server in its production shape: no config file, no database, no dependencies. Each setting is a flag, and each flag has an equivalent environment variable. See Configuration.

  2. Acquire a lock from your code.

    Terminal window
    go get github.com/monolock-dev/monolock-go
    import (
    "context"
    "time"
    monolock "github.com/monolock-dev/monolock-go"
    )
    func main() {
    c := monolock.New(monolock.Config{Address: "127.0.0.1:7070"})
    // Blocks until this process owns "nightly-import", runs the
    // function, and releases the lock when it returns.
    err := c.Do(context.Background(), "nightly-import", 2*time.Second,
    func(ctx context.Context, token uint64) error {
    // Only one process across your fleet runs this at a time.
    // ctx is cancelled the moment ownership stops being
    // confirmed; token fences out stale holders (see below).
    return doTheWork(ctx, token)
    })
    if err != nil {
    // ...
    }
    }
  3. See the handover.

    Run the program two times at the same time. The second process goes into the first-in, first-out (FIFO) queue and prints nothing. When you stop the first process, the server promotes the second process immediately. There is no timeout to wait for. If you stop the first process with kill -9, the handover takes a maximum of the lease (2 seconds above). This is the failure-detection window that you selected in the call.

sequenceDiagram
    participant A as worker A
    participant S as monolock
    participant B as worker B
    A->>S: ACQUIRE "nightly-import" lease=2s
    S-->>A: ACQUIRED token=41
    B->>S: ACQUIRE "nightly-import" lease=2s
    S-->>B: WAITING (queued, FIFO)
    par holder works
        loop every lease/4
            A->>S: HEARTBEAT
            S-->>A: ACQUIRED token=41
        end
    and waiter keeps its place
        loop every lease/4
            B->>S: HEARTBEAT
            S-->>B: WAITING
        end
    end
    A--xS: connection closed
    S-->>B: ACQUIRED token=42

Each connection makes a claim on one named lock. The owner holds the lock while its heartbeats continue to arrive. The waiter also sends heartbeats, and thus it keeps its position in the queue. When the connection of the owner closes in a controlled stop, the server promotes the subsequent waiter immediately. When the owner stops silently, the server waits for the lease that the client selected. Then the server moves the lock.

Examine the tokens: 41, then 42. Each grant contains a fencing token that is strictly larger than each earlier token. Give the token to the resource that your lock guards. The resource then rejects writes from a stale holder — a holder that lost the lock and does not know it yet. This is the difference between hope that mutual exclusion holds and enforcement by the resource. Read Fencing tokens.