Documentation
¶
Overview ¶
Package monolock is the Go client for a monolock server.
One lock is one TCP connection. Do blocks until this process owns the named lock, runs the given function for exactly as long as ownership is confirmed by the server, and releases the lock on the way out. When the function's context is cancelled the work it guards must stop; a new Do call queues up for a fresh turn.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrHeartbeatTimeout means no WAITING or ACQUIRED arrived before the // client side confirmation deadline. The lock must be treated as lost. ErrHeartbeatTimeout = errors.New("monolock: heartbeat confirmation timeout") // ErrProtocolViolation means the server sent something illegal for the // current state, e.g. WAITING to a session that already owns the lock. ErrProtocolViolation = errors.New("monolock: protocol violation") // ErrConnectionClosed means the server closed the connection. ErrConnectionClosed = errors.New("monolock: connection closed") )
Client-side conditions: states the client diagnoses on its own, with no ERROR code on the wire. Everything the server itself reports arrives as a *ServerError instead, and Acquire's input validation returns the protocol's own errors (protocol.ErrEmptyName, protocol.ErrNameTooLong, protocol.ErrInvalidLease).
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client dials a monolock server. It is nothing but configuration: safe for concurrent use, holding several different locks at once, one connection each, with nothing to close. Each lock lives exactly as long as its Do call.
func New ¶
New returns a client for cfg. It does not connect; connections are made per Do call, so a bad Address surfaces as a dial error there.
func (*Client) Do ¶
func (c *Client) Do(ctx context.Context, lockName string, lease time.Duration, fn func(ctx context.Context, token uint64) error) error
Do claims lockName, blocks until this client owns it, runs fn while the server keeps confirming ownership, and releases the lock on the way out — also when fn panics. Closing the connection is the release, so the next waiter is promoted at once.
fn receives the lock context and the fencing token of the grant. The context is cancelled on heartbeat confirmation timeout, EOF, connection error, server shutdown, or cancellation of ctx; fn must stop the work it guards the moment that happens. The token grows with every grant the server hands out: pass it along with every operation against the resource the lock guards and have the resource reject anything carrying a smaller token than the largest it has seen — that fences out a previous holder that woke up after losing the lock.
lease is this connection's failure-detection window: the server drops the session once it stays silent for that long, so it bounds how long the lock may sit behind a dead holder. Pick the smallest value that survives the network pauses between you and the server. It must be positive and fit the protocol's uint32 millisecond field.
Cancelling ctx is a graceful stop, not a loss. While still queueing, Do aborts and returns ctx.Err() at once. Once fn is running, the cancellation only cancels fn's context — the lock stays held, heartbeats and all, until fn winds down and returns, so a routine shutdown never hands the lock over while the work is still finishing; Do then releases and returns fn's error alone. The safety net for a holder that cannot wind down is the lease itself: kill the process and the lock moves on within it.
Do makes exactly one attempt. Invalid input comes back as the protocol's own validation errors (protocol.ErrEmptyName, protocol.ErrNameTooLong, protocol.ErrInvalidLease); a dial failure, a connection dropped while waiting in the queue, or a server rejection is returned as is, and a *ServerError reports through Temporary whether a new attempt may help. Retrying — and pacing the retries — is the caller's decision; every attempt is a fresh claim and therefore a fresh position at the back of the FIFO queue.
Once fn runs, Do returns fn's error. If the lock was lost while fn was still running, the cause (ErrHeartbeatTimeout, ErrConnectionClosed, a *ServerError, ...) is joined in even when fn returned nil, because fn's last actions may have run without the lock.
func (*Client) DoRetry ¶
func (c *Client) DoRetry(ctx context.Context, lockName string, lease time.Duration, fn func(ctx context.Context, token uint64) error) error
DoRetry is Do with the acquisition retried. A failure to get the lock that may pass on a new attempt — a dropped dial or connection, the server shutting down, any rejection Temporary says yes to — is retried with exponential backoff and jitter, from 100ms doubling to 5s, until ctx is cancelled. Invalid input and permanent rejections return at once. Every attempt is a fresh claim and therefore a fresh position at the back of the FIFO queue.
Once fn has run, its outcome is returned exactly as Do would, with no second run: whether half-done work may be repeated is a property of the work, not of the lock, so re-running fn stays the caller's decision.
type Config ¶
type Config struct {
// Address is the "host:port" of the monolock server. Required.
Address string
// Dialer establishes connections, one per Do, and is the place to
// configure how they are made: a *tls.Dialer is how a server running
// with -tls-cert is reached (its client certificate is this client's
// identity for the server's ACL and audit log), a *net.Dialer tunes
// dial timeouts, keep-alives or the source address. Nil means a plain
// &net.Dialer{}, whose dial is bounded only by the Do context.
Dialer ContextDialer
}
Config configures a Client.
type ContextDialer ¶
type ContextDialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
ContextDialer establishes the client's connections; *net.Dialer and *tls.Dialer both satisfy it.
type ServerError ¶
ServerError is an ERROR message received from the server. Reason is the human-readable text the server sent, possibly empty. Branch on Code, or match the canonical protocol errors: errors.Is(err, protocol.ErrShuttingDown).
func (*ServerError) Error ¶
func (e *ServerError) Error() string
func (*ServerError) Temporary ¶
func (e *ServerError) Temporary() bool
Temporary reports whether a new Acquire may succeed. The classification is the protocol's own: for a known code it is the canonical error's answer, and a code this client does not know yet is read by the same range rule — below 0x10 a server condition worth retrying, from 0x10 up a client error the same bytes would hit again.
func (*ServerError) Unwrap ¶
func (e *ServerError) Unwrap() error
Unwrap ties the received code back to its canonical protocol error, so errors.Is(err, protocol.ErrShuttingDown) works. A code this version of the protocol does not know unwraps to nothing.