dynamolock: cirello.io/dynamolock Index | Files

package dynamolock

import "cirello.io/dynamolock"

Package dynamolock provides a simple utility for using DynamoDB's consistent read/write feature to use it for managing distributed locks.

In order to use this package, the client must create a table in DynamoDB, although the client provides a convenience method for creating that table (CreateTable).

Basic usage:

import (
	"log"

	"cirello.io/dynamolock"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/dynamodb"
)

// ---

svc := dynamodb.New(session.Must(session.NewSession(&aws.Config{
	Region: aws.String("us-west-2"),
})))
c, err := dynamolock.New(svc,
	"locks",
	dynamolock.WithLeaseDuration(3*time.Second),
	dynamolock.WithHeartbeatPeriod(1*time.Second),
)
if err != nil {
	log.Fatal(err)
}
defer c.Close()

log.Println("ensuring table exists")
c.CreateTable("locks",
	dynamolock.WithProvisionedThroughput(&dynamodb.ProvisionedThroughput{
		ReadCapacityUnits:  aws.Int64(5),
		WriteCapacityUnits: aws.Int64(5),
	}),
	dynamolock.WithCustomPartitionKeyName("key"),
)

data := []byte("some content a")
lockedItem, err := c.AcquireLock("spock",
	dynamolock.WithData(data),
	dynamolock.ReplaceData(),
)
if err != nil {
	log.Fatal(err)
}

log.Println("lock content:", string(lockedItem.Data()))
if got := string(lockedItem.Data()); string(data) != got {
	log.Println("losing information inside lock storage, wanted:", string(data), " got:", got)
}

log.Println("cleaning lock")
success, err := c.ReleaseLock(lockedItem)
if !success {
	log.Fatal("lost lock before release")
}
if err != nil {
	log.Fatal("error releasing lock:", err)
}
log.Println("done")

This package is covered by this SLA: https://github.com/cirello-io/public/blob/master/SLA.md

Index

Package Files

client.go client_heartbeat.go client_session_monitor.go doc.go errors.go lock.go structs.go

Variables

var (
    ErrSessionMonitorNotSet  = errors.New("session monitor is not set")
    ErrLockAlreadyReleased   = errors.New("lock is already released")
    ErrCannotReleaseNullLock = errors.New("cannot release null lock item")
    ErrOwnerMismatched       = errors.New("lock owner mismatched")
)

Errors related to session manager life-cycle.

var ErrClientClosed = errors.New("client already closed")

ErrClientClosed reports the client cannot be used because it is already closed.

type AcquireLockOption Uses

type AcquireLockOption func(*acquireLockOptions)

AcquireLockOption allows to change how the lock is actually held by the client.

func FailIfLocked Uses

func FailIfLocked() AcquireLockOption

FailIfLocked will not retry to acquire the lock, instead returning.

func ReplaceData Uses

func ReplaceData() AcquireLockOption

ReplaceData will force the new content to be stored in the key.

func WithAdditionalAttributes Uses

func WithAdditionalAttributes(attr map[string]*dynamodb.AttributeValue) AcquireLockOption

WithAdditionalAttributes stores some additional attributes with each lock. This can be used to add any arbitrary parameters to each lock row.

func WithAdditionalTimeToWaitForLock Uses

func WithAdditionalTimeToWaitForLock(d time.Duration) AcquireLockOption

WithAdditionalTimeToWaitForLock defines how long to wait in addition to the lease duration (if set to 10 minutes, this will try to acquire a lock for at least 10 minutes before giving up and returning an error).

func WithData Uses

func WithData(b []byte) AcquireLockOption

WithData stores the content into the lock itself.

func WithDeleteLockOnRelease Uses

func WithDeleteLockOnRelease() AcquireLockOption

WithDeleteLockOnRelease defines whether or not the lock should be deleted when Close() is called on the resulting LockItem will force the new content to be stored in the key.

func WithRefreshPeriod Uses

func WithRefreshPeriod(d time.Duration) AcquireLockOption

WithRefreshPeriod defines how long to wait before trying to get the lock again (if set to 10 seconds, for example, it would attempt to do so every 10 seconds).

func WithSessionMonitor Uses

func WithSessionMonitor(safeTime time.Duration, callback func()) AcquireLockOption

WithSessionMonitor registers a callback that is triggered if the lock is about to expire.

The purpose of this construct is to provide two abilities: provide the ability to determine if the lock is about to expire, and run a user-provided callback when the lock is about to expire. The advantage this provides is notification that your lock is about to expire before it is actually expired, and in case of leader election will help in preventing that there are no two leaders present simultaneously.

If due to any reason heartbeating is unsuccessful for a configurable period of time, your lock enters into a phase known as "danger zone." It is during this "danger zone" that the callback will be run.

Bear in mind that the callback may be null. In this case, no callback will be run upon the lock entering the "danger zone"; yet, one can still make use of the Lock.IsAlmostExpired() call. Furthermore, non-null callbacks can only ever be executed once in a lock's lifetime. Independent of whether or not a callback is run, the client will attempt to heartbeat the lock until the lock is released or obtained by someone else.

Consider an example which uses this mechanism for leader election. One way to make use of this SessionMonitor is to register a callback that kills the instance in case the leader's lock enters the danger zone:

type Client Uses

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

Client is a dynamoDB based distributed lock client.

func New Uses

func New(dynamoDB dynamodbiface.DynamoDBAPI, tableName string, opts ...ClientOption) (*Client, error)

New creates a new dynamoDB based distributed lock client.

func (*Client) AcquireLock Uses

func (c *Client) AcquireLock(key string, opts ...AcquireLockOption) (*Lock, error)

AcquireLock holds the defined lock.

func (*Client) Close Uses

func (c *Client) Close() error

Close releases all of the locks.

func (*Client) CreateTable Uses

func (c *Client) CreateTable(tableName string, opts ...CreateTableOption) (*dynamodb.CreateTableOutput, error)

CreateTable prepares a DynamoDB table with the right schema for it to be used by this locking library. The table should be set up in advance, because it takes a few minutes for DynamoDB to provision a new instance. Also, if the table already exists, it will return an error.

func (*Client) Get Uses

func (c *Client) Get(key string) (*Lock, error)

Get finds out who owns the given lock, but does not acquire the lock. It returns the metadata currently associated with the given lock. If the client currently has the lock, it will return the lock, and operations such as releaseLock will work. However, if the client does not have the lock, then operations like releaseLock will not work (after calling Get, the caller should check lockItem.isExpired() to figure out if it currently has the lock.)

func (*Client) ReleaseLock Uses

func (c *Client) ReleaseLock(lockItem *Lock, opts ...ReleaseLockOption) (bool, error)

ReleaseLock releases the given lock if the current user still has it, returning true if the lock was successfully released, and false if someone else already stole the lock or a problem happened. Deletes the lock item if it is released and deleteLockItemOnClose is set.

func (*Client) SendHeartbeat Uses

func (c *Client) SendHeartbeat(lockItem *Lock, opts ...SendHeartbeatOption) error

SendHeartbeat indicatee that the given lock is still being worked on. If using WithHeartbeatPeriod > 0 when setting up this object, then this method is unnecessary, because the background thread will be periodically calling it and sending heartbeats. However, if WithHeartbeatPeriod = 0, then this method must be called to instruct DynamoDB that the lock should not be expired.

type ClientOption Uses

type ClientOption func(*Client)

ClientOption reconfigure the lock client creation.

func DisableHeartbeat Uses

func DisableHeartbeat() ClientOption

DisableHeartbeat disables automatic hearbeats. Use SendHeartbeat to freshen up the lock.

func WithHeartbeatPeriod Uses

func WithHeartbeatPeriod(d time.Duration) ClientOption

WithHeartbeatPeriod defines the frequency of the heartbeats. Set to zero to disable it. Heartbeats should have no more than half of the duration of the lease.

func WithLeaseDuration Uses

func WithLeaseDuration(d time.Duration) ClientOption

WithLeaseDuration defines how long should the lease be held.

func WithLogger Uses

func WithLogger(l Logger) ClientOption

WithLogger injects a logger into the client, so its internals can be recorded.

func WithOwnerName Uses

func WithOwnerName(s string) ClientOption

WithOwnerName changes the owner linked to the client, and by consequence to locks.

func WithPartitionKeyName Uses

func WithPartitionKeyName(s string) ClientOption

WithPartitionKeyName defines the key name used for asserting keys uniqueness.

type CreateTableOption Uses

type CreateTableOption func(*createDynamoDBTableOptions)

CreateTableOption is an options type for the CreateTable method in the lock client. This allows the user to create a DynamoDB table that is lock client-compatible and specify optional parameters such as the desired throughput and whether or not to use a sort key.

func WithCustomPartitionKeyName Uses

func WithCustomPartitionKeyName(s string) CreateTableOption

WithCustomPartitionKeyName changes the partition key name of the table. If not specified, the default "key" will be used.

func WithProvisionedThroughput Uses

func WithProvisionedThroughput(provisionedThroughput *dynamodb.ProvisionedThroughput) CreateTableOption

WithProvisionedThroughput changes the billing mode of DynamoDB and tells DynamoDB to operate in a provisioned throughput mode instead of pay-per-request

type Lock Uses

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

Lock item properly speaking.

func (*Lock) AdditionalAttributes Uses

func (l *Lock) AdditionalAttributes() map[string]*dynamodb.AttributeValue

AdditionalAttributes returns the lock's additional data stored during acquisition.

func (*Lock) Close Uses

func (l *Lock) Close() error

Close releases the lock.

func (*Lock) Data Uses

func (l *Lock) Data() []byte

Data returns the content of the lock, if any is available.

func (*Lock) IsAlmostExpired Uses

func (l *Lock) IsAlmostExpired() (bool, error)

IsAlmostExpired returns whether or not the lock is entering the "danger zone" time period.

It returns if the lock has been released or the lock's lease has entered the "danger zone". It returns false if the lock has not been released and the lock has not yet entered the "danger zone"

func (*Lock) IsExpired Uses

func (l *Lock) IsExpired() bool

IsExpired returns if the lock is expired, released, or neither.

func (*Lock) OwnerName Uses

func (l *Lock) OwnerName() string

OwnerName returns the lock's owner.

type LockNotGrantedError Uses

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

LockNotGrantedError indicates that an AcquireLock call has failed to establish a lock because of its current lifecycle state.

func (*LockNotGrantedError) Error Uses

func (e *LockNotGrantedError) Error() string

func (*LockNotGrantedError) Unwrap Uses

func (e *LockNotGrantedError) Unwrap() error

Unwrap reveals the underlying cause why the lock was not granted.

type Logger Uses

type Logger interface {
    Println(v ...interface{})
}

Logger defines the minimum desired logger interface for the lock client.

type ReleaseLockOption Uses

type ReleaseLockOption func(*releaseLockOptions)

ReleaseLockOption provides options for releasing a lock when calling the releaseLock() method. This class contains the options that may be configured during the act of releasing a lock.

func WithDataAfterRelease Uses

func WithDataAfterRelease(data []byte) ReleaseLockOption

WithDataAfterRelease is the new data to persist to the lock (only used if deleteLock=false.) If the data is null, then the lock client will keep the data as-is and not change it.

func WithDeleteLock Uses

func WithDeleteLock(deleteLock bool) ReleaseLockOption

WithDeleteLock defines whether or not to delete the lock when releasing it. If set to false, the lock row will continue to be in DynamoDB, but it will be marked as released.

type SendHeartbeatOption Uses

type SendHeartbeatOption func(*sendHeartbeatOptions)

SendHeartbeatOption allows to proceed with Lock content changes in the heartbeat cycle.

func DeleteData Uses

func DeleteData() SendHeartbeatOption

DeleteData removes the Lock data on heartbeat.

func ReplaceHeartbeatData Uses

func ReplaceHeartbeatData(data []byte) SendHeartbeatOption

ReplaceHeartbeatData overrides the content of the Lock in the heartbeat cycle.

type TimeoutError Uses

type TimeoutError struct {
    Age time.Duration
}

TimeoutError indicates that the dynamolock gave up acquiring the lock. It holds the length of the attempt that resulted in the error.

func (*TimeoutError) Error Uses

func (e *TimeoutError) Error() string

Package dynamolock imports 13 packages (graph) and is imported by 1 packages. Updated 2019-10-18. Refresh now. Tools for package owners.