locker

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Mar 8, 2018 License: MIT Imports: 6 Imported by: 0

README

locker

A distributed lock service client for etcd.

Forked from github.com/jagregory/locker

Features
  • Lock/Unlock mechanism
  • Migrate to github.com/coreos/etcd/clientv3
  • TTL

Godoc Build Status

What? Why?

A distributed lock service is somewhat self-explanatory. Locking (mutexes) as a service that's distributed across a cluster.

What a lock service gives you is a key-value store with locking semantics, and a mechanism for watching state changes. The distributed part of a distributed lock service is thanks to the etcd underpinnings, and it simply means your locks are persisted across a cluster.

Usage

Creating a lock and release it with the Unlock
import "github.com/PumpkinSeed/locker"

// Add etcd cluster nodes
var machines = []string{"http://127.0.0.1:2379", "http://127.0.0.1:2380", "http://127.0.0.1:2381"}

// Add timeout for etcd DialTimeout option
var timeout = int64(5)

// Add ttl (time-to-live)
var ttl = int64(5)

// Create the locker client.
client, err := locker.New(machines, timeout, ttl, context.Background())

var key = "1231231-123123-123"
quit := make(chan bool)
report := client.Lock(key, locker.DefaultValue, quit)
// report has a Msg and an Err field, Msg will contains 'success' or 'fail' operations.
Report
  • Report returned by the Lock, it has a Msg and an Err field
  • The Msg Success if the key not locked yet, and Fail if the key locked
  • The Err will return an error occured among the lock mechanism
Releasing the lock

The third argument to Lock is a quit channel. Push anything into this channel to kill the locking, which will delete the locked key from the etcd.

client.Unlock(key, quit)
Watching a lock (deprecated)

An interesting aspect of lock services is the ability to watch a lock that isn't owned by you. A service can alter its behaviour depending on the value of a lock. You can use the Watch function to watch the value of a lock.

valueChanges := make(chan string)
go client.Watch("key", valueChanges, nil)

select {
case change := <-valueChanges
	fmt.Printf("Lock value changed: %s", change)
}

Quitting works the same way as Lock.

Contribution

  • Docker environment provided for testing etcd in the docker directory.
  • utils.go has a debug option, currently in false if it's true than it will log debug messages to the os.Stdout

Documentation

Overview

Package locker is a distributed lock service built on top of etcd. https://github.com/coreos/etcd

Locker gives you a way of creating and maintaining locks across a network, useful for synchronising access to resources when multiple machines are involved.

A simple example is a name server. A service would lock its hostname record and set the value to its IP address:

client.Lock("www.example.com", "10.1.1.1", nil, nil)

Other clients wanting to resolve the address could call

client.Get("www.example.com")

and would recieve the IP address: 10.1.1.1.

This gets a bit more interesting when clients start watching the records and are notified of changes when they occur.

valueChanges := make(chan string)
go client.Watch("www.example.com", valueChanges, nil)

select {
case newIp := <-valueChanges:
    redirectRequestsToNewIp(newIp)
}

Index

Constants

View Source
const (
	Fail    string = "fail"
	Success        = "success"
)
View Source
const DefaultValue = "ok"

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// Store is what locker uses to persist locks.
	Store Store
	// contains filtered or unexported fields
}

Client is the main locker type. Use it to manage your locks. A locker Client has a Store which it uses to persist the locks.

func New

func New(machines []string, timeout int64, ttl int64, ctx context.Context) (Client, error)

New creates a default locker client using Etcd as a store. It requires you provide an etcd.Client, this is so locker doesn't make any dumb assumptions about how your Etcd cluster is configured.

client := locker.New(etcdclient)

func (Client) Get

func (c Client) Get(name string) (string, error)

Get returns the value of a lock. LockNotFound will be returned if a lock with the name isn't held.

func (Client) Inspect

func (c Client) Inspect(name string) Report

func (Client) Lock

func (c Client) Lock(name, value string, quit <-chan bool) Report

Lock will create a lock for a key and set its value. If the owned channel is provided a bool will be pushed whenever our ownership of the lock changes. Pushing true into the quit channel will stop the locker from refreshing the lock and let it expire if we own it.

owned := make(chan bool)

go client.Lock("my-service", "http://10.0.0.1:9292", owned, nil)

for {
  select {
  case v := <-owned:
    fmt.Printf("Lock ownership changed: %t\n", v)
  }
}

Lock is a blocking call, so it's recommended to run it in a goroutine.

func (Client) Unlock

func (c Client) Unlock(name string, quit chan<- bool) error

func (Client) Watch

func (c Client) Watch(name string, valueChanges chan<- string, quit <-chan bool) error

Watch will create a watch on a lock and push value changes into the valueChanges channel. An empty string indicates the lack of a lock. Pushing true into quit will end the watch.

valueChanges := make(chan string)

go client.Watch("my-service", valueChanges, nil)

for {
  select {
  case v := <-valueChanges:
    fmt.Printf("Lock value changed: %s\n", v)
  }
}

Watch is a blocking call, so it's recommended to run it in a goroutine.

type EtcdStore

type EtcdStore struct {
	EtcdClientv3 *clientv3.Client

	// Version of the etcd
	Version string

	// TTL is the time-to-live for the lock. Default: 5s.
	TTL int64

	Log log.Logger
}

EtcdStore is a backing store for Locker which uses Etcd for storage.

func (EtcdStore) AcquireOrFreshenLock

func (s EtcdStore) AcquireOrFreshenLock(ctx context.Context, name, value string) error

AcquireOrFreshenLock will aquires a named lock if it isn't already held, or updates its TTL if it is.

func (EtcdStore) Delete

func (s EtcdStore) Delete(ctx context.Context, name string) error

func (EtcdStore) Get

func (s EtcdStore) Get(ctx context.Context, name string) (string, error)

Get returns the value of a lock. LockNotFound will be returned if a lock with the name isn't held.

type LockDenied

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

LockDenied is returned when a lock is attempted on a key which has been acquired by another client.

func (LockDenied) Error

func (e LockDenied) Error() string

type LockNotFound

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

LockNotFound is returned when a Get is made against a key which isn't locked.

func (LockNotFound) Error

func (e LockNotFound) Error() string

type Report

type Report struct {
	Err error
	Msg string
}

type Store

type Store interface {
	// Get returns the value of a lock. LockNotFound will be returned if a
	// lock with the name isn't held.
	Get(ctx context.Context, name string) (string, error)

	// AcquireOrFreshenLock will aquires a named lock if it isn't already
	// held, or updates its TTL if it is.
	AcquireOrFreshenLock(ctx context.Context, name, value string) error

	Delete(ctx context.Context, name string) error
}

Store is a persistance mechaism for locker to store locks. Needs to be able to support querying and an atomic compare-and-swap. Currently, the only implementation of a Store is EtcdStore.

Jump to

Keyboard shortcuts

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