From 4b48876f0e9362016bab966bd3cca5aaeb9ba2fe Mon Sep 17 00:00:00 2001 From: "Jason E. Aten" Date: Sun, 28 Aug 2016 00:58:57 -0500 Subject: [PATCH] clientv3/concurrency: allow election on prefixes of keys. After winning an election or obtaining a lock, we auto-append a slash after the provided key prefix. This avoids the previous deadlock due to waiting on the wrong key. Fixes #6278 Conflicts: clientv3/concurrency/election.go clientv3/concurrency/mutex.go --- clientv3/concurrency/election.go | 3 +-- clientv3/concurrency/mutex.go | 4 ++-- integration/v3_election_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/clientv3/concurrency/election.go b/clientv3/concurrency/election.go index 89b52089a..b6800053a 100644 --- a/clientv3/concurrency/election.go +++ b/clientv3/concurrency/election.go @@ -40,7 +40,7 @@ type Election struct { // NewElection returns a new election on a given key prefix. func NewElection(client *v3.Client, pfx string) *Election { - return &Election{client: client, keyPrefix: pfx} + return &Election{client: client, keyPrefix: pfx + "/"} } // Campaign puts a value as eligible for the election. It blocks until @@ -59,7 +59,6 @@ func (e *Election) Campaign(ctx context.Context, val string) error { 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] diff --git a/clientv3/concurrency/mutex.go b/clientv3/concurrency/mutex.go index 803a8470a..e588b33bc 100644 --- a/clientv3/concurrency/mutex.go +++ b/clientv3/concurrency/mutex.go @@ -32,7 +32,7 @@ type Mutex struct { } func NewMutex(client *v3.Client, pfx string) *Mutex { - return &Mutex{client, pfx, "", -1} + return &Mutex{client, pfx + "/", "", -1} } // Lock locks the mutex with a cancellable context. If the context is cancelled @@ -43,7 +43,7 @@ func (m *Mutex) Lock(ctx context.Context) error { return serr } - m.myKey = fmt.Sprintf("%s/%x", m.pfx, s.Lease()) + 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())) diff --git a/integration/v3_election_test.go b/integration/v3_election_test.go index 9f4cea6be..8ca3298ed 100644 --- a/integration/v3_election_test.go +++ b/integration/v3_election_test.go @@ -174,3 +174,31 @@ func TestElectionSessionRecampaign(t *testing.T) { t.Fatalf("expected value=%q, got response %v", "def", resp) } } + +// TestElectionOnPrefixOfExistingKey checks that a single +// candidate can be elected on a new key that is a prefix +// of an existing key. To wit, check for regression +// of bug #6278. https://github.com/coreos/etcd/issues/6278 +// +func TestElectionOnPrefixOfExistingKey(t *testing.T) { + clus := NewClusterV3(t, &ClusterConfig{Size: 1}) + defer clus.Terminate(t) + + cli := clus.RandClient() + if _, err := cli.Put(context.TODO(), "testa", "value"); err != nil { + t.Fatal(err) + } + s, serr := concurrency.NewSession(cli) + if serr != nil { + t.Fatal(serr) + } + e := concurrency.NewElection(s, "test") + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + err := e.Campaign(ctx, "abc") + cancel() + if err != nil { + // after 5 seconds, deadlock results in + // 'context deadline exceeded' here. + t.Fatal(err) + } +}