github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/lock/etcdlock/mutex.go (about) 1 package etcdlock 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 "time" 8 9 "github.com/projecteru2/core/types" 10 11 clientv3 "go.etcd.io/etcd/client/v3" 12 "go.etcd.io/etcd/client/v3/concurrency" 13 "golang.org/x/net/context" 14 ) 15 16 // Mutex is etcdv3 lock 17 type Mutex struct { 18 timeout time.Duration 19 mutex *concurrency.Mutex 20 session *concurrency.Session 21 locked bool 22 lockedMux sync.Mutex 23 } 24 25 type lockContext struct { 26 err error 27 mutex sync.Mutex 28 context.Context 29 } 30 31 func (c *lockContext) setError(err error) { 32 c.mutex.Lock() 33 defer c.mutex.Unlock() 34 c.err = err 35 } 36 37 func (c *lockContext) Err() error { 38 c.mutex.Lock() 39 defer c.mutex.Unlock() 40 if c.err != nil { 41 return c.err 42 } 43 return c.Context.Err() 44 } 45 46 // New new a lock 47 func New(cli *clientv3.Client, key string, ttl time.Duration) (*Mutex, error) { 48 if key == "" { 49 return nil, types.ErrLockKeyInvaild 50 } 51 52 if !strings.HasPrefix(key, "/") { 53 key = fmt.Sprintf("/%s", key) 54 } 55 56 session, err := concurrency.NewSession(cli, concurrency.WithTTL(int(ttl.Seconds()))) 57 if err != nil { 58 return nil, err 59 } 60 61 mutex := &Mutex{mutex: concurrency.NewMutex(session, key), session: session} 62 mutex.timeout = ttl 63 return mutex, nil 64 } 65 66 // Lock get locked 67 func (m *Mutex) Lock(ctx context.Context) (context.Context, error) { 68 lockCtx, cancel := context.WithTimeout(ctx, m.timeout) 69 defer cancel() 70 71 if err := m.mutex.Lock(lockCtx); err != nil { 72 return nil, err 73 } 74 75 ctx, cancel = context.WithCancel(ctx) 76 rCtx := &lockContext{Context: ctx} 77 78 m.lockedMux.Lock() 79 m.locked = true 80 m.lockedMux.Unlock() 81 82 go func() { 83 defer cancel() 84 85 select { 86 case <-m.session.Done(): 87 m.lockedMux.Lock() 88 if m.locked { 89 // passive lock release, happened when etcdserver down or so 90 rCtx.setError(types.ErrLockSessionDone) 91 m.lockedMux.Unlock() 92 return 93 } 94 // positive lock release, happened when we call lock.Unlock() 95 m.lockedMux.Unlock() 96 <-ctx.Done() 97 return 98 99 case <-ctx.Done(): 100 // context canceled or timeouted from upstream 101 return 102 } 103 }() 104 105 return rCtx, nil 106 } 107 108 // TryLock tries to lock 109 // returns error if the lock is already acquired by someone else 110 func (m *Mutex) TryLock(ctx context.Context) (context.Context, error) { 111 lockCtx, cancel := context.WithTimeout(ctx, m.timeout) 112 defer cancel() 113 114 if err := m.mutex.TryLock(lockCtx); err != nil { 115 return nil, err 116 } 117 118 ctx, cancel = context.WithCancel(ctx) 119 rCtx := &lockContext{Context: ctx} 120 121 m.lockedMux.Lock() 122 m.locked = true 123 m.lockedMux.Unlock() 124 125 // how to make this DIY?@zc 126 go func() { 127 defer cancel() 128 129 select { 130 case <-m.session.Done(): 131 // session.Done() has multi semantics, see comments in Lock() func 132 m.lockedMux.Lock() 133 if m.locked { 134 rCtx.setError(types.ErrLockSessionDone) 135 m.lockedMux.Unlock() 136 return 137 } 138 m.lockedMux.Unlock() 139 <-ctx.Done() 140 return 141 142 case <-ctx.Done(): 143 return 144 } 145 }() 146 147 return rCtx, nil 148 } 149 150 // Unlock unlock 151 func (m *Mutex) Unlock(ctx context.Context) error { 152 defer m.session.Close() 153 // release resource 154 155 lockCtx, cancel := context.WithTimeout(ctx, m.timeout) 156 defer cancel() 157 return m.unlock(lockCtx) 158 } 159 160 func (m *Mutex) unlock(ctx context.Context) error { 161 m.lockedMux.Lock() 162 m.locked = false 163 m.lockedMux.Unlock() 164 165 _, err := m.session.Client().Txn(ctx).If(m.mutex.IsOwner()). 166 Then(clientv3.OpDelete(m.mutex.Key())).Commit() 167 // no way to clear it... 168 // m.myKey = "\x00" 169 // m.myRev = -1 170 return err 171 }