Skip to content

Commit

Permalink
Merge pull request apache#31 from richardod/trunk
Browse files Browse the repository at this point in the history
Bug fix where 2 candidates can become leaders
  • Loading branch information
ewhauser committed Jan 29, 2016
2 parents f00d7e7 + 50d8432 commit 1776528
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 10 deletions.
34 changes: 32 additions & 2 deletions src/dotnet/ZooKeeperNet.Recipes.Tests/LeaderElectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void Teardown() {
}

private class TestLeaderWatcher : ILeaderWatcher {
public static byte Leader;
public byte Leader;
private readonly byte b;

public TestLeaderWatcher(byte b) {
Expand All @@ -53,7 +53,6 @@ public void TakeLeadership() {
[Test]
public void testElection() {
String dir = "/test";
String testString = "Hello World";
int num_clients = 10;
clients = new ZooKeeper[num_clients];
LeaderElection[] elections = new LeaderElection[num_clients];
Expand All @@ -71,5 +70,36 @@ public void testElection() {
}
Assert.Pass();
}

[Test]
public void testNode4DoesNotBecomeLeaderWhenNonLeader3Closes()
{
var dir = "/test";
var num_clients = 4;
clients = new ZooKeeper[num_clients];
var elections = new LeaderElection[num_clients];
var leaderWatchers = new TestLeaderWatcher[num_clients];

for (byte i = 0; i < clients.Length; i++)
{
clients[i] = CreateClient();
leaderWatchers[i] = new TestLeaderWatcher((byte)(i + 1)); // Start at 1 so we can check it is set
elections[i] = new LeaderElection(clients[i], dir, leaderWatchers[i], new[] { i });
elections[i].Start();
}

// Kill 2
elections[2].Close();
// First one should still be leader
Thread.Sleep(3000);
Assert.AreEqual(1, leaderWatchers[0].Leader);
Assert.AreEqual(0, leaderWatchers[1].Leader);
Assert.AreEqual(0, leaderWatchers[2].Leader);
Assert.AreEqual(0, leaderWatchers[3].Leader);
elections[0].Close();
elections[1].Close();
elections[3].Close();
}
}
}

38 changes: 30 additions & 8 deletions src/dotnet/ZooKeeperNet.Recipes/LeaderElection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/

using System;
using System.Diagnostics;
using System.Linq;

namespace ZooKeeperNet.Recipes {
Expand Down Expand Up @@ -100,8 +99,8 @@ public bool RunForLeader() {
if (penultimate == null) {
throw new InvalidOperationException("Penultimate value in priors is null, but count shoudl have been at least 2.");
}
var watchPath = path.Combine(penultimate.Name);
if (Zookeeper.Exists(watchPath, new LeaderWatcher(this, watchPath, watcher)) == null) {
var penultimatePath = path.Combine(penultimate.Name);
if (Zookeeper.Exists(penultimatePath, new LeaderWatcher(Zookeeper, this, penultimatePath, watcher, path, id)) == null) {
IsOwner = true;
watcher.TakeLeadership();
return true;
Expand All @@ -111,21 +110,44 @@ public bool RunForLeader() {

private class LeaderWatcher : IWatcher {
private readonly LeaderElection election;
private readonly string penultimatePath;
private readonly string path;
private readonly ILeaderWatcher watcher;
private readonly IZooKeeper zooKeeper;
private readonly string id;

public LeaderWatcher(LeaderElection election, string path, ILeaderWatcher watcher) {
public LeaderWatcher(IZooKeeper zooKeeper, LeaderElection election, string penultimatePath, ILeaderWatcher watcher, string path, string id) {
this.zooKeeper = zooKeeper;
this.election = election;
this.path = path;
this.penultimatePath = penultimatePath;
this.watcher = watcher;
this.path = path;
this.id = id;
}

public void Process(WatchedEvent @event) {
if (@event.Type == EventType.NodeDeleted && @event.Path == path) {
election.IsOwner = true;
watcher.TakeLeadership();
if (@event.Type == EventType.NodeDeleted && @event.Path == penultimatePath) {
var names = zooKeeper.GetChildren(path, false);
var sortedNames = new SortedSet<ZNodeName>();

foreach (var name in names)
{
sortedNames.Add(new ZNodeName(name));
}

if (SmallestZNode(sortedNames))
{
election.IsOwner = true;
watcher.TakeLeadership();
}
}
}

private bool SmallestZNode(SortedSet<ZNodeName> sortedNames)
{
// If a client receives a notification that the znode it is watching is gone, then it becomes the new leader in the case that there is no smaller znode
return sortedNames.Any() && id.EndsWith(sortedNames.First().Name, StringComparison.InvariantCulture);
}
}

public void Start() {
Expand Down

0 comments on commit 1776528

Please sign in to comment.