1// Copyright 2016 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package concurrency
16
17import (
18	"sync"
19
20	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
21	v3 "github.com/coreos/etcd/clientv3"
22)
23
24// Mutex implements the sync Locker interface with etcd
25type Mutex struct {
26	client *v3.Client
27
28	pfx   string
29	myKey string
30	myRev int64
31}
32
33func NewMutex(client *v3.Client, pfx string) *Mutex {
34	return &Mutex{client, pfx, "", -1}
35}
36
37// Lock locks the mutex with a cancellable context. If the context is cancelled
38// while trying to acquire the lock, the mutex tries to clean its stale lock entry.
39func (m *Mutex) Lock(ctx context.Context) error {
40	s, err := NewSession(m.client)
41	if err != nil {
42		return err
43	}
44	// put self in lock waiters via myKey; oldest waiter holds lock
45	m.myKey, m.myRev, err = NewUniqueKey(ctx, m.client, m.pfx, v3.WithLease(s.Lease()))
46	// wait for deletion revisions prior to myKey
47	err = waitDeletes(ctx, m.client, m.pfx, v3.WithPrefix(), v3.WithRev(m.myRev-1))
48	// release lock key if cancelled
49	select {
50	case <-ctx.Done():
51		m.Unlock()
52	default:
53	}
54	return err
55}
56
57func (m *Mutex) Unlock() error {
58	if _, err := m.client.Delete(m.client.Ctx(), m.myKey); err != nil {
59		return err
60	}
61	m.myKey = "\x00"
62	m.myRev = -1
63	return nil
64}
65
66func (m *Mutex) IsOwner() v3.Cmp {
67	return v3.Compare(v3.CreatedRevision(m.myKey), "=", m.myRev)
68}
69
70func (m *Mutex) Key() string { return m.myKey }
71
72type lockerMutex struct{ *Mutex }
73
74func (lm *lockerMutex) Lock() {
75	if err := lm.Mutex.Lock(lm.client.Ctx()); err != nil {
76		panic(err)
77	}
78}
79func (lm *lockerMutex) Unlock() {
80	if err := lm.Mutex.Unlock(); err != nil {
81		panic(err)
82	}
83}
84
85// NewLocker creates a sync.Locker backed by an etcd mutex.
86func NewLocker(client *v3.Client, pfx string) sync.Locker {
87	return &lockerMutex{NewMutex(client, pfx)}
88}
89