// Lock locks the mutex with a cancellable context. If the context is cancelled // while trying to acquire the lock, the mutex tries to clean its stale lock entry. func (m *Mutex) Lock(ctx context.Context) error { s, serr := NewSession(m.client) if serr != nil { return serr } m.myKey = fmt.Sprintf("%s/%x", m.pfx, s.Lease()) cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0) // put self in lock waiters via myKey; oldest waiter holds lock put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease())) // reuse key in case this session already holds the lock get := v3.OpGet(m.myKey) resp, err := m.client.Txn(ctx).If(cmp).Then(put).Else(get).Commit() if err != nil { return err } m.myRev = resp.Header.Revision if !resp.Succeeded { m.myRev = resp.Responses[0].GetResponseRange().Kvs[0].CreateRevision } // wait for deletion revisions prior to myKey err = waitDeletes(ctx, m.client, m.pfx, v3.WithPrefix(), v3.WithRev(m.myRev-1)) // release lock key if cancelled select { case <-ctx.Done(): m.Unlock() default: } return err }
func parseCompare(line string) (*clientv3.Cmp, error) { var ( key string op string val string ) lparenSplit := strings.SplitN(line, "(", 2) if len(lparenSplit) != 2 { return nil, fmt.Errorf("malformed comparison: %s", line) } target := lparenSplit[0] n, serr := fmt.Sscanf(lparenSplit[1], "%q) %s %q", &key, &op, &val) if n != 3 { return nil, fmt.Errorf("malformed comparison: %s; got %s(%q) %s %q", line, target, key, op, val) } if serr != nil { return nil, fmt.Errorf("malformed comparison: %s (%v)", line, serr) } var ( v int64 err error cmp clientv3.Cmp ) switch target { case "ver", "version": if v, err = strconv.ParseInt(val, 10, 64); err == nil { cmp = clientv3.Compare(clientv3.Version(key), op, v) } case "c", "create": if v, err = strconv.ParseInt(val, 10, 64); err == nil { cmp = clientv3.Compare(clientv3.CreateRevision(key), op, v) } case "m", "mod": if v, err = strconv.ParseInt(val, 10, 64); err == nil { cmp = clientv3.Compare(clientv3.ModRevision(key), op, v) } case "val", "value": cmp = clientv3.Compare(clientv3.Value(key), op, val) default: return nil, fmt.Errorf("malformed comparison: %s (unknown target %s)", line, target) } if err != nil { return nil, fmt.Errorf("invalid txn compare request: %s", line) } return &cmp, nil }
// Lock locks resources on sync // This call blocks until you can get lock func (s *Sync) Lock(path string, block bool) error { for { if s.HasLock(path) { return nil } var err error lease, err := s.etcdClient.Grant(s.withTimeout(), masterTTL) var resp *etcd.TxnResponse if err == nil { cmp := etcd.Compare(etcd.CreateRevision(path), "=", 0) put := etcd.OpPut(path, s.processID, etcd.WithLease(lease.ID)) resp, err = s.etcdClient.Txn(s.withTimeout()).If(cmp).Then(put).Commit() } if err != nil || !resp.Succeeded { msg := fmt.Sprintf("failed to lock path %s", path) if err != nil { msg = fmt.Sprintf("failed to lock path %s: %s", path, err) } log.Notice(msg) s.locks.Remove(path) if !block { return errors.New(msg) } time.Sleep(masterTTL * time.Second) continue } log.Info("Locked %s", path) s.locks.Set(path, lease.ID) //Refresh master token go func() { defer func() { log.Notice("releasing keepalive lock for %s", path) s.locks.Remove(path) }() for s.HasLock(path) { ch, err := s.etcdClient.KeepAlive(s.withTimeout(), lease.ID) if err != nil { log.Notice("failed to keepalive lock for %s %s", path, err) return } for range ch { } } }() return nil } }
// Proclaim lets the leader announce a new value without another election. func (e *Election) Proclaim(ctx context.Context, val string) error { if e.leaderSession == nil { return ErrElectionNotLeader } cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev) txn := e.client.Txn(ctx).If(cmp) txn = txn.Then(v3.OpPut(e.leaderKey, val, v3.WithLease(e.leaderSession.Lease()))) tresp, terr := txn.Commit() if terr != nil { return terr } if !tresp.Succeeded { e.leaderKey = "" return ErrElectionNotLeader } return nil }
// Campaign puts a value as eligible for the election. It blocks until // it is elected, an error occurs, or the context is cancelled. func (e *Election) Campaign(ctx context.Context, val string) error { s, serr := NewSession(e.client) if serr != nil { return serr } k := fmt.Sprintf("%s/%x", e.keyPrefix, s.Lease()) txn := e.client.Txn(ctx).If(v3.Compare(v3.CreateRevision(k), "=", 0)) txn = txn.Then(v3.OpPut(k, val, v3.WithLease(s.Lease()))) txn = txn.Else(v3.OpGet(k)) resp, err := txn.Commit() if err != nil { return err } e.leaderKey, e.leaderRev, e.leaderSession = k, resp.Header.Revision, s if !resp.Succeeded { kv := resp.Responses[0].GetResponseRange().Kvs[0] e.leaderRev = kv.CreateRevision if string(kv.Value) != val { if err = e.Proclaim(ctx, val); err != nil { e.Resign(ctx) return err } } } err = waitDeletes(ctx, e.client, e.keyPrefix, v3.WithPrefix(), v3.WithRev(e.leaderRev-1)) if err != nil { // clean up in case of context cancel select { case <-ctx.Done(): e.Resign(e.client.Ctx()) default: e.leaderSession = nil } return err } return nil }
func (m *Mutex) IsOwner() v3.Cmp { return v3.Compare(v3.CreateRevision(m.myKey), "=", m.myRev) }