func (rwm *RWMutex) Lock() error { rk, err := NewUniqueEphemeralKey(rwm.client, rwm.key+"/write") if err != nil { return err } rwm.myKey = rk for { // find any key of lower rev number blocks the write lock opts := append(v3.WithLastRev(), v3.WithRev(rk.Revision()-1)) resp, err := rwm.client.Get(rwm.ctx, rwm.key, opts...) if err != nil { return err } if len(resp.Kvs) == 0 { // no matching for revision before myKey; acquired break } if err := rwm.waitOnLowest(); err != nil { return err } // get the new lowest, etc until this is the only one left } return nil }
func (rwm *RWMutex) waitOnLowest() error { // must block; get key before ek for waiting opts := append(v3.WithLastRev(), v3.WithRev(rwm.myKey.Revision()-1)) lastKey, err := rwm.client.Get(rwm.ctx, rwm.key, opts...) if err != nil { return err } // wait for release on prior key _, err = WaitEvents( rwm.client, string(lastKey.Kvs[0].Key), rwm.myKey.Revision(), []storagepb.Event_EventType{storagepb.DELETE}) return err }
// waitOnLowest will wait on the last key with a revision < rwm.myKey.Revision with a // given prefix. If there are no keys left to wait on, return true. func (rwm *RWMutex) waitOnLastRev(pfx string) (bool, error) { client := rwm.s.Client() // get key that's blocking myKey opts := append(v3.WithLastRev(), v3.WithMaxModRev(rwm.myKey.Revision()-1)) lastKey, err := client.Get(rwm.ctx, pfx, opts...) if err != nil { return false, err } if len(lastKey.Kvs) == 0 { return true, nil } // wait for release on blocking key _, err = WaitEvents( client, string(lastKey.Kvs[0].Key), rwm.myKey.Revision(), []mvccpb.Event_EventType{mvccpb.DELETE}) return false, err }
// 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, err := NewSession(m.client) if err != nil { return err } // put self in lock waiters via myKey; oldest waiter holds lock m.myKey, m.myRev, err = NewUniqueKey(ctx, m.kv, m.pfx, v3.WithLease(s.Lease())) // wait for lock to become available for err == nil { // find oldest element in waiters via revision of insertion var resp *v3.GetResponse resp, err = m.kv.Get(ctx, m.pfx, v3.WithFirstRev()...) if err != nil { break } if m.myRev == resp.Kvs[0].CreateRevision { // myKey is oldest in waiters; myKey holds the lock now return nil } // otherwise myKey isn't lowest, so there must be a pfx prior to myKey opts := append(v3.WithLastRev(), v3.WithRev(m.myRev-1)) resp, err = m.kv.Get(ctx, m.pfx, opts...) if err != nil { break } lastKey := string(resp.Kvs[0].Key) // wait for release on prior pfx err = waitUpdate(ctx, m.client, lastKey, v3.WithRev(m.myRev)) // try again in case lastKey left the wait list before acquiring the lock; // myKey can only hold the lock if it's the oldest in the list } // release lock key if cancelled select { case <-ctx.Done(): m.Unlock() default: } return err }