Documentation ¶
Overview ¶
Package lock provides a distributed locking algorithm backed by Google Cloud Storage. See https://www.joyfulbikeshedding.com/blog/2021-05-19-robust-distributed-locking-algorithm-based-on-google-cloud-storage.html for more details on the general design.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var (
ErrLockAbandoned = errors.New("lock abandoned")
)
Functions ¶
This section is empty.
Types ¶
type Lock ¶
type Lock struct {
// contains filtered or unexported fields
}
Lock provides a lock based off of a Google Cloud Storage object, without requiring communication between clients.
Example ¶
package main import ( "context" "errors" "fmt" "os" "time" "cloud.google.com/go/storage" "github.com/thg-ice/distributed-lock/mock_gcs" ) func main() { ctx, cancel := context.WithCancel(context.TODO()) defer cancel() bucketName := "testing" if v, ok := os.LookupEnv("GCS_BUCKET"); ok { bucketName = v } var client *storage.Client if _, ok := os.LookupEnv("NO_MOCK"); ok { var err error client, err = storage.NewClient(ctx) if err != nil { panic(err) } } else { server := mock_gcs.NewServer(bucketName) defer server.Close() var err error client, err = server.Client(ctx) if err != nil { panic(err) } } l := NewLock(client.Bucket(bucketName), "pod-uuid", "path/to/lock/file.lock", 5*time.Minute, func(context.Context) Logger { return stderrLogger{} }) // Acquire the lock if err := l.Lock(ctx, 30*time.Second); err != nil { // Action would need to be taken here if the lock couldn't be taken, such as re-queuing a reconcile request panic(err) } // Make sure the lock is released so others can take it defer func() { if err := l.Unlock(ctx); err != nil { panic(err) } }() // Regularly refresh the lock. If we're told that the lock had to be abandoned, then cancel the context so any // in-process work is stopped. go func() { t := time.Tick(2 * time.Second) for { select { case <-ctx.Done(): return case <-t: err := l.RefreshLock(ctx) if err != nil { if errors.Is(err, ErrLockAbandoned) { cancel() } fmt.Printf("Failed to refresh the lock: %s", err) } } } }() fmt.Println("hello") } var _ Logger = stderrLogger{} type stderrLogger struct { } func (n stderrLogger) Info(msg string, keysAndValues ...any) { if len(keysAndValues)%2 != 0 { panic("Incorrect number of parameters") } _, _ = fmt.Fprintf(os.Stderr, "INFO: %s\n", msg) } func (n stderrLogger) Error(err error, msg string, keysAndValues ...any) { if len(keysAndValues)%2 != 0 { panic("Incorrect number of parameters") } _, _ = fmt.Fprintf(os.Stderr, "ERROR: %s: %s\n", err, msg) }
Output: hello
func (*Lock) Lock ¶
Lock will attempt to acquire the configured lock until the context has timed out. The caller is expected to frequently call RefreshLock while holding the lock and Unlock when the lock is no longer needed.
func (*Lock) RefreshLock ¶
RefreshLock will update the information on the lock to ensure that the client still owns it. If ErrLockAbandoned is returned, then the client should assume the lock has been lost and stop immediately.