@@ -2,8 +2,10 @@ package lock
22
33import (
44 "errors"
5+ "strings"
56 "time"
67
8+ "go.uber.org/zap"
79 "golang.org/x/net/context"
810
911 v3 "go.etcd.io/etcd/client/v3"
@@ -21,28 +23,29 @@ type RuleLock interface {
2123type GetSession func (context.Context ) (* v3c.Session , error )
2224
2325// NewV3Locker creates a locker backed by etcd V3.
24- func NewV3Locker (cl * v3.Client , lockTimeout int , useTryLock bool ) RuleLocker {
26+ func NewV3Locker (cl * v3.Client , lockTimeout int , useTryLock bool , logger * zap. Logger ) RuleLocker {
2527 // The TTL is for the lease associated with the session, in seconds. While the session is still open,
2628 // the lease's TTL will keep getting renewed to keep it from expiring, so all this really does is
2729 // set the amount of time it takes for the lease to expire if the lease stops being renewed due
2830 // to the application shutting down before a session could be properly closed.
2931 newSession := func (_ context.Context ) (* v3c.Session , error ) {
3032 return v3c .NewSession (cl , v3c .WithTTL (30 ))
3133 }
32- return NewSessionLocker (newSession , lockTimeout , true , useTryLock )
34+ return NewSessionLocker (newSession , lockTimeout , true , useTryLock , logger )
3335}
3436
3537// NewSessionLocker creates a new locker with the provided session constructor. Note that
3638// if closeSession is false, it means that the session provided by getSession will not be
3739// closed but instead be reused. In that case the locker must be protected by another locker
3840// (for instance an in-memory locker) because locks within the same session are reentrant
3941// which means that two goroutines can obtain the same lock.
40- func NewSessionLocker (getSession GetSession , lockTimeout int , closeSession , useTryLock bool ) RuleLocker {
42+ func NewSessionLocker (getSession GetSession , lockTimeout int , closeSession , useTryLock bool , logger * zap. Logger ) RuleLocker {
4143 return & v3Locker {
4244 lockTimeout : lockTimeout ,
4345 newSession : getSession ,
4446 closeSession : closeSession ,
4547 useTryLock : useTryLock ,
48+ logger : logger ,
4649 }
4750}
4851
@@ -51,12 +54,13 @@ type v3Locker struct {
5154 newSession GetSession
5255 closeSession bool
5356 useTryLock bool
57+ logger * zap.Logger
5458}
5559
5660func (v3l * v3Locker ) Lock (key string , options ... Option ) (RuleLock , error ) {
57- return v3l .lockWithTimeout (key , v3l .lockTimeout )
61+ return v3l .lockWithTimeout (key , v3l .lockTimeout , v3l . logger )
5862}
59- func (v3l * v3Locker ) lockWithTimeout (key string , timeout int ) (RuleLock , error ) {
63+ func (v3l * v3Locker ) lockWithTimeout (key string , timeout int , logger * zap. Logger ) (RuleLock , error ) {
6064 ctx , cancel := context .WithTimeout (context .Background (), time .Duration (timeout )* time .Second )
6165 defer cancel ()
6266 s , err := v3l .newSession (ctx )
@@ -73,7 +77,8 @@ func (v3l *v3Locker) lockWithTimeout(key string, timeout int) (RuleLock, error)
7377 return nil , err
7478 }
7579 lock := & v3Lock {
76- mutex : m ,
80+ mutex : m ,
81+ logger : logger ,
7782 }
7883 if v3l .closeSession {
7984 lock .session = s
@@ -84,24 +89,45 @@ func (v3l *v3Locker) lockWithTimeout(key string, timeout int) (RuleLock, error)
8489type v3Lock struct {
8590 mutex * v3c.Mutex
8691 session * v3c.Session
92+ logger * zap.Logger
8793}
8894
8995// ErrNilMutex indicates that the lock has a nil mutex
9096var ErrNilMutex = errors .New ("mutex is nil" )
9197
98+ // Max number of unlock retries
99+ var unlockMaxRetries = 5
100+
101+ // This should be given every chance to complete, otherwise
102+ // a lock could prevent future interactions with a resource.
92103func (v3l * v3Lock ) Unlock (_ ... Option ) error {
93104 if v3l .mutex != nil {
94- // This should be given every chance to complete, otherwise
95- // a lock could prevent future interactions with a resource.
96- ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Minute )
97- defer cancel ()
98- err := v3l .mutex .Unlock (ctx )
105+ var err error
106+ for i := range unlockMaxRetries {
107+ ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Minute )
108+ err = v3l .mutex .Unlock (ctx )
109+ cancel ()
110+ if err != nil {
111+ v3l .logger .Warn ("Unlock error" , zap .String ("key" , v3l .mutex .Key ()), zap .Int ("attempt" , i + 1 ), zap .Error (err ))
112+ time .Sleep (time .Minute )
113+ } else {
114+ break
115+ }
116+ }
117+ if err != nil {
118+ err = errors .Join (errors .New ("Unlock:" ), err )
119+ }
120+
99121 // If the lock failed to be released, as least closing the session
100122 // will allow the lease it is associated with to expire.
101123 if v3l .session != nil {
102124 serr := v3l .session .Close ()
103- if err == nil {
104- err = serr
125+ // The Unlock will close the session in some cases
126+ if serr != nil && strings .Contains (serr .Error (), "lease not found" ) {
127+ serr = nil
128+ }
129+ if err == nil && serr != nil {
130+ err = errors .Join (errors .New ("Close:" ), serr )
105131 }
106132 }
107133 return err
0 commit comments