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  }