From fbf71c3e96ca44612fe214470b0f55fe3c80e02f Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Thu, 5 Sep 2019 13:52:06 +0800 Subject: [PATCH 01/19] Fix issue #761: ThreadSafeDoubleCheckLocking.java: Instantiating by Reflection call will be successful if you do that firstly --- singleton/pom.xml | 4 ++++ .../singleton/ThreadSafeDoubleCheckLocking.java | 6 +++++- .../ThreadSafeDoubleCheckLockingTest.java | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/singleton/pom.xml b/singleton/pom.xml index e3d8f06e2ea5..038d6fdac040 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -38,5 +38,9 @@ junit-jupiter-engine test + + junit + junit + diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java index a7557fda37e0..6c17f7c3f001 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java @@ -35,12 +35,16 @@ public final class ThreadSafeDoubleCheckLocking { private static volatile ThreadSafeDoubleCheckLocking instance; + private static boolean flag = true; + /** * private constructor to prevent client from instantiating. */ private ThreadSafeDoubleCheckLocking() { // to prevent instantiating by Reflection call - if (instance != null) { + if (flag) { + flag = false; + } else { throw new IllegalStateException("Already initialized."); } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java index 1d00d9f8f624..6286b09fb2a4 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java @@ -22,6 +22,11 @@ */ package com.iluwatar.singleton; +import org.junit.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + /** * Date: 12/29/15 - 19:26 PM * @@ -36,4 +41,15 @@ public ThreadSafeDoubleCheckLockingTest() { super(ThreadSafeDoubleCheckLocking::getInstance); } + /** + * Test creating new instance by refection + */ + @Test(expected = InvocationTargetException.class) + public void testCreatingNewInstanceByRefection() throws Exception { + ThreadSafeDoubleCheckLocking instance1 = ThreadSafeDoubleCheckLocking.getInstance(); + Constructor constructor = ThreadSafeDoubleCheckLocking.class.getDeclaredConstructor(); + constructor.setAccessible(true); + ThreadSafeDoubleCheckLocking instance2 = (ThreadSafeDoubleCheckLocking) constructor.newInstance(null); + } + } \ No newline at end of file From aea5cffe14b423f9a81b04406674d0318a026d10 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Mon, 9 Sep 2019 17:49:01 +0800 Subject: [PATCH 02/19] Create leader election module --- leader-election/README.md | 0 leader-election/pom.xml | 43 +++++++++++++++++++++++++++++++++++++++ pom.xml | 1 + 3 files changed, 44 insertions(+) create mode 100644 leader-election/README.md create mode 100644 leader-election/pom.xml diff --git a/leader-election/README.md b/leader-election/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/leader-election/pom.xml b/leader-election/pom.xml new file mode 100644 index 000000000000..e47eb2781677 --- /dev/null +++ b/leader-election/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + java-design-patterns + com.iluwatar + 1.21.0-SNAPSHOT + + leader-election + + + org.junit.jupiter + junit-jupiter-engine + test + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index cb5dbbd1c343..eb58d6d8d471 100644 --- a/pom.xml +++ b/pom.xml @@ -172,6 +172,7 @@ commander typeobjectpattern bytecode + leader-election From dfb965e5d32f5a9b89f13d42bd0144dc4b204a67 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Tue, 10 Sep 2019 10:33:46 +0800 Subject: [PATCH 03/19] Create Interface of Instance and MessageManager --- .../main/java/com/iluwatar/leader/election/Instance.java | 9 +++++++++ .../com/iluwatar/leader/election/MessageManager.java | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/Instance.java create mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/Instance.java b/leader-election/src/main/java/com/iluwatar/leader/election/Instance.java new file mode 100644 index 000000000000..a5df0d1dc1c5 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leader/election/Instance.java @@ -0,0 +1,9 @@ +package com.iluwatar.leader.election; + +public interface Instance { + + boolean checkIfLeaderIsAlive(); + + boolean isAlive(); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java new file mode 100644 index 000000000000..3edb4e5b86b7 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java @@ -0,0 +1,7 @@ +package com.iluwatar.leader.election; + +public interface MessageManager { + + boolean sendHealthCheckMessageToLeader(int leaderID); + +} From 08c59e5feb9a10ef34c9ee84b3eb75c32b9b58f6 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Tue, 10 Sep 2019 10:34:44 +0800 Subject: [PATCH 04/19] Create implementations with token ring algorithm --- .../com/iluwatar/leader/election/App.java | 30 +++++++ .../leader/election/TokenRingInstance.java | 80 +++++++++++++++++++ .../election/TokenRingMessageManager.java | 22 +++++ 3 files changed, 132 insertions(+) create mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/App.java create mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java create mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/App.java b/leader-election/src/main/java/com/iluwatar/leader/election/App.java new file mode 100644 index 000000000000..a1d74698b4bd --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leader/election/App.java @@ -0,0 +1,30 @@ +package com.iluwatar.leader.election; + +import java.util.HashMap; +import java.util.Map; + +public class App { + + public static void main(String[] args) { + MessageManager messageManager = new TokenRingMessageManager(); + TokenRingInstance instance1 = new TokenRingInstance(messageManager, 1, 1, true); + TokenRingInstance instance2 = new TokenRingInstance(messageManager, 2, 1, true); + TokenRingInstance instance3 = new TokenRingInstance(messageManager, 3, 1, true); + TokenRingInstance instance4 = new TokenRingInstance(messageManager, 4, 1, true); + Map map = new HashMap(); + map.put(1, instance1); + map.put(2, instance2); + map.put(3, instance3); + map.put(4, instance4); + ((TokenRingMessageManager) messageManager).setInstanceMap(map); + Thread t1 = new Thread(instance1); + Thread t2 = new Thread(instance2); + Thread t3 = new Thread(instance3); + Thread t4 = new Thread(instance4); + t1.start(); + t2.start(); + t3.start(); + t4.start(); + return; + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java b/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java new file mode 100644 index 000000000000..fb3ac6a7876c --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java @@ -0,0 +1,80 @@ +package com.iluwatar.leader.election; + +public class TokenRingInstance implements Instance, Runnable { + + private MessageManager messageManager; + + private int localID; + + private int leaderID; + + private boolean alive; + + public TokenRingInstance() { + } + + public TokenRingInstance(int localID, int leaderID) { + this.localID = localID; + this.leaderID = leaderID; + this.alive = true; + } + + public TokenRingInstance(MessageManager messageManager, int localID, int leaderID, boolean alive) { + this.messageManager = messageManager; + this.localID = localID; + this.leaderID = leaderID; + this.alive = alive; + } + + @Override + public void run() { + while (true) { + boolean isLeaderAlive = messageManager.sendHealthCheckMessageToLeader(this.leaderID); + if (isLeaderAlive) { + System.out.println(localID + " alive"); + } else { + System.out.println(localID + " not alive"); + + } + } + } + + @Override + public boolean checkIfLeaderIsAlive() { + boolean isAlive = messageManager.sendHealthCheckMessageToLeader(leaderID); + return isAlive; + } + + public int getLocalID() { + return localID; + } + + public void setLocalID(int localID) { + this.localID = localID; + } + + public int getLeaderID() { + return leaderID; + } + + public void setLeaderID(int leaderID) { + this.leaderID = leaderID; + } + + public MessageManager getMessageManager() { + return messageManager; + } + + public void setMessageManager(MessageManager messageManager) { + this.messageManager = messageManager; + } + + @Override + public boolean isAlive() { + return alive; + } + + public void setAlive(boolean alive) { + this.alive = alive; + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java new file mode 100644 index 000000000000..84f932dfef2d --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java @@ -0,0 +1,22 @@ +package com.iluwatar.leader.election; + +import java.util.Map; + +public class TokenRingMessageManager implements MessageManager { + + private Map instanceMap; + + public boolean sendHealthCheckMessageToLeader(int leaderID) { + Instance leaderInstance = instanceMap.get(leaderID); + boolean alive = leaderInstance.isAlive(); + return alive; + } + + public Map getInstanceMap() { + return instanceMap; + } + + public void setInstanceMap(Map instanceMap) { + this.instanceMap = instanceMap; + } +} From 4781edf03c4ab1bb9b85c73bf79e482ff9a01b2f Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Tue, 10 Sep 2019 17:00:44 +0800 Subject: [PATCH 05/19] Change package structure. Create basic message system. --- .../com/iluwatar/leader/election/App.java | 30 ------- .../iluwatar/leader/election/Instance.java | 9 --- .../leader/election/MessageManager.java | 7 -- .../leader/election/TokenRingInstance.java | 80 ------------------- .../election/TokenRingMessageManager.java | 22 ----- .../java/com/iluwatar/leaderelection/App.java | 8 ++ .../com/iluwatar/leaderelection/Instance.java | 9 +++ .../com/iluwatar/leaderelection/Message.java | 4 + .../leaderelection/MessageManager.java | 7 ++ .../leaderelection/ring/RingInstance.java | 77 ++++++++++++++++++ .../leaderelection/ring/RingMessage.java | 33 ++++++++ .../ring/RingMessageManager.java | 61 ++++++++++++++ .../leaderelection/ring/RingMessageType.java | 9 +++ 13 files changed, 208 insertions(+), 148 deletions(-) delete mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/App.java delete mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/Instance.java delete mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java delete mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java delete mode 100644 leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/App.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/Message.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/App.java b/leader-election/src/main/java/com/iluwatar/leader/election/App.java deleted file mode 100644 index a1d74698b4bd..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leader/election/App.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.iluwatar.leader.election; - -import java.util.HashMap; -import java.util.Map; - -public class App { - - public static void main(String[] args) { - MessageManager messageManager = new TokenRingMessageManager(); - TokenRingInstance instance1 = new TokenRingInstance(messageManager, 1, 1, true); - TokenRingInstance instance2 = new TokenRingInstance(messageManager, 2, 1, true); - TokenRingInstance instance3 = new TokenRingInstance(messageManager, 3, 1, true); - TokenRingInstance instance4 = new TokenRingInstance(messageManager, 4, 1, true); - Map map = new HashMap(); - map.put(1, instance1); - map.put(2, instance2); - map.put(3, instance3); - map.put(4, instance4); - ((TokenRingMessageManager) messageManager).setInstanceMap(map); - Thread t1 = new Thread(instance1); - Thread t2 = new Thread(instance2); - Thread t3 = new Thread(instance3); - Thread t4 = new Thread(instance4); - t1.start(); - t2.start(); - t3.start(); - t4.start(); - return; - } -} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/Instance.java b/leader-election/src/main/java/com/iluwatar/leader/election/Instance.java deleted file mode 100644 index a5df0d1dc1c5..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leader/election/Instance.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.iluwatar.leader.election; - -public interface Instance { - - boolean checkIfLeaderIsAlive(); - - boolean isAlive(); - -} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java deleted file mode 100644 index 3edb4e5b86b7..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leader/election/MessageManager.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.iluwatar.leader.election; - -public interface MessageManager { - - boolean sendHealthCheckMessageToLeader(int leaderID); - -} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java b/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java deleted file mode 100644 index fb3ac6a7876c..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingInstance.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.iluwatar.leader.election; - -public class TokenRingInstance implements Instance, Runnable { - - private MessageManager messageManager; - - private int localID; - - private int leaderID; - - private boolean alive; - - public TokenRingInstance() { - } - - public TokenRingInstance(int localID, int leaderID) { - this.localID = localID; - this.leaderID = leaderID; - this.alive = true; - } - - public TokenRingInstance(MessageManager messageManager, int localID, int leaderID, boolean alive) { - this.messageManager = messageManager; - this.localID = localID; - this.leaderID = leaderID; - this.alive = alive; - } - - @Override - public void run() { - while (true) { - boolean isLeaderAlive = messageManager.sendHealthCheckMessageToLeader(this.leaderID); - if (isLeaderAlive) { - System.out.println(localID + " alive"); - } else { - System.out.println(localID + " not alive"); - - } - } - } - - @Override - public boolean checkIfLeaderIsAlive() { - boolean isAlive = messageManager.sendHealthCheckMessageToLeader(leaderID); - return isAlive; - } - - public int getLocalID() { - return localID; - } - - public void setLocalID(int localID) { - this.localID = localID; - } - - public int getLeaderID() { - return leaderID; - } - - public void setLeaderID(int leaderID) { - this.leaderID = leaderID; - } - - public MessageManager getMessageManager() { - return messageManager; - } - - public void setMessageManager(MessageManager messageManager) { - this.messageManager = messageManager; - } - - @Override - public boolean isAlive() { - return alive; - } - - public void setAlive(boolean alive) { - this.alive = alive; - } -} diff --git a/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java deleted file mode 100644 index 84f932dfef2d..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leader/election/TokenRingMessageManager.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.iluwatar.leader.election; - -import java.util.Map; - -public class TokenRingMessageManager implements MessageManager { - - private Map instanceMap; - - public boolean sendHealthCheckMessageToLeader(int leaderID) { - Instance leaderInstance = instanceMap.get(leaderID); - boolean alive = leaderInstance.isAlive(); - return alive; - } - - public Map getInstanceMap() { - return instanceMap; - } - - public void setInstanceMap(Map instanceMap) { - this.instanceMap = instanceMap; - } -} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/App.java b/leader-election/src/main/java/com/iluwatar/leaderelection/App.java new file mode 100644 index 000000000000..45d0cf08f6a5 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/App.java @@ -0,0 +1,8 @@ +package com.iluwatar.leaderelection; + +public class App { + + public static void main(String[] args) { + + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java new file mode 100644 index 000000000000..158c84e0325b --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -0,0 +1,9 @@ +package com.iluwatar.leaderelection; + +public interface Instance { + + boolean isAlive(); + + void onMessage(Message message); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java new file mode 100644 index 000000000000..e95e243734bd --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -0,0 +1,4 @@ +package com.iluwatar.leaderelection; + +public interface Message { +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java new file mode 100644 index 000000000000..6a3d425a284e --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -0,0 +1,7 @@ +package com.iluwatar.leaderelection; + +public interface MessageManager { + + boolean sendHeartbeatMessageToLeader(int leaderID); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java new file mode 100644 index 000000000000..31325e3ee0ba --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -0,0 +1,77 @@ +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RingInstance implements Instance { + + private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); + + private MessageManager messageManager; + + private final int localID; + + private int leaderID; + + private boolean alive; + + public RingInstance(MessageManager messageManager, int localID, int leaderID, boolean alive) { + this.messageManager = messageManager; + this.localID = localID; + this.leaderID = leaderID; + this.alive = alive; + } + + @Override + public void onMessage(Message message) { + if (message instanceof RingMessage) { + switch (((RingMessage) message).getType()) { + case Election: + LOGGER.info("Instance " + localID + ": Election Message Received"); + this.handleElectionMessage(); + break; + case Leader: + LOGGER.info("Instance " + localID + ": Leader Message Received"); + this.handleLeaderMessage(); + break; + case Heartbeat: + LOGGER.info("Instance " + localID + ": Heartbeat Message Received"); + this.handleHeartbeatMessage(); + break; + } + } + } + + private void handleHeartbeatMessage() { + boolean isLeaderAlive = messageManager.sendHeartbeatMessageToLeader(this.leaderID); + if (isLeaderAlive) { + + } else { + + } + } + + private void handleElectionMessage() { + + } + + private void handleLeaderMessage() { + + } + + private boolean isLeader() { + return localID == leaderID; + } + + @Override + public boolean isAlive() { + return alive; + } + + public void setAlive(boolean alive) { + this.alive = alive; + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java new file mode 100644 index 000000000000..3d63ed9f6ec8 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java @@ -0,0 +1,33 @@ +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Message; + +public class RingMessage implements Message { + + private RingMessageType type; + + private String content; + + public RingMessage() {} + + public RingMessage(RingMessageType type, String content) { + this.type = type; + this.content = content; + } + + public RingMessageType getType() { + return type; + } + + public void setType(RingMessageType type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java new file mode 100644 index 000000000000..c579e57eb803 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -0,0 +1,61 @@ +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RingMessageManager implements MessageManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(RingMessageManager.class); + + private Map instanceMap; + + public boolean sendHeartbeatMessageToLeader(int leaderID) { + Instance leaderInstance = instanceMap.get(leaderID); + boolean alive = leaderInstance.isAlive(); + return alive; + } + + public void invokeNextHeartbeatCheck(int currentID) { + Instance nextHeartbeatInstance = this.findNextInstance(currentID); + Message heartbeatMessage = new RingMessage(RingMessageType.Heartbeat, ""); + nextHeartbeatInstance.onMessage(heartbeatMessage); + } + + private Instance findNextInstance(int currentID) { + Instance result = null; + List candidateSet = + instanceMap.keySet() + .stream() + .filter((i) -> i > currentID && instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()); + if (candidateSet.isEmpty()) { + int index = instanceMap.keySet() + .stream() + .filter((i) -> instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()) + .get(0); + result = instanceMap.get(index); + } else { + int index = candidateSet.get(0); + result = instanceMap.get(index); + } + return result; + } + + public Map getInstanceMap() { + return instanceMap; + } + + public void setInstanceMap(Map instanceMap) { + this.instanceMap = instanceMap; + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java new file mode 100644 index 000000000000..239c50a9a6d1 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java @@ -0,0 +1,9 @@ +package com.iluwatar.leaderelection.ring; + +public enum RingMessageType { + + Election, + Leader, + Heartbeat + +} From d44ba34b9074ff3d9ae4a906fe56d17764dd3db6 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Tue, 10 Sep 2019 17:54:17 +0800 Subject: [PATCH 06/19] Implement heartbeat and heartbeat invoking message system --- .../com/iluwatar/leaderelection/Instance.java | 2 + .../leaderelection/MessageManager.java | 8 ++- .../leaderelection/ring/RingInstance.java | 49 +++++++++++++------ .../ring/RingMessageManager.java | 28 ++++++----- .../leaderelection/ring/RingMessageType.java | 8 +-- 5 files changed, 63 insertions(+), 32 deletions(-) diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java index 158c84e0325b..d847ea9f51ee 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -4,6 +4,8 @@ public interface Instance { boolean isAlive(); + void setAlive(boolean alive); + void onMessage(Message message); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java index 6a3d425a284e..1a668c03877a 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -2,6 +2,12 @@ public interface MessageManager { - boolean sendHeartbeatMessageToLeader(int leaderID); + boolean sendHeartbeatMessage(int leaderID); + + void sendElectionMessage(int currentID, String content); + + void sendLeaderMessage(int currentID, int leaderID); + + void sendHeartbeatInvokeMessage(int currentID); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 31325e3ee0ba..9b0b43ee2250 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -6,39 +6,59 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RingInstance implements Instance { +import java.util.PriorityQueue; +import java.util.Queue; + +public class RingInstance implements Instance, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); private MessageManager messageManager; + private Queue messageQueue; + private final int localID; private int leaderID; private boolean alive; - public RingInstance(MessageManager messageManager, int localID, int leaderID, boolean alive) { + public RingInstance(MessageManager messageManager, int localID, int leaderID) { this.messageManager = messageManager; + this.messageQueue = new PriorityQueue<>(); this.localID = localID; this.leaderID = leaderID; - this.alive = alive; + this.alive = true; + } + + @Override + public void run() { + while (true) { + if (!messageQueue.isEmpty()) { + this.processMessage(messageQueue.poll()); + } + } } @Override public void onMessage(Message message) { + messageQueue.offer(message); + } + + private void processMessage(Message message) { if (message instanceof RingMessage) { switch (((RingMessage) message).getType()) { - case Election: - LOGGER.info("Instance " + localID + ": Election Message Received"); + case ELECTION: + LOGGER.info("Instance " + localID + ": Election Message handling..."); this.handleElectionMessage(); break; - case Leader: - LOGGER.info("Instance " + localID + ": Leader Message Received"); + case LEADER + : + LOGGER.info("Instance " + localID + ": Leader Message handling..."); this.handleLeaderMessage(); break; - case Heartbeat: - LOGGER.info("Instance " + localID + ": Heartbeat Message Received"); + case HEARTBEAT_INVOKE: + LOGGER.info("Instance " + localID + ": Heartbeat Message handling..."); this.handleHeartbeatMessage(); break; } @@ -46,11 +66,11 @@ public void onMessage(Message message) { } private void handleHeartbeatMessage() { - boolean isLeaderAlive = messageManager.sendHeartbeatMessageToLeader(this.leaderID); + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderID); if (isLeaderAlive) { - + messageManager.sendHeartbeatInvokeMessage(this.localID); } else { - + messageManager.sendElectionMessage(this.localID, String.valueOf(localID)); } } @@ -62,15 +82,12 @@ private void handleLeaderMessage() { } - private boolean isLeader() { - return localID == leaderID; - } - @Override public boolean isAlive() { return alive; } + @Override public void setAlive(boolean alive) { this.alive = alive; } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index c579e57eb803..1814675a7b13 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -16,16 +16,28 @@ public class RingMessageManager implements MessageManager { private Map instanceMap; - public boolean sendHeartbeatMessageToLeader(int leaderID) { + @Override + public boolean sendHeartbeatMessage(int leaderID) { Instance leaderInstance = instanceMap.get(leaderID); boolean alive = leaderInstance.isAlive(); return alive; } - public void invokeNextHeartbeatCheck(int currentID) { + @Override + public void sendElectionMessage(int currentID, String content) { + + } + + @Override + public void sendLeaderMessage(int currentID, int leaderID) { + + } + + @Override + public void sendHeartbeatInvokeMessage(int currentID) { Instance nextHeartbeatInstance = this.findNextInstance(currentID); - Message heartbeatMessage = new RingMessage(RingMessageType.Heartbeat, ""); - nextHeartbeatInstance.onMessage(heartbeatMessage); + Message heartbeatInvokeMessage = new RingMessage(RingMessageType.HEARTBEAT_INVOKE, ""); + nextHeartbeatInstance.onMessage(heartbeatInvokeMessage); } private Instance findNextInstance(int currentID) { @@ -50,12 +62,4 @@ private Instance findNextInstance(int currentID) { } return result; } - - public Map getInstanceMap() { - return instanceMap; - } - - public void setInstanceMap(Map instanceMap) { - this.instanceMap = instanceMap; - } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java index 239c50a9a6d1..573c7da41f58 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java @@ -2,8 +2,10 @@ public enum RingMessageType { - Election, - Leader, - Heartbeat + ELECTION, + LEADER, + HEARTBEAT, + HEARTBEAT_INVOKE } + From c0af709612075dfb8cbf73be0809612d298ea71f Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Wed, 11 Sep 2019 10:02:20 +0800 Subject: [PATCH 07/19] Implement election message handler --- .../com/iluwatar/leaderelection/Message.java | 5 ++ .../iluwatar/leaderelection/MessageType.java | 11 ++++ .../leaderelection/ring/RingInstance.java | 54 +++++++++++-------- .../leaderelection/ring/RingMessage.java | 9 ++-- .../ring/RingMessageManager.java | 11 ++-- .../leaderelection/ring/RingMessageType.java | 11 ---- 6 files changed, 60 insertions(+), 41 deletions(-) create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java delete mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index e95e243734bd..a166022cac9f 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -1,4 +1,9 @@ package com.iluwatar.leaderelection; public interface Message { + + MessageType getType(); + + String getContent(); + } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java new file mode 100644 index 000000000000..dd7bc70b2000 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -0,0 +1,11 @@ +package com.iluwatar.leaderelection; + +public enum MessageType { + + ELECTION, + LEADER, + HEARTBEAT, + HEARTBEAT_INVOKE + +} + diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 9b0b43ee2250..16e8bc6dade9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -6,8 +6,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.PriorityQueue; -import java.util.Queue; +import java.util.*; +import java.util.stream.Collectors; public class RingInstance implements Instance, Runnable { @@ -46,39 +46,49 @@ public void onMessage(Message message) { } private void processMessage(Message message) { - if (message instanceof RingMessage) { - switch (((RingMessage) message).getType()) { - case ELECTION: - LOGGER.info("Instance " + localID + ": Election Message handling..."); - this.handleElectionMessage(); - break; - case LEADER - : - LOGGER.info("Instance " + localID + ": Leader Message handling..."); - this.handleLeaderMessage(); - break; - case HEARTBEAT_INVOKE: - LOGGER.info("Instance " + localID + ": Heartbeat Message handling..."); - this.handleHeartbeatMessage(); - break; - } + switch (message.getType()) { + case ELECTION: + LOGGER.info("Instance " + localID + ": Election Message handling..."); + this.handleElectionMessage(message); + break; + case LEADER: + LOGGER.info("Instance " + localID + ": Leader Message handling..."); + this.handleLeaderMessage(message); + break; + case HEARTBEAT_INVOKE: + LOGGER.info("Instance " + localID + ": Heartbeat Message handling..."); + this.handleHeartbeatMessage(message); + break; } } - private void handleHeartbeatMessage() { + private void handleHeartbeatMessage(Message message) { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderID); if (isLeaderAlive) { + LOGGER.info("Instance " + localID + ": Leader is alive."); messageManager.sendHeartbeatInvokeMessage(this.localID); } else { + LOGGER.info("Instance " + localID + ": Leader is not alive. Start election."); messageManager.sendElectionMessage(this.localID, String.valueOf(localID)); } } - private void handleElectionMessage() { - + private void handleElectionMessage(Message message) { + String content = message.getContent(); + List candidateList = + Arrays.stream(content.trim().split(",")) + .map(Integer::valueOf) + .sorted() + .collect(Collectors.toList()); + if (candidateList.contains(this.localID)) { + messageManager.sendLeaderMessage(this.localID, candidateList.get(0)); + } else { + content += "," + localID; + messageManager.sendElectionMessage(this.localID, content); + } } - private void handleLeaderMessage() { + private void handleLeaderMessage(Message message) { } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java index 3d63ed9f6ec8..3797091eeb19 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java @@ -1,25 +1,26 @@ package com.iluwatar.leaderelection.ring; import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; public class RingMessage implements Message { - private RingMessageType type; + private MessageType type; private String content; public RingMessage() {} - public RingMessage(RingMessageType type, String content) { + public RingMessage(MessageType type, String content) { this.type = type; this.content = content; } - public RingMessageType getType() { + public MessageType getType() { return type; } - public void setType(RingMessageType type) { + public void setType(MessageType type) { this.type = type; } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 1814675a7b13..9cee1ea8756d 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -3,6 +3,7 @@ import com.iluwatar.leaderelection.Instance; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +26,9 @@ public boolean sendHeartbeatMessage(int leaderID) { @Override public void sendElectionMessage(int currentID, String content) { - + Instance nextInstance = this.findNextInstance(currentID); + Message electionMessage = new RingMessage(MessageType.ELECTION, content); + nextInstance.onMessage(electionMessage); } @Override @@ -35,9 +38,9 @@ public void sendLeaderMessage(int currentID, int leaderID) { @Override public void sendHeartbeatInvokeMessage(int currentID) { - Instance nextHeartbeatInstance = this.findNextInstance(currentID); - Message heartbeatInvokeMessage = new RingMessage(RingMessageType.HEARTBEAT_INVOKE, ""); - nextHeartbeatInstance.onMessage(heartbeatInvokeMessage); + Instance nextInstance = this.findNextInstance(currentID); + Message heartbeatInvokeMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); } private Instance findNextInstance(int currentID) { diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java deleted file mode 100644 index 573c7da41f58..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageType.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.iluwatar.leaderelection.ring; - -public enum RingMessageType { - - ELECTION, - LEADER, - HEARTBEAT, - HEARTBEAT_INVOKE - -} - From 3831a473b1970c760b922470f4c51e74c89619cd Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Wed, 11 Sep 2019 10:10:24 +0800 Subject: [PATCH 08/19] Add leader message handler --- .../iluwatar/leaderelection/ring/RingInstance.java | 12 +++++++----- .../iluwatar/leaderelection/ring/RingMessage.java | 1 - .../leaderelection/ring/RingMessageManager.java | 10 +++++++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 16e8bc6dade9..962bda8a8cb4 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -14,13 +14,9 @@ public class RingInstance implements Instance, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); private MessageManager messageManager; - private Queue messageQueue; - private final int localID; - private int leaderID; - private boolean alive; public RingInstance(MessageManager messageManager, int localID, int leaderID) { @@ -89,7 +85,13 @@ private void handleElectionMessage(Message message) { } private void handleLeaderMessage(Message message) { - + int newLeaderID = Integer.valueOf(message.getContent()); + if (this.leaderID != newLeaderID) { + this.leaderID = newLeaderID; + messageManager.sendLeaderMessage(this.localID, newLeaderID); + } else { + messageManager.sendHeartbeatInvokeMessage(this.localID); + } } @Override diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java index 3797091eeb19..ce284d734711 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java @@ -6,7 +6,6 @@ public class RingMessage implements Message { private MessageType type; - private String content; public RingMessage() {} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 9cee1ea8756d..f9fc80003aba 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -17,6 +17,12 @@ public class RingMessageManager implements MessageManager { private Map instanceMap; + public RingMessageManager() {} + + public RingMessageManager(Map instanceMap) { + this.instanceMap = instanceMap; + } + @Override public boolean sendHeartbeatMessage(int leaderID) { Instance leaderInstance = instanceMap.get(leaderID); @@ -33,7 +39,9 @@ public void sendElectionMessage(int currentID, String content) { @Override public void sendLeaderMessage(int currentID, int leaderID) { - + Instance nextInstance = this.findNextInstance(currentID); + Message leaderMessage = new RingMessage(MessageType.LEADER, String.valueOf(leaderID)); + nextInstance.onMessage(leaderMessage); } @Override From 4fca9f10e11eaa2369677b10713899ba1e38f00f Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Wed, 11 Sep 2019 13:24:44 +0800 Subject: [PATCH 09/19] Add main entry point --- .../java/com/iluwatar/leaderelection/App.java | 8 ---- .../iluwatar/leaderelection/ring/RingApp.java | 45 +++++++++++++++++++ .../leaderelection/ring/RingInstance.java | 28 +++++++----- .../ring/RingMessageManager.java | 2 - 4 files changed, 61 insertions(+), 22 deletions(-) delete mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/App.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/App.java b/leader-election/src/main/java/com/iluwatar/leaderelection/App.java deleted file mode 100644 index 45d0cf08f6a5..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/App.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.iluwatar.leaderelection; - -public class App { - - public static void main(String[] args) { - - } -} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java new file mode 100644 index 000000000000..55240f3c034a --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -0,0 +1,45 @@ +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; + +import java.util.HashMap; +import java.util.Map; + +public class RingApp { + + public static void main(String[] args) { + + Map instanceMap = new HashMap<>(); + MessageManager messageManager = new RingMessageManager(instanceMap); + + RingInstance instance1 = new RingInstance(messageManager, 1, 1); + RingInstance instance2 = new RingInstance(messageManager, 2, 1); + RingInstance instance3 = new RingInstance(messageManager, 3, 1); + RingInstance instance4 = new RingInstance(messageManager, 4, 1); + RingInstance instance5 = new RingInstance(messageManager, 5, 1); + + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); + + instance2.onMessage(new RingMessage(MessageType.HEARTBEAT_INVOKE, "")); + + Thread thread1 = new Thread(instance1); + Thread thread2 = new Thread(instance2); + Thread thread3 = new Thread(instance3); + Thread thread4 = new Thread(instance4); + Thread thread5 = new Thread(instance5); + + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); + + instance1.setAlive(false); + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 962bda8a8cb4..9d3f5cf1eba9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -3,16 +3,15 @@ import com.iluwatar.leaderelection.Instance; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; public class RingInstance implements Instance, Runnable { - private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); - private MessageManager messageManager; private Queue messageQueue; private final int localID; @@ -21,7 +20,7 @@ public class RingInstance implements Instance, Runnable { public RingInstance(MessageManager messageManager, int localID, int leaderID) { this.messageManager = messageManager; - this.messageQueue = new PriorityQueue<>(); + this.messageQueue = new ConcurrentLinkedQueue<>(); this.localID = localID; this.leaderID = leaderID; this.alive = true; @@ -31,8 +30,9 @@ public RingInstance(MessageManager messageManager, int localID, int leaderID) { public void run() { while (true) { if (!messageQueue.isEmpty()) { - this.processMessage(messageQueue.poll()); + this.processMessage(messageQueue.remove()); } + System.out.flush(); } } @@ -44,15 +44,15 @@ public void onMessage(Message message) { private void processMessage(Message message) { switch (message.getType()) { case ELECTION: - LOGGER.info("Instance " + localID + ": Election Message handling..."); + System.out.println("Instance " + localID + " - Election Message handling..."); this.handleElectionMessage(message); break; case LEADER: - LOGGER.info("Instance " + localID + ": Leader Message handling..."); + System.out.println("Instance " + localID + " - Leader Message handling..."); this.handleLeaderMessage(message); break; case HEARTBEAT_INVOKE: - LOGGER.info("Instance " + localID + ": Heartbeat Message handling..."); + System.out.println("Instance " + localID + " - Heartbeat Message handling..."); this.handleHeartbeatMessage(message); break; } @@ -61,22 +61,24 @@ private void processMessage(Message message) { private void handleHeartbeatMessage(Message message) { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderID); if (isLeaderAlive) { - LOGGER.info("Instance " + localID + ": Leader is alive."); + System.out.println("Instance " + localID + "- Leader is alive."); messageManager.sendHeartbeatInvokeMessage(this.localID); } else { - LOGGER.info("Instance " + localID + ": Leader is not alive. Start election."); + System.out.println("Instance " + localID + "- Leader is not alive. Start election."); messageManager.sendElectionMessage(this.localID, String.valueOf(localID)); } } private void handleElectionMessage(Message message) { String content = message.getContent(); + System.out.println("Instance " + localID + " - Election Message: " + content); List candidateList = Arrays.stream(content.trim().split(",")) .map(Integer::valueOf) .sorted() .collect(Collectors.toList()); if (candidateList.contains(this.localID)) { + System.out.println("Instance " + localID + " - New leader should be " + candidateList.get(0) + ". Start leader notification."); messageManager.sendLeaderMessage(this.localID, candidateList.get(0)); } else { content += "," + localID; @@ -87,9 +89,11 @@ private void handleElectionMessage(Message message) { private void handleLeaderMessage(Message message) { int newLeaderID = Integer.valueOf(message.getContent()); if (this.leaderID != newLeaderID) { + System.out.println("Instance " + localID + " - Update leaderID"); this.leaderID = newLeaderID; messageManager.sendLeaderMessage(this.localID, newLeaderID); } else { + System.out.println("Instance " + localID + " - Leader update done. Start heartbeat."); messageManager.sendHeartbeatInvokeMessage(this.localID); } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index f9fc80003aba..47fb02a764e4 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -13,8 +13,6 @@ public class RingMessageManager implements MessageManager { - private static final Logger LOGGER = LoggerFactory.getLogger(RingMessageManager.class); - private Map instanceMap; public RingMessageManager() {} From 6b7dd6bf6cd4f44f036fa86be01c42a7549348fd Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Thu, 12 Sep 2019 10:39:21 +0800 Subject: [PATCH 10/19] Add comments --- leader-election/pom.xml | 2 +- .../com/iluwatar/leaderelection/Instance.java | 26 ++++++++ .../com/iluwatar/leaderelection/Message.java | 26 ++++++++ .../leaderelection/MessageManager.java | 26 ++++++++ .../iluwatar/leaderelection/MessageType.java | 26 ++++++++ .../iluwatar/leaderelection/ring/RingApp.java | 23 +++++++ .../leaderelection/ring/RingInstance.java | 65 +++++++++++++++++++ .../leaderelection/ring/RingMessage.java | 26 ++++++++ .../ring/RingMessageManager.java | 52 ++++++++++++++- 9 files changed, 269 insertions(+), 3 deletions(-) diff --git a/leader-election/pom.xml b/leader-election/pom.xml index e47eb2781677..daac186af9d0 100644 --- a/leader-election/pom.xml +++ b/leader-election/pom.xml @@ -15,7 +15,7 @@ all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + IMPLIED, INCLUDING BUT NOT LIMITED TO THFE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java index d847ea9f51ee..6a2596d35cf0 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -1,5 +1,31 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection; +/** + * Instance interface + */ public interface Instance { boolean isAlive(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index a166022cac9f..d4facf2d4ec5 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -1,5 +1,31 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection; +/** + * Message interface + */ public interface Message { MessageType getType(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java index 1a668c03877a..36415182f8b4 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -1,5 +1,31 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection; +/** + * MessageManager interface + */ public interface MessageManager { boolean sendHeartbeatMessage(int leaderID); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java index dd7bc70b2000..5a1af57e977d 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -1,5 +1,31 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection; +/** + * Message Type enum + */ public enum MessageType { ELECTION, diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java index 55240f3c034a..fd75898ae389 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -1,3 +1,26 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection.ring; import com.iluwatar.leaderelection.Instance; diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 9d3f5cf1eba9..b5037199f57c 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -1,3 +1,26 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection.ring; import com.iluwatar.leaderelection.Instance; @@ -10,6 +33,17 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; +/** + * Implementation with token ring alogirthm. The instances in the system are organized as a ring. + * Each instance should have a sequential id and the instance with smallest (or largest) id should + * be the initial leader. All the other instances send heartbeat message to leader periodically + * to check its health. If one certain instance finds the server done, it will send an election + * message to the next alive instance in the ring, which contains its own ID. Then the next instance + * add its ID into the message and pass it to the next. After all the alive instances' ID are add + * to the message, the message is send back to the first instance and it will choose the instance + * with smallest ID to be the new leader, and then send a leader message to other instances to + * inform the result. + */ public class RingInstance implements Instance, Runnable { private MessageManager messageManager; @@ -18,6 +52,9 @@ public class RingInstance implements Instance, Runnable { private int leaderID; private boolean alive; + /** + * Constructor of RingInstance. + */ public RingInstance(MessageManager messageManager, int localID, int leaderID) { this.messageManager = messageManager; this.messageQueue = new ConcurrentLinkedQueue<>(); @@ -26,6 +63,9 @@ public RingInstance(MessageManager messageManager, int localID, int leaderID) { this.alive = true; } + /** + * The instance will execute the message in its message queue periodically once it is alive. + */ @Override public void run() { while (true) { @@ -36,6 +76,10 @@ public void run() { } } + /** + * Message listening method of the instance. + * @param message + */ @Override public void onMessage(Message message) { messageQueue.offer(message); @@ -58,6 +102,11 @@ private void processMessage(Message message) { } } + /** + * Process the heartbeat invoke message. After receiving the message, the instance will send a heartbeat + * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, + * it will start the election process. + */ private void handleHeartbeatMessage(Message message) { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderID); if (isLeaderAlive) { @@ -69,6 +118,11 @@ private void handleHeartbeatMessage(Message message) { } } + /** + * Process election message. If the local ID is contained in the ID list, the instance will select the + * alive instance with smallest ID to be the new leader, and send the leader inform message. If not, + * it will add its local ID to the list and send the message to the next instance in the ring. + */ private void handleElectionMessage(Message message) { String content = message.getContent(); System.out.println("Instance " + localID + " - Election Message: " + content); @@ -86,6 +140,10 @@ private void handleElectionMessage(Message message) { } } + /** + * Process leader Message. The instance will set the leader ID to be the new one and send the message to + * the next instance until all the alive instance in the ring is informed. + */ private void handleLeaderMessage(Message message) { int newLeaderID = Integer.valueOf(message.getContent()); if (this.leaderID != newLeaderID) { @@ -98,11 +156,18 @@ private void handleLeaderMessage(Message message) { } } + /** + * Check whether the certain instnace is alive or not. + * @return {@code true} if the instance is alive + */ @Override public boolean isAlive() { return alive; } + /** + * Set the status of instance. + */ @Override public void setAlive(boolean alive) { this.alive = alive; diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java index ce284d734711..5d0a260615ff 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java @@ -1,8 +1,34 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection.ring; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageType; +/** + * Message used by Ring type instance + */ public class RingMessage implements Message { private MessageType type; diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 47fb02a764e4..b71df83f88ff 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -1,11 +1,32 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + package com.iluwatar.leaderelection.ring; import com.iluwatar.leaderelection.Instance; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageManager; import com.iluwatar.leaderelection.MessageType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; @@ -15,12 +36,20 @@ public class RingMessageManager implements MessageManager { private Map instanceMap; + /** + * Constructor of RingMessageManager. + */ public RingMessageManager() {} public RingMessageManager(Map instanceMap) { this.instanceMap = instanceMap; } + /** + * Send heartbeat message to current leader instance to check the health. + * @param leaderID + * @return {@code true} if the leader is alive. + */ @Override public boolean sendHeartbeatMessage(int leaderID) { Instance leaderInstance = instanceMap.get(leaderID); @@ -28,6 +57,11 @@ public boolean sendHeartbeatMessage(int leaderID) { return alive; } + /** + * Send election message to the next instance. + * @param currentID + * @param content list contains all the IDs of instances which have received this election message. + */ @Override public void sendElectionMessage(int currentID, String content) { Instance nextInstance = this.findNextInstance(currentID); @@ -35,6 +69,11 @@ public void sendElectionMessage(int currentID, String content) { nextInstance.onMessage(electionMessage); } + /** + * Send leader message to the next instance. + * @param currentID + * @param leaderID + */ @Override public void sendLeaderMessage(int currentID, int leaderID) { Instance nextInstance = this.findNextInstance(currentID); @@ -42,6 +81,10 @@ public void sendLeaderMessage(int currentID, int leaderID) { nextInstance.onMessage(leaderMessage); } + /** + * Send heartbeat invoke message to the next instance. + * @param currentID + */ @Override public void sendHeartbeatInvokeMessage(int currentID) { Instance nextInstance = this.findNextInstance(currentID); @@ -49,6 +92,11 @@ public void sendHeartbeatInvokeMessage(int currentID) { nextInstance.onMessage(heartbeatInvokeMessage); } + /** + * Find the next instance in the ring. + * @param currentID + * @return The next instance. + */ private Instance findNextInstance(int currentID) { Instance result = null; List candidateSet = From 2f8bbd473f68725eaa3f47801fb8dba58fe12d32 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Thu, 12 Sep 2019 10:52:12 +0800 Subject: [PATCH 11/19] Update README.md --- leader-election/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/leader-election/README.md b/leader-election/README.md index e69de29bb2d1..36c2670dffa0 100644 --- a/leader-election/README.md +++ b/leader-election/README.md @@ -0,0 +1,31 @@ +--- +layout: pattern +title: Leader Election +folder: leader-election +permalink: /patterns/leader-election/ +categories: Other +tags: + - Java + - Difficulty-Beginner +--- + +## Intent +Leader Election pattern is commonly used in cloud system design. It can help to ensure that task instances selec the leader instance correctly and do not conflict with each other, cause contention for shared resources, or inadvertently interfere with the work that other task instances are performing. + +## Applicability +Use this pattern when + +* the tasks in a distributed application, such as a cloud-hosted solution, require careful coordination and there is no natural leader. + +Do not use this pattern when + +* there is a natural leader or dedicated process that can always act as the leader. For example, it may be possible to implement a singleton process that coordinates the task instances. If this process fails or becomes unhealthy, the system can shut it down and restart it. +* the coordination between tasks can be easily achieved by using a more lightweight mechanism. For example, if several task instances simply require coordinated access to a shared resource, a preferable solution might be to use optimistic or pessimistic locking to control access to that resource. + +## Real world examples + +* [Raft Leader Election](https://github.com/ronenhamias/raft-leader-election) + +## Credits + +* [ Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications](https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn568104(v=pandp.10)) From 009bf9136ae3390a83189605f97bb4301a888802 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Thu, 12 Sep 2019 11:51:29 +0800 Subject: [PATCH 12/19] Fix checkstyle issue --- leader-election/pom.xml | 2 +- .../com/iluwatar/leaderelection/Instance.java | 6 +- .../com/iluwatar/leaderelection/Message.java | 4 +- .../leaderelection/MessageManager.java | 8 +- .../iluwatar/leaderelection/MessageType.java | 8 +- .../iluwatar/leaderelection/ring/RingApp.java | 60 +++-- .../leaderelection/ring/RingInstance.java | 226 +++++++++--------- .../leaderelection/ring/RingMessage.java | 38 +-- .../ring/RingMessageManager.java | 150 ++++++------ 9 files changed, 255 insertions(+), 247 deletions(-) diff --git a/leader-election/pom.xml b/leader-election/pom.xml index daac186af9d0..7c1312d01a1d 100644 --- a/leader-election/pom.xml +++ b/leader-election/pom.xml @@ -30,7 +30,7 @@ java-design-patterns com.iluwatar - 1.21.0-SNAPSHOT + 1.22.0-SNAPSHOT leader-election diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java index 6a2596d35cf0..78efd8a62a2b 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -28,10 +28,10 @@ */ public interface Instance { - boolean isAlive(); + boolean isAlive(); - void setAlive(boolean alive); + void setAlive(boolean alive); - void onMessage(Message message); + void onMessage(Message message); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index d4facf2d4ec5..adecc3e20467 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -28,8 +28,8 @@ */ public interface Message { - MessageType getType(); + MessageType getType(); - String getContent(); + String getContent(); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java index 36415182f8b4..b5c5244ac85c 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -28,12 +28,12 @@ */ public interface MessageManager { - boolean sendHeartbeatMessage(int leaderID); + boolean sendHeartbeatMessage(int leaderId); - void sendElectionMessage(int currentID, String content); + void sendElectionMessage(int currentId, String content); - void sendLeaderMessage(int currentID, int leaderID); + void sendLeaderMessage(int currentId, int leaderId); - void sendHeartbeatInvokeMessage(int currentID); + void sendHeartbeatInvokeMessage(int currentId); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java index 5a1af57e977d..478fa6d9e7e5 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -28,10 +28,10 @@ */ public enum MessageType { - ELECTION, - LEADER, - HEARTBEAT, - HEARTBEAT_INVOKE + ELECTION, + LEADER, + HEARTBEAT, + HEARTBEAT_INVOKE } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java index fd75898ae389..8c6737d978be 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -30,39 +30,47 @@ import java.util.HashMap; import java.util.Map; +/** + * Example of how to use ring leader election. Initially 5 instances is created in the clould + * system, and the instance with ID 1 is set as leader. After the system is started stop the + * leader instance, and the new leader will be elected. + */ public class RingApp { - public static void main(String[] args) { + /** + * Program entry point + */ + public static void main(String[] args) { - Map instanceMap = new HashMap<>(); - MessageManager messageManager = new RingMessageManager(instanceMap); + Map instanceMap = new HashMap<>(); + MessageManager messageManager = new RingMessageManager(instanceMap); - RingInstance instance1 = new RingInstance(messageManager, 1, 1); - RingInstance instance2 = new RingInstance(messageManager, 2, 1); - RingInstance instance3 = new RingInstance(messageManager, 3, 1); - RingInstance instance4 = new RingInstance(messageManager, 4, 1); - RingInstance instance5 = new RingInstance(messageManager, 5, 1); + RingInstance instance1 = new RingInstance(messageManager, 1, 1); + RingInstance instance2 = new RingInstance(messageManager, 2, 1); + RingInstance instance3 = new RingInstance(messageManager, 3, 1); + RingInstance instance4 = new RingInstance(messageManager, 4, 1); + RingInstance instance5 = new RingInstance(messageManager, 5, 1); - instanceMap.put(1, instance1); - instanceMap.put(2, instance2); - instanceMap.put(3, instance3); - instanceMap.put(4, instance4); - instanceMap.put(5, instance5); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); - instance2.onMessage(new RingMessage(MessageType.HEARTBEAT_INVOKE, "")); + instance2.onMessage(new RingMessage(MessageType.HEARTBEAT_INVOKE, "")); - Thread thread1 = new Thread(instance1); - Thread thread2 = new Thread(instance2); - Thread thread3 = new Thread(instance3); - Thread thread4 = new Thread(instance4); - Thread thread5 = new Thread(instance5); + Thread thread1 = new Thread(instance1); + Thread thread2 = new Thread(instance2); + Thread thread3 = new Thread(instance3); + Thread thread4 = new Thread(instance4); + Thread thread5 = new Thread(instance5); - thread1.start(); - thread2.start(); - thread3.start(); - thread4.start(); - thread5.start(); + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); - instance1.setAlive(false); - } + instance1.setAlive(false); + } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index b5037199f57c..c4a92adc69d8 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -46,130 +46,132 @@ */ public class RingInstance implements Instance, Runnable { - private MessageManager messageManager; - private Queue messageQueue; - private final int localID; - private int leaderID; - private boolean alive; + private MessageManager messageManager; + private Queue messageQueue; + private final int localId; + private int leaderId; + private boolean alive; - /** - * Constructor of RingInstance. - */ - public RingInstance(MessageManager messageManager, int localID, int leaderID) { - this.messageManager = messageManager; - this.messageQueue = new ConcurrentLinkedQueue<>(); - this.localID = localID; - this.leaderID = leaderID; - this.alive = true; - } + /** + * Constructor of RingInstance. + */ + public RingInstance(MessageManager messageManager, int localId, int leaderId) { + this.messageManager = messageManager; + this.messageQueue = new ConcurrentLinkedQueue<>(); + this.localId = localId; + this.leaderId = leaderId; + this.alive = true; + } - /** - * The instance will execute the message in its message queue periodically once it is alive. - */ - @Override - public void run() { - while (true) { - if (!messageQueue.isEmpty()) { - this.processMessage(messageQueue.remove()); - } - System.out.flush(); - } + /** + * The instance will execute the message in its message queue periodically once it is alive. + */ + @Override + public void run() { + while (true) { + if (!messageQueue.isEmpty()) { + this.processMessage(messageQueue.remove()); + } + System.out.flush(); } + } - /** - * Message listening method of the instance. - * @param message - */ - @Override - public void onMessage(Message message) { - messageQueue.offer(message); - } + /** + * Message listening method of the instance. + */ + @Override + public void onMessage(Message message) { + messageQueue.offer(message); + } - private void processMessage(Message message) { - switch (message.getType()) { - case ELECTION: - System.out.println("Instance " + localID + " - Election Message handling..."); - this.handleElectionMessage(message); - break; - case LEADER: - System.out.println("Instance " + localID + " - Leader Message handling..."); - this.handleLeaderMessage(message); - break; - case HEARTBEAT_INVOKE: - System.out.println("Instance " + localID + " - Heartbeat Message handling..."); - this.handleHeartbeatMessage(message); - break; - } + private void processMessage(Message message) { + switch (message.getType()) { + case ELECTION: + System.out.println("Instance " + localId + " - Election Message handling..."); + this.handleElectionMessage(message); + break; + case LEADER: + System.out.println("Instance " + localId + " - Leader Message handling..."); + this.handleLeaderMessage(message); + break; + case HEARTBEAT_INVOKE: + System.out.println("Instance " + localId + " - Heartbeat Message handling..."); + this.handleHeartbeatMessage(message); + break; + default: + break; } + } - /** - * Process the heartbeat invoke message. After receiving the message, the instance will send a heartbeat - * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, - * it will start the election process. - */ - private void handleHeartbeatMessage(Message message) { - boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderID); - if (isLeaderAlive) { - System.out.println("Instance " + localID + "- Leader is alive."); - messageManager.sendHeartbeatInvokeMessage(this.localID); - } else { - System.out.println("Instance " + localID + "- Leader is not alive. Start election."); - messageManager.sendElectionMessage(this.localID, String.valueOf(localID)); - } + /** + * Process the heartbeat invoke message. After receiving the message, the instance will send a heartbeat + * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, + * it will start the election process. + */ + private void handleHeartbeatMessage(Message message) { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); + if (isLeaderAlive) { + System.out.println("Instance " + localId + "- Leader is alive."); + messageManager.sendHeartbeatInvokeMessage(this.localId); + } else { + System.out.println("Instance " + localId + "- Leader is not alive. Start election."); + messageManager.sendElectionMessage(this.localId, String.valueOf(localId)); } + } - /** - * Process election message. If the local ID is contained in the ID list, the instance will select the - * alive instance with smallest ID to be the new leader, and send the leader inform message. If not, - * it will add its local ID to the list and send the message to the next instance in the ring. - */ - private void handleElectionMessage(Message message) { - String content = message.getContent(); - System.out.println("Instance " + localID + " - Election Message: " + content); - List candidateList = - Arrays.stream(content.trim().split(",")) - .map(Integer::valueOf) - .sorted() - .collect(Collectors.toList()); - if (candidateList.contains(this.localID)) { - System.out.println("Instance " + localID + " - New leader should be " + candidateList.get(0) + ". Start leader notification."); - messageManager.sendLeaderMessage(this.localID, candidateList.get(0)); - } else { - content += "," + localID; - messageManager.sendElectionMessage(this.localID, content); - } + /** + * Process election message. If the local ID is contained in the ID list, the instance will select the + * alive instance with smallest ID to be the new leader, and send the leader inform message. If not, + * it will add its local ID to the list and send the message to the next instance in the ring. + */ + private void handleElectionMessage(Message message) { + String content = message.getContent(); + System.out.println("Instance " + localId + " - Election Message: " + content); + List candidateList = + Arrays.stream(content.trim().split(",")) + .map(Integer::valueOf) + .sorted() + .collect(Collectors.toList()); + if (candidateList.contains(this.localId)) { + int newLeaderId = candidateList.get(0); + System.out.println("Instance " + localId + " - New leader should be " + newLeaderId + "."); + messageManager.sendLeaderMessage(this.localId, newLeaderId); + } else { + content += "," + localId; + messageManager.sendElectionMessage(this.localId, content); } + } - /** - * Process leader Message. The instance will set the leader ID to be the new one and send the message to - * the next instance until all the alive instance in the ring is informed. - */ - private void handleLeaderMessage(Message message) { - int newLeaderID = Integer.valueOf(message.getContent()); - if (this.leaderID != newLeaderID) { - System.out.println("Instance " + localID + " - Update leaderID"); - this.leaderID = newLeaderID; - messageManager.sendLeaderMessage(this.localID, newLeaderID); - } else { - System.out.println("Instance " + localID + " - Leader update done. Start heartbeat."); - messageManager.sendHeartbeatInvokeMessage(this.localID); - } + /** + * Process leader Message. The instance will set the leader ID to be the new one and send the message to + * the next instance until all the alive instance in the ring is informed. + */ + private void handleLeaderMessage(Message message) { + int newLeaderId = Integer.valueOf(message.getContent()); + if (this.leaderId != newLeaderId) { + System.out.println("Instance " + localId + " - Update leaderID"); + this.leaderId = newLeaderId; + messageManager.sendLeaderMessage(this.localId, newLeaderId); + } else { + System.out.println("Instance " + localId + " - Leader update done. Start heartbeat."); + messageManager.sendHeartbeatInvokeMessage(this.localId); } + } - /** - * Check whether the certain instnace is alive or not. - * @return {@code true} if the instance is alive - */ - @Override - public boolean isAlive() { - return alive; - } + /** + * Check whether the certain instnace is alive or not. + * @return {@code true} if the instance is alive + */ + @Override + public boolean isAlive() { + return alive; + } - /** - * Set the status of instance. - */ - @Override - public void setAlive(boolean alive) { - this.alive = alive; - } + /** + * Set the status of instance. + */ + @Override + public void setAlive(boolean alive) { + this.alive = alive; + } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java index 5d0a260615ff..24a425eaec06 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java @@ -31,29 +31,29 @@ */ public class RingMessage implements Message { - private MessageType type; - private String content; + private MessageType type; + private String content; - public RingMessage() {} + public RingMessage() {} - public RingMessage(MessageType type, String content) { - this.type = type; - this.content = content; - } + public RingMessage(MessageType type, String content) { + this.type = type; + this.content = content; + } - public MessageType getType() { - return type; - } + public MessageType getType() { + return type; + } - public void setType(MessageType type) { - this.type = type; - } + public void setType(MessageType type) { + this.type = type; + } - public String getContent() { - return content; - } + public String getContent() { + return content; + } - public void setContent(String content) { - this.content = content; - } + public void setContent(String content) { + this.content = content; + } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index b71df83f88ff..d8fc735fda79 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -32,91 +32,89 @@ import java.util.Map; import java.util.stream.Collectors; +/** + * Implementation of Ring message manager + */ public class RingMessageManager implements MessageManager { - private Map instanceMap; + private Map instanceMap; - /** - * Constructor of RingMessageManager. - */ - public RingMessageManager() {} - - public RingMessageManager(Map instanceMap) { - this.instanceMap = instanceMap; - } + /** + * Constructor of RingMessageManager. + */ + public RingMessageManager(Map instanceMap) { + this.instanceMap = instanceMap; + } - /** - * Send heartbeat message to current leader instance to check the health. - * @param leaderID - * @return {@code true} if the leader is alive. - */ - @Override - public boolean sendHeartbeatMessage(int leaderID) { - Instance leaderInstance = instanceMap.get(leaderID); - boolean alive = leaderInstance.isAlive(); - return alive; - } + /** + * Send heartbeat message to current leader instance to check the health. + * @param leaderId leaderID + * @return {@code true} if the leader is alive. + */ + @Override + public boolean sendHeartbeatMessage(int leaderId) { + Instance leaderInstance = instanceMap.get(leaderId); + boolean alive = leaderInstance.isAlive(); + return alive; + } - /** - * Send election message to the next instance. - * @param currentID - * @param content list contains all the IDs of instances which have received this election message. - */ - @Override - public void sendElectionMessage(int currentID, String content) { - Instance nextInstance = this.findNextInstance(currentID); - Message electionMessage = new RingMessage(MessageType.ELECTION, content); - nextInstance.onMessage(electionMessage); - } + /** + * Send election message to the next instance. + * @param currentId currentID + * @param content list contains all the IDs of instances which have received this election message. + */ + @Override + public void sendElectionMessage(int currentId, String content) { + Instance nextInstance = this.findNextInstance(currentId); + Message electionMessage = new RingMessage(MessageType.ELECTION, content); + nextInstance.onMessage(electionMessage); + } - /** - * Send leader message to the next instance. - * @param currentID - * @param leaderID - */ - @Override - public void sendLeaderMessage(int currentID, int leaderID) { - Instance nextInstance = this.findNextInstance(currentID); - Message leaderMessage = new RingMessage(MessageType.LEADER, String.valueOf(leaderID)); - nextInstance.onMessage(leaderMessage); - } + /** + * Send leader message to the next instance. + */ + @Override + public void sendLeaderMessage(int currentId, int leaderId) { + Instance nextInstance = this.findNextInstance(currentId); + Message leaderMessage = new RingMessage(MessageType.LEADER, String.valueOf(leaderId)); + nextInstance.onMessage(leaderMessage); + } - /** - * Send heartbeat invoke message to the next instance. - * @param currentID - */ - @Override - public void sendHeartbeatInvokeMessage(int currentID) { - Instance nextInstance = this.findNextInstance(currentID); - Message heartbeatInvokeMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); - nextInstance.onMessage(heartbeatInvokeMessage); - } + /** + * Send heartbeat invoke message to the next instance. + */ + @Override + public void sendHeartbeatInvokeMessage(int currentId) { + Instance nextInstance = this.findNextInstance(currentId); + Message heartbeatInvokeMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); + } - /** - * Find the next instance in the ring. - * @param currentID - * @return The next instance. - */ - private Instance findNextInstance(int currentID) { - Instance result = null; - List candidateSet = - instanceMap.keySet() + /** + * Find the next instance in the ring. + * @return The next instance. + */ + private Instance findNextInstance(int currentId) { + Instance result = null; + List candidateSet = + instanceMap.keySet() + .stream() + .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()); + if (candidateSet.isEmpty()) { + int index = + instanceMap.keySet() .stream() - .filter((i) -> i > currentID && instanceMap.get(i).isAlive()) + .filter((i) -> instanceMap.get(i).isAlive()) .sorted() - .collect(Collectors.toList()); - if (candidateSet.isEmpty()) { - int index = instanceMap.keySet() - .stream() - .filter((i) -> instanceMap.get(i).isAlive()) - .sorted() - .collect(Collectors.toList()) - .get(0); - result = instanceMap.get(index); - } else { - int index = candidateSet.get(0); - result = instanceMap.get(index); - } - return result; + .collect(Collectors.toList()) + .get(0); + result = instanceMap.get(index); + } else { + int index = candidateSet.get(0); + result = instanceMap.get(index); } + return result; + } } From 98fe8ac9247c93d947f3fcb065ec880a0e0c3da5 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Thu, 12 Sep 2019 15:15:57 +0800 Subject: [PATCH 13/19] Add Unit Tests --- .../leaderelection/ring/RingInstance.java | 4 +- .../leaderelection/ring/RingAppTest.java | 39 ++++++ .../leaderelection/ring/RingInstanceTest.java | 75 ++++++++++ .../ring/RingMessageManagerTest.java | 128 ++++++++++++++++++ .../leaderelection/ring/RingMessageTest.java | 62 +++++++++ 5 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index c4a92adc69d8..3c59aa319057 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -96,7 +96,7 @@ private void processMessage(Message message) { break; case HEARTBEAT_INVOKE: System.out.println("Instance " + localId + " - Heartbeat Message handling..."); - this.handleHeartbeatMessage(message); + this.handleHeartbeatInvodeMessage(); break; default: break; @@ -108,7 +108,7 @@ private void processMessage(Message message) { * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, * it will start the election process. */ - private void handleHeartbeatMessage(Message message) { + private void handleHeartbeatInvodeMessage() { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); if (isLeaderAlive) { System.out.println("Instance " + localId + "- Leader is alive."); diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java new file mode 100644 index 000000000000..ff271b4a8985 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import org.junit.jupiter.api.Test; + +/** + * RingApp Test + */ +public class RingAppTest { + + @Test + public void test() { + String[] args = {}; + RingApp.main(args); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java new file mode 100644 index 000000000000..ddd22e30bfeb --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -0,0 +1,75 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RingInstance Test + */ +public class RingInstanceTest { + + @Test + public void testOnMessage() { + try { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + ringInstance.onMessage(ringMessage); + Class ringInstanceClass = ringInstance.getClass(); + Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + assertEquals(ringMessage, ((Queue) messageQueueField.get(ringInstance)).poll()); + } catch (Exception e) { + fail("fail to access messasge queue."); + } + } + + @Test + public void testIsAlive() { + try { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + Class ringInstanceClass = ringInstance.getClass(); + Field aliveField = ringInstanceClass.getDeclaredField("alive"); + aliveField.setAccessible(true); + aliveField.set(ringInstance, false); + assertFalse(ringInstance.isAlive()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Fail to access field alive."); + } + } + + @Test + public void testSetAlive() { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + ringInstance.setAlive(false); + assertFalse(ringInstance.isAlive()); + } +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java new file mode 100644 index 000000000000..4deb304cdf25 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -0,0 +1,128 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RingMessageManager Test + */ +public class RingMessageManagerTest { + + @Test + public void testSendHeartbeatMessage() { + Instance instance1 = new RingInstance(null, 1, 1); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + MessageManager messageManager = new RingMessageManager(instanceMap); + assertTrue(messageManager.sendHeartbeatMessage(1)); + } + + @Test + public void testSendElectionMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + String messageContent = "2"; + messageManager.sendElectionMessage(2, messageContent); + Message ringMessage = new RingMessage(MessageType.ELECTION, messageContent); + Class instanceClass = instance3.getClass(); + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + + } + + @Test + public void testSendLeaderMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + String messageContent = "3"; + messageManager.sendLeaderMessage(2, 3); + Message ringMessage = new RingMessage(MessageType.LEADER, messageContent); + Class instanceClass = instance3.getClass(); + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + + @Test + public void testSendHeartbeatInvokeMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + messageManager.sendHeartbeatInvokeMessage(2); + Message ringMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); + Class instanceClass = instance3.getClass(); + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java new file mode 100644 index 000000000000..f1da148e45d7 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java @@ -0,0 +1,62 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * RingMessage Test + */ +public class RingMessageTest { + + @Test + public void testGetType() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + assertEquals(ringMessage.getType(), MessageType.HEARTBEAT); + } + + @Test + public void testSetType() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + ringMessage.setType(MessageType.ELECTION); + assertEquals(ringMessage.getType(), MessageType.ELECTION); + } + + @Test + public void testGetContent() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, "test"); + assertEquals(ringMessage.getContent(), "test"); + } + + @Test + public void testSetContent() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + ringMessage.setContent("test"); + assertEquals(ringMessage.getContent(), "test"); + } +} From ebb5bb71cb76f64b16afba1868a27bca1aabfc7d Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Thu, 12 Sep 2019 15:15:57 +0800 Subject: [PATCH 14/19] Add Unit Tests --- .../leaderelection/MessageManager.java | 25 +++- .../leaderelection/ring/RingInstance.java | 4 +- .../ring/RingMessageManager.java | 6 +- .../leaderelection/ring/RingAppTest.java | 39 ++++++ .../leaderelection/ring/RingInstanceTest.java | 75 ++++++++++ .../ring/RingMessageManagerTest.java | 128 ++++++++++++++++++ .../leaderelection/ring/RingMessageTest.java | 62 +++++++++ 7 files changed, 333 insertions(+), 6 deletions(-) create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java index b5c5244ac85c..56e78a00856a 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -28,12 +28,33 @@ */ public interface MessageManager { + /** + * Send heartbeat message to leader instance to check whether the leader instance is alive. + * @param leaderId Instance ID of leader instance. + * @return {@code true} if leader instance is alive, or {@code false} if not. + */ boolean sendHeartbeatMessage(int leaderId); - void sendElectionMessage(int currentId, String content); + /** + * Send election message to other instances. + * @param currentId Instance ID of which sends this message. + * @param content Election message content. + * @return {@code true} if the message is accepted by the target instances. + */ + boolean sendElectionMessage(int currentId, String content); - void sendLeaderMessage(int currentId, int leaderId); + /** + * Send new leader notification message to other instances. + * @param currentId Instance ID of which sends this message. + * @param leaderId Leader message content. + * @return {@code true} if the message is accepted by the target instances. + */ + boolean sendLeaderMessage(int currentId, int leaderId); + /** + * Send heartbeat invoke message. This will invoke heartbeat task in the target instance. + * @param currentId Instance ID of which sends this message. + */ void sendHeartbeatInvokeMessage(int currentId); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index c4a92adc69d8..3c59aa319057 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -96,7 +96,7 @@ private void processMessage(Message message) { break; case HEARTBEAT_INVOKE: System.out.println("Instance " + localId + " - Heartbeat Message handling..."); - this.handleHeartbeatMessage(message); + this.handleHeartbeatInvodeMessage(); break; default: break; @@ -108,7 +108,7 @@ private void processMessage(Message message) { * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, * it will start the election process. */ - private void handleHeartbeatMessage(Message message) { + private void handleHeartbeatInvodeMessage() { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); if (isLeaderAlive) { System.out.println("Instance " + localId + "- Leader is alive."); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index d8fc735fda79..014955adde88 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -64,20 +64,22 @@ public boolean sendHeartbeatMessage(int leaderId) { * @param content list contains all the IDs of instances which have received this election message. */ @Override - public void sendElectionMessage(int currentId, String content) { + public boolean sendElectionMessage(int currentId, String content) { Instance nextInstance = this.findNextInstance(currentId); Message electionMessage = new RingMessage(MessageType.ELECTION, content); nextInstance.onMessage(electionMessage); + return true; } /** * Send leader message to the next instance. */ @Override - public void sendLeaderMessage(int currentId, int leaderId) { + public boolean sendLeaderMessage(int currentId, int leaderId) { Instance nextInstance = this.findNextInstance(currentId); Message leaderMessage = new RingMessage(MessageType.LEADER, String.valueOf(leaderId)); nextInstance.onMessage(leaderMessage); + return true; } /** diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java new file mode 100644 index 000000000000..ff271b4a8985 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import org.junit.jupiter.api.Test; + +/** + * RingApp Test + */ +public class RingAppTest { + + @Test + public void test() { + String[] args = {}; + RingApp.main(args); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java new file mode 100644 index 000000000000..ddd22e30bfeb --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -0,0 +1,75 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RingInstance Test + */ +public class RingInstanceTest { + + @Test + public void testOnMessage() { + try { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + ringInstance.onMessage(ringMessage); + Class ringInstanceClass = ringInstance.getClass(); + Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + assertEquals(ringMessage, ((Queue) messageQueueField.get(ringInstance)).poll()); + } catch (Exception e) { + fail("fail to access messasge queue."); + } + } + + @Test + public void testIsAlive() { + try { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + Class ringInstanceClass = ringInstance.getClass(); + Field aliveField = ringInstanceClass.getDeclaredField("alive"); + aliveField.setAccessible(true); + aliveField.set(ringInstance, false); + assertFalse(ringInstance.isAlive()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Fail to access field alive."); + } + } + + @Test + public void testSetAlive() { + final RingInstance ringInstance = new RingInstance(null, 1, 1); + ringInstance.setAlive(false); + assertFalse(ringInstance.isAlive()); + } +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java new file mode 100644 index 000000000000..4deb304cdf25 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -0,0 +1,128 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * RingMessageManager Test + */ +public class RingMessageManagerTest { + + @Test + public void testSendHeartbeatMessage() { + Instance instance1 = new RingInstance(null, 1, 1); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + MessageManager messageManager = new RingMessageManager(instanceMap); + assertTrue(messageManager.sendHeartbeatMessage(1)); + } + + @Test + public void testSendElectionMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + String messageContent = "2"; + messageManager.sendElectionMessage(2, messageContent); + Message ringMessage = new RingMessage(MessageType.ELECTION, messageContent); + Class instanceClass = instance3.getClass(); + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + + } + + @Test + public void testSendLeaderMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + String messageContent = "3"; + messageManager.sendLeaderMessage(2, 3); + Message ringMessage = new RingMessage(MessageType.LEADER, messageContent); + Class instanceClass = instance3.getClass(); + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + + @Test + public void testSendHeartbeatInvokeMessage() { + try { + Instance instance1 = new RingInstance(null, 1, 1); + Instance instance2 = new RingInstance(null, 1, 2); + Instance instance3 = new RingInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new RingMessageManager(instanceMap); + messageManager.sendHeartbeatInvokeMessage(2); + Message ringMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); + Class instanceClass = instance3.getClass(); + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(ringMessageSent.getType(), ringMessage.getType()); + assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java new file mode 100644 index 000000000000..f1da148e45d7 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java @@ -0,0 +1,62 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.ring; + +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * RingMessage Test + */ +public class RingMessageTest { + + @Test + public void testGetType() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + assertEquals(ringMessage.getType(), MessageType.HEARTBEAT); + } + + @Test + public void testSetType() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + ringMessage.setType(MessageType.ELECTION); + assertEquals(ringMessage.getType(), MessageType.ELECTION); + } + + @Test + public void testGetContent() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, "test"); + assertEquals(ringMessage.getContent(), "test"); + } + + @Test + public void testSetContent() { + final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + ringMessage.setContent("test"); + assertEquals(ringMessage.getContent(), "test"); + } +} From ab1794b0e060d56d9a008dd1ff139148559149b5 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Mon, 16 Sep 2019 16:01:35 +0800 Subject: [PATCH 15/19] Add bully leader selection --- .../leaderelection/AbstractInstance.java | 129 ++++++++++++++++++ .../AbstractMessageManager.java | 63 +++++++++ .../iluwatar/leaderelection/MessageType.java | 2 + .../leaderelection/bully/BullyApp.java | 26 ++++ .../leaderelection/bully/BullyInstance.java | 93 +++++++++++++ .../leaderelection/bully/BullyMessage.java | 58 ++++++++ .../bully/BullyMessageManager.java | 79 +++++++++++ .../leaderelection/ring/RingInstance.java | 117 +++++----------- .../ring/RingMessageManager.java | 35 +---- .../leaderelection/ring/RingInstanceTest.java | 5 +- .../ring/RingMessageManagerTest.java | 11 +- 11 files changed, 493 insertions(+), 125 deletions(-) create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java create mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java new file mode 100644 index 000000000000..cd532a19548d --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -0,0 +1,129 @@ +package com.iluwatar.leaderelection; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +public abstract class AbstractInstance implements Instance, Runnable { + + protected MessageManager messageManager; + protected Queue messageQueue; + protected final int localId; + protected int leaderId; + protected boolean alive; + + /** + * Constructor of BullyInstance. + */ + public AbstractInstance(MessageManager messageManager, int localId, int leaderId) { + this.messageManager = messageManager; + this.messageQueue = new ConcurrentLinkedQueue<>(); + this.localId = localId; + this.leaderId = leaderId; + this.alive = true; + } + + /** + * The instance will execute the message in its message queue periodically once it is alive. + */ + @Override + public void run() { + while (true) { + if (!this.messageQueue.isEmpty()) { + this.processMessage(this.messageQueue.remove()); + } + System.out.flush(); + } + } + + /** + * Message listening method of the instance. + */ + @Override + public void onMessage(Message message) { + messageQueue.offer(message); + } + + /** + * Check whether the certain instnace is alive or not. + * @return {@code true} if the instance is alive + */ + @Override + public boolean isAlive() { + return alive; + } + + /** + * Set the status of instance. + */ + @Override + public void setAlive(boolean alive) { + this.alive = alive; + } + + private void processMessage(Message message) { + switch (message.getType()) { + case ELECTION: + System.out.println("Instance " + localId + " - Election Message handling..."); + handleElectionMessage(message); + break; + case LEADER: + System.out.println("Instance " + localId + " - Leader Message handling..."); + handleLeaderMessage(message); + break; + case HEARTBEAT: + System.out.println("Instance " + localId + " - Heartbeat Message handling..."); + handleHeartbeatMessage(message); + break; + case ELECTION_INVODE: + System.out.println("Instance " + localId + " - Election Invoke Message handling..."); + handleElectionInvokeMessage(); + break; + case LEADER_INVOKE: + System.out.println("Instance " + localId + " - Leader Invoke Message handling..."); + handleLeaderInvokeMessage(); + break; + case HEARTBEAT_INVOKE: + System.out.println("Instance " + localId + " - Heartbeat Invoke Message handling..."); + handleHeartbeatInvokeMessage(); + break; + default: + break; + } + } + + protected abstract void handleElectionMessage(Message message); + + protected abstract void handleElectionInvokeMessage(); + + protected abstract void handleLeaderMessage(Message message); + + protected abstract void handleLeaderInvokeMessage(); + + protected abstract void handleHeartbeatMessage(Message message); + + protected abstract void handleHeartbeatInvokeMessage(); + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java new file mode 100644 index 000000000000..85af76ea74c6 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java @@ -0,0 +1,63 @@ +package com.iluwatar.leaderelection; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +public abstract class AbstractMessageManager implements MessageManager { + + protected Map instanceMap; + + public AbstractMessageManager(Map instanceMap) { + this.instanceMap = instanceMap; + } + + /** + * Find the next instance with smallest ID. + * @return The next instance. + */ + protected Instance findNextInstance(int currentId) { + Instance result = null; + List candidateList = instanceMap.keySet() + .stream() + .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()); + if (candidateList.isEmpty()) { + int index = instanceMap.keySet() + .stream() + .filter((i) -> instanceMap.get(i).isAlive()) + .sorted() + .collect(Collectors.toList()) + .get(0); + result = instanceMap.get(index); + } else { + int index = candidateList.get(0); + result = instanceMap.get(index); + } + return result; + } + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java index 478fa6d9e7e5..91663142c011 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -31,6 +31,8 @@ public enum MessageType { ELECTION, LEADER, HEARTBEAT, + ELECTION_INVODE, + LEADER_INVOKE, HEARTBEAT_INVOKE } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java new file mode 100644 index 000000000000..c162bfa1b110 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -0,0 +1,26 @@ +package com.iluwatar.leaderelection.bully; + +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +public class BullyApp { +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java new file mode 100644 index 000000000000..5566500c7ba3 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -0,0 +1,93 @@ +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; + +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Impelemetation with bully algorithm. Each instance should have a sequential id and is able to + * communicate with other instances in the system. Initially the instance with smallest (or largest) + * ID is selected to be the leader. All the other instances send heartbeat message to leader periodically + * to check its health. If one certain instance finds the server done, it will send an election message + * to all the instances of which the ID is larger. If the target instance is alive, it will return an + * alive message (in this sample return true) and then send election message with its ID. If not, + * the original instance will send leader message to all the other instances. + */ +public class BullyInstance extends AbstractInstance { + + /** + * Constructor of BullyInstance. + */ + public BullyInstance(MessageManager messageManager, int localId, int leaderId) { + super(messageManager, localId, leaderId); + } + + @Override + protected void handleElectionMessage(Message message) {} + + @Override + protected void handleHeartbeatInvokeMessage() { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); + if (isLeaderAlive) { + System.out.println("Instance " + localId + "- Leader is alive."); + messageManager.sendHeartbeatInvokeMessage(localId); + } else { + System.out.println("Instance " + localId + "- Leader is not alive. Start election."); + boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + System.out.println("Instance " + localId + "- Succeed in election. Start leader notification."); + messageManager.sendLeaderMessage(localId, localId); + } + } + } + + @Override + protected void handleElectionInvokeMessage() { + System.out.println("Instance " + localId + "- Start election."); + boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + System.out.println("Instance " + localId + "- Succeed in election. Start leader notification."); + messageManager.sendLeaderMessage(localId, localId); + } + } + + @Override + protected void handleLeaderMessage(Message message) { + leaderId = Integer.valueOf(message.getContent()); + System.out.println("Instance " + localId + " - Leader update done."); + } + + @Override + protected void handleLeaderInvokeMessage() { + + } + + @Override + protected void handleHeartbeatMessage(Message message) { + + } + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java new file mode 100644 index 000000000000..d496698497fb --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java @@ -0,0 +1,58 @@ +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.MessageType; + +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * Message used by Bully type instance + */ +public class BullyMessage { + + private MessageType type; + private String content; + + public BullyMessage() {} + + public BullyMessage(MessageType type, String content) { + this.type = type; + this.content = content; + } + + public MessageType getType() { + return type; + } + + public void setType(MessageType type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java new file mode 100644 index 000000000000..54375e6532a0 --- /dev/null +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -0,0 +1,79 @@ +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractMessageManager; +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import com.iluwatar.leaderelection.ring.RingMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +public class BullyMessageManager extends AbstractMessageManager { + + public BullyMessageManager(Map instanceMap) { + super(instanceMap); + } + + @Override + public boolean sendHeartbeatMessage(int leaderId) { + Instance leaderInstance = instanceMap.get(leaderId); + boolean alive = leaderInstance.isAlive(); + return alive; + } + + @Override + public boolean sendElectionMessage(int currentId, String content) { + List candidateList = findElectionCandidateInstanceList(currentId); + if (candidateList.isEmpty()) { + return true; + } else { + Message electionMessage = new RingMessage(MessageType.ELECTION_INVODE, ""); + candidateList.stream() + .forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); + return false; + } + } + + @Override + public boolean sendLeaderMessage(int currentId, int leaderId) { + return false; + } + + @Override + public void sendHeartbeatInvokeMessage(int currentId) { + + } + + private List findElectionCandidateInstanceList(int currentId) { + return instanceMap.keySet() + .stream() + .filter((i) -> i < currentId && instanceMap.get(i).isAlive()) + .collect(Collectors.toList()); + } + +} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 3c59aa319057..b7ea332d26c5 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -23,18 +23,16 @@ package com.iluwatar.leaderelection.ring; -import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.AbstractInstance; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageManager; import java.util.Arrays; import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; /** - * Implementation with token ring alogirthm. The instances in the system are organized as a ring. + * Implementation with token ring algorithm. The instances in the system are organized as a ring. * Each instance should have a sequential id and the instance with smallest (or largest) id should * be the initial leader. All the other instances send heartbeat message to leader periodically * to check its health. If one certain instance finds the server done, it will send an election @@ -44,63 +42,13 @@ * with smallest ID to be the new leader, and then send a leader message to other instances to * inform the result. */ -public class RingInstance implements Instance, Runnable { - - private MessageManager messageManager; - private Queue messageQueue; - private final int localId; - private int leaderId; - private boolean alive; +public class RingInstance extends AbstractInstance { /** * Constructor of RingInstance. */ public RingInstance(MessageManager messageManager, int localId, int leaderId) { - this.messageManager = messageManager; - this.messageQueue = new ConcurrentLinkedQueue<>(); - this.localId = localId; - this.leaderId = leaderId; - this.alive = true; - } - - /** - * The instance will execute the message in its message queue periodically once it is alive. - */ - @Override - public void run() { - while (true) { - if (!messageQueue.isEmpty()) { - this.processMessage(messageQueue.remove()); - } - System.out.flush(); - } - } - - /** - * Message listening method of the instance. - */ - @Override - public void onMessage(Message message) { - messageQueue.offer(message); - } - - private void processMessage(Message message) { - switch (message.getType()) { - case ELECTION: - System.out.println("Instance " + localId + " - Election Message handling..."); - this.handleElectionMessage(message); - break; - case LEADER: - System.out.println("Instance " + localId + " - Leader Message handling..."); - this.handleLeaderMessage(message); - break; - case HEARTBEAT_INVOKE: - System.out.println("Instance " + localId + " - Heartbeat Message handling..."); - this.handleHeartbeatInvodeMessage(); - break; - default: - break; - } + super(messageManager, localId, leaderId); } /** @@ -108,14 +56,20 @@ private void processMessage(Message message) { * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, * it will start the election process. */ - private void handleHeartbeatInvodeMessage() { - boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); - if (isLeaderAlive) { - System.out.println("Instance " + localId + "- Leader is alive."); - messageManager.sendHeartbeatInvokeMessage(this.localId); - } else { - System.out.println("Instance " + localId + "- Leader is not alive. Start election."); - messageManager.sendElectionMessage(this.localId, String.valueOf(localId)); + @Override + protected void handleHeartbeatInvokeMessage() { + try { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); + if (isLeaderAlive) { + System.out.println("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second."); + Thread.sleep(5000); + messageManager.sendHeartbeatInvokeMessage(this.localId); + } else { + System.out.println("Instance " + localId + "- Leader is not alive. Start election."); + messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId)); + } + } catch (InterruptedException e) { + System.out.println("Instance " + localId + "- Interrupted."); } } @@ -124,7 +78,8 @@ private void handleHeartbeatInvodeMessage() { * alive instance with smallest ID to be the new leader, and send the leader inform message. If not, * it will add its local ID to the list and send the message to the next instance in the ring. */ - private void handleElectionMessage(Message message) { + @Override + protected void handleElectionMessage(Message message) { String content = message.getContent(); System.out.println("Instance " + localId + " - Election Message: " + content); List candidateList = @@ -132,13 +87,13 @@ private void handleElectionMessage(Message message) { .map(Integer::valueOf) .sorted() .collect(Collectors.toList()); - if (candidateList.contains(this.localId)) { + if (candidateList.contains(localId)) { int newLeaderId = candidateList.get(0); System.out.println("Instance " + localId + " - New leader should be " + newLeaderId + "."); - messageManager.sendLeaderMessage(this.localId, newLeaderId); + messageManager.sendLeaderMessage(localId, newLeaderId); } else { content += "," + localId; - messageManager.sendElectionMessage(this.localId, content); + messageManager.sendElectionMessage(localId, content); } } @@ -146,32 +101,26 @@ private void handleElectionMessage(Message message) { * Process leader Message. The instance will set the leader ID to be the new one and send the message to * the next instance until all the alive instance in the ring is informed. */ - private void handleLeaderMessage(Message message) { + @Override + protected void handleLeaderMessage(Message message) { int newLeaderId = Integer.valueOf(message.getContent()); if (this.leaderId != newLeaderId) { System.out.println("Instance " + localId + " - Update leaderID"); this.leaderId = newLeaderId; - messageManager.sendLeaderMessage(this.localId, newLeaderId); + messageManager.sendLeaderMessage(localId, newLeaderId); } else { System.out.println("Instance " + localId + " - Leader update done. Start heartbeat."); - messageManager.sendHeartbeatInvokeMessage(this.localId); + messageManager.sendHeartbeatInvokeMessage(localId); } } - /** - * Check whether the certain instnace is alive or not. - * @return {@code true} if the instance is alive - */ @Override - public boolean isAlive() { - return alive; - } + protected void handleLeaderInvokeMessage() {} - /** - * Set the status of instance. - */ @Override - public void setAlive(boolean alive) { - this.alive = alive; - } + protected void handleHeartbeatMessage(Message message) {} + + @Override + protected void handleElectionInvokeMessage() {} + } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 014955adde88..f758ab9a6833 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -23,9 +23,9 @@ package com.iluwatar.leaderelection.ring; +import com.iluwatar.leaderelection.AbstractMessageManager; import com.iluwatar.leaderelection.Instance; import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageManager; import com.iluwatar.leaderelection.MessageType; import java.util.List; @@ -35,15 +35,13 @@ /** * Implementation of Ring message manager */ -public class RingMessageManager implements MessageManager { - - private Map instanceMap; +public class RingMessageManager extends AbstractMessageManager { /** * Constructor of RingMessageManager. */ public RingMessageManager(Map instanceMap) { - this.instanceMap = instanceMap; + super(instanceMap); } /** @@ -92,31 +90,4 @@ public void sendHeartbeatInvokeMessage(int currentId) { nextInstance.onMessage(heartbeatInvokeMessage); } - /** - * Find the next instance in the ring. - * @return The next instance. - */ - private Instance findNextInstance(int currentId) { - Instance result = null; - List candidateSet = - instanceMap.keySet() - .stream() - .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) - .sorted() - .collect(Collectors.toList()); - if (candidateSet.isEmpty()) { - int index = - instanceMap.keySet() - .stream() - .filter((i) -> instanceMap.get(i).isAlive()) - .sorted() - .collect(Collectors.toList()) - .get(0); - result = instanceMap.get(index); - } else { - int index = candidateSet.get(0); - result = instanceMap.get(index); - } - return result; - } } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java index ddd22e30bfeb..8ba19f8db8e1 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -23,6 +23,7 @@ package com.iluwatar.leaderelection.ring; +import com.iluwatar.leaderelection.AbstractInstance; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageType; import org.junit.jupiter.api.Test; @@ -43,7 +44,7 @@ public void testOnMessage() { final RingInstance ringInstance = new RingInstance(null, 1, 1); RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); ringInstance.onMessage(ringMessage); - Class ringInstanceClass = ringInstance.getClass(); + Class ringInstanceClass = AbstractInstance.class; Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); assertEquals(ringMessage, ((Queue) messageQueueField.get(ringInstance)).poll()); @@ -56,7 +57,7 @@ public void testOnMessage() { public void testIsAlive() { try { final RingInstance ringInstance = new RingInstance(null, 1, 1); - Class ringInstanceClass = ringInstance.getClass(); + Class ringInstanceClass = AbstractInstance.class; Field aliveField = ringInstanceClass.getDeclaredField("alive"); aliveField.setAccessible(true); aliveField.set(ringInstance, false); diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java index 4deb304cdf25..b6c220ce5f79 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -23,10 +23,7 @@ package com.iluwatar.leaderelection.ring; -import com.iluwatar.leaderelection.Instance; -import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageManager; -import com.iluwatar.leaderelection.MessageType; +import com.iluwatar.leaderelection.*; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; @@ -64,7 +61,7 @@ public void testSendElectionMessage() { String messageContent = "2"; messageManager.sendElectionMessage(2, messageContent); Message ringMessage = new RingMessage(MessageType.ELECTION, messageContent); - Class instanceClass = instance3.getClass(); + Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); @@ -90,7 +87,7 @@ public void testSendLeaderMessage() { String messageContent = "3"; messageManager.sendLeaderMessage(2, 3); Message ringMessage = new RingMessage(MessageType.LEADER, messageContent); - Class instanceClass = instance3.getClass(); + Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); @@ -114,7 +111,7 @@ public void testSendHeartbeatInvokeMessage() { MessageManager messageManager = new RingMessageManager(instanceMap); messageManager.sendHeartbeatInvokeMessage(2); Message ringMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); - Class instanceClass = instance3.getClass(); + Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); From fec0739a1ffe05dbaf45c6ff720391c45534a511 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Mon, 16 Sep 2019 16:26:09 +0800 Subject: [PATCH 16/19] Change System.out to log print. Add MIT license in each file. --- .../leaderelection/AbstractInstance.java | 28 +++++---- .../AbstractMessageManager.java | 13 ++-- .../com/iluwatar/leaderelection/Message.java | 30 ++++++++-- .../leaderelection/bully/BullyApp.java | 5 +- .../leaderelection/bully/BullyInstance.java | 30 ++++++---- .../leaderelection/bully/BullyMessage.java | 58 ------------------ .../bully/BullyMessageManager.java | 27 ++++----- .../iluwatar/leaderelection/ring/RingApp.java | 3 +- .../leaderelection/ring/RingInstance.java | 18 +++--- .../leaderelection/ring/RingMessage.java | 59 ------------------- .../ring/RingMessageManager.java | 8 +-- 11 files changed, 98 insertions(+), 181 deletions(-) delete mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java delete mode 100644 leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java index cd532a19548d..7e3b3349110e 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -1,8 +1,3 @@ -package com.iluwatar.leaderelection; - -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - /** * The MIT License * Copyright (c) 2014-2016 Ilkka Seppälä @@ -26,8 +21,18 @@ * THE SOFTWARE. */ +package com.iluwatar.leaderelection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + public abstract class AbstractInstance implements Instance, Runnable { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInstance.class); + protected MessageManager messageManager; protected Queue messageQueue; protected final int localId; @@ -54,7 +59,6 @@ public void run() { if (!this.messageQueue.isEmpty()) { this.processMessage(this.messageQueue.remove()); } - System.out.flush(); } } @@ -86,27 +90,27 @@ public void setAlive(boolean alive) { private void processMessage(Message message) { switch (message.getType()) { case ELECTION: - System.out.println("Instance " + localId + " - Election Message handling..."); + LOGGER.info("Instance " + localId + " - Election Message handling..."); handleElectionMessage(message); break; case LEADER: - System.out.println("Instance " + localId + " - Leader Message handling..."); + LOGGER.info("Instance " + localId + " - Leader Message handling..."); handleLeaderMessage(message); break; case HEARTBEAT: - System.out.println("Instance " + localId + " - Heartbeat Message handling..."); + LOGGER.info("Instance " + localId + " - Heartbeat Message handling..."); handleHeartbeatMessage(message); break; case ELECTION_INVODE: - System.out.println("Instance " + localId + " - Election Invoke Message handling..."); + LOGGER.info("Instance " + localId + " - Election Invoke Message handling..."); handleElectionInvokeMessage(); break; case LEADER_INVOKE: - System.out.println("Instance " + localId + " - Leader Invoke Message handling..."); + LOGGER.info("Instance " + localId + " - Leader Invoke Message handling..."); handleLeaderInvokeMessage(); break; case HEARTBEAT_INVOKE: - System.out.println("Instance " + localId + " - Heartbeat Invoke Message handling..."); + LOGGER.info("Instance " + localId + " - Heartbeat Invoke Message handling..."); handleHeartbeatInvokeMessage(); break; default: diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java index 85af76ea74c6..76975f2ce028 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java @@ -1,9 +1,3 @@ -package com.iluwatar.leaderelection; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - /** * The MIT License * Copyright (c) 2014-2016 Ilkka Seppälä @@ -26,6 +20,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + +package com.iluwatar.leaderelection; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public abstract class AbstractMessageManager implements MessageManager { protected Map instanceMap; diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index adecc3e20467..25b0b6ff97fb 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -24,12 +24,34 @@ package com.iluwatar.leaderelection; /** - * Message interface + * Message used to transport data between instances. */ -public interface Message { +public class Message { - MessageType getType(); + private MessageType type; - String getContent(); + private String content; + public Message() {} + + public Message(MessageType type, String content) { + this.type = type; + this.content = content; + } + + public MessageType getType() { + return type; + } + + public void setType(MessageType type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java index c162bfa1b110..0f76c574ad18 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -1,5 +1,3 @@ -package com.iluwatar.leaderelection.bully; - /** * The MIT License * Copyright (c) 2014-2016 Ilkka Seppälä @@ -22,5 +20,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + +package com.iluwatar.leaderelection.bully; + public class BullyApp { } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java index 5566500c7ba3..cb458c3213df 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -1,9 +1,3 @@ -package com.iluwatar.leaderelection.bully; - -import com.iluwatar.leaderelection.AbstractInstance; -import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageManager; - /** * The MIT License * Copyright (c) 2014-2016 Ilkka Seppälä @@ -27,6 +21,14 @@ * THE SOFTWARE. */ +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Impelemetation with bully algorithm. Each instance should have a sequential id and is able to * communicate with other instances in the system. Initially the instance with smallest (or largest) @@ -35,9 +37,11 @@ * to all the instances of which the ID is larger. If the target instance is alive, it will return an * alive message (in this sample return true) and then send election message with its ID. If not, * the original instance will send leader message to all the other instances. - */ + */ public class BullyInstance extends AbstractInstance { + private static final Logger LOGGER = LoggerFactory.getLogger(BullyInstance.class); + /** * Constructor of BullyInstance. */ @@ -52,13 +56,13 @@ protected void handleElectionMessage(Message message) {} protected void handleHeartbeatInvokeMessage() { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); if (isLeaderAlive) { - System.out.println("Instance " + localId + "- Leader is alive."); + LOGGER.info("Instance " + localId + "- Leader is alive."); messageManager.sendHeartbeatInvokeMessage(localId); } else { - System.out.println("Instance " + localId + "- Leader is not alive. Start election."); + LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); if (electionResult) { - System.out.println("Instance " + localId + "- Succeed in election. Start leader notification."); + LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); messageManager.sendLeaderMessage(localId, localId); } } @@ -66,10 +70,10 @@ protected void handleHeartbeatInvokeMessage() { @Override protected void handleElectionInvokeMessage() { - System.out.println("Instance " + localId + "- Start election."); + LOGGER.info("Instance " + localId + "- Start election."); boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); if (electionResult) { - System.out.println("Instance " + localId + "- Succeed in election. Start leader notification."); + LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); messageManager.sendLeaderMessage(localId, localId); } } @@ -77,7 +81,7 @@ protected void handleElectionInvokeMessage() { @Override protected void handleLeaderMessage(Message message) { leaderId = Integer.valueOf(message.getContent()); - System.out.println("Instance " + localId + " - Leader update done."); + LOGGER.info("Instance " + localId + " - Leader update done."); } @Override diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java deleted file mode 100644 index d496698497fb..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessage.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.iluwatar.leaderelection.bully; - -import com.iluwatar.leaderelection.MessageType; - -/** - * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Message used by Bully type instance - */ -public class BullyMessage { - - private MessageType type; - private String content; - - public BullyMessage() {} - - public BullyMessage(MessageType type, String content) { - this.type = type; - this.content = content; - } - - public MessageType getType() { - return type; - } - - public void setType(MessageType type) { - this.type = type; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } -} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java index 54375e6532a0..2222ef4d3552 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -1,16 +1,3 @@ -package com.iluwatar.leaderelection.bully; - -import com.iluwatar.leaderelection.AbstractMessageManager; -import com.iluwatar.leaderelection.Instance; -import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageType; -import com.iluwatar.leaderelection.ring.RingMessage; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - /** * The MIT License * Copyright (c) 2014-2016 Ilkka Seppälä @@ -33,6 +20,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractMessageManager; +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public class BullyMessageManager extends AbstractMessageManager { public BullyMessageManager(Map instanceMap) { @@ -52,7 +51,7 @@ public boolean sendElectionMessage(int currentId, String content) { if (candidateList.isEmpty()) { return true; } else { - Message electionMessage = new RingMessage(MessageType.ELECTION_INVODE, ""); + Message electionMessage = new Message(MessageType.ELECTION_INVODE, ""); candidateList.stream() .forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); return false; diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java index 8c6737d978be..ade5bb70dc29 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -24,6 +24,7 @@ package com.iluwatar.leaderelection.ring; import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageManager; import com.iluwatar.leaderelection.MessageType; @@ -57,7 +58,7 @@ public static void main(String[] args) { instanceMap.put(4, instance4); instanceMap.put(5, instance5); - instance2.onMessage(new RingMessage(MessageType.HEARTBEAT_INVOKE, "")); + instance2.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); Thread thread1 = new Thread(instance1); Thread thread2 = new Thread(instance2); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index b7ea332d26c5..8028114aba95 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -26,6 +26,8 @@ import com.iluwatar.leaderelection.AbstractInstance; import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.List; @@ -44,6 +46,8 @@ */ public class RingInstance extends AbstractInstance { + private static final Logger LOGGER = LoggerFactory.getLogger(RingInstance.class); + /** * Constructor of RingInstance. */ @@ -61,15 +65,15 @@ protected void handleHeartbeatInvokeMessage() { try { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); if (isLeaderAlive) { - System.out.println("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second."); + LOGGER.info("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second."); Thread.sleep(5000); messageManager.sendHeartbeatInvokeMessage(this.localId); } else { - System.out.println("Instance " + localId + "- Leader is not alive. Start election."); + LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); messageManager.sendElectionMessage(this.localId, String.valueOf(this.localId)); } } catch (InterruptedException e) { - System.out.println("Instance " + localId + "- Interrupted."); + LOGGER.info("Instance " + localId + "- Interrupted."); } } @@ -81,7 +85,7 @@ protected void handleHeartbeatInvokeMessage() { @Override protected void handleElectionMessage(Message message) { String content = message.getContent(); - System.out.println("Instance " + localId + " - Election Message: " + content); + LOGGER.info("Instance " + localId + " - Election Message: " + content); List candidateList = Arrays.stream(content.trim().split(",")) .map(Integer::valueOf) @@ -89,7 +93,7 @@ protected void handleElectionMessage(Message message) { .collect(Collectors.toList()); if (candidateList.contains(localId)) { int newLeaderId = candidateList.get(0); - System.out.println("Instance " + localId + " - New leader should be " + newLeaderId + "."); + LOGGER.info("Instance " + localId + " - New leader should be " + newLeaderId + "."); messageManager.sendLeaderMessage(localId, newLeaderId); } else { content += "," + localId; @@ -105,11 +109,11 @@ protected void handleElectionMessage(Message message) { protected void handleLeaderMessage(Message message) { int newLeaderId = Integer.valueOf(message.getContent()); if (this.leaderId != newLeaderId) { - System.out.println("Instance " + localId + " - Update leaderID"); + LOGGER.info("Instance " + localId + " - Update leaderID"); this.leaderId = newLeaderId; messageManager.sendLeaderMessage(localId, newLeaderId); } else { - System.out.println("Instance " + localId + " - Leader update done. Start heartbeat."); + LOGGER.info("Instance " + localId + " - Leader update done. Start heartbeat."); messageManager.sendHeartbeatInvokeMessage(localId); } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java deleted file mode 100644 index 24a425eaec06..000000000000 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessage.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2014-2016 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.iluwatar.leaderelection.ring; - -import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageType; - -/** - * Message used by Ring type instance - */ -public class RingMessage implements Message { - - private MessageType type; - private String content; - - public RingMessage() {} - - public RingMessage(MessageType type, String content) { - this.type = type; - this.content = content; - } - - public MessageType getType() { - return type; - } - - public void setType(MessageType type) { - this.type = type; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } -} diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index f758ab9a6833..2f26492801dd 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -28,9 +28,7 @@ import com.iluwatar.leaderelection.Message; import com.iluwatar.leaderelection.MessageType; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * Implementation of Ring message manager @@ -64,7 +62,7 @@ public boolean sendHeartbeatMessage(int leaderId) { @Override public boolean sendElectionMessage(int currentId, String content) { Instance nextInstance = this.findNextInstance(currentId); - Message electionMessage = new RingMessage(MessageType.ELECTION, content); + Message electionMessage = new Message(MessageType.ELECTION, content); nextInstance.onMessage(electionMessage); return true; } @@ -75,7 +73,7 @@ public boolean sendElectionMessage(int currentId, String content) { @Override public boolean sendLeaderMessage(int currentId, int leaderId) { Instance nextInstance = this.findNextInstance(currentId); - Message leaderMessage = new RingMessage(MessageType.LEADER, String.valueOf(leaderId)); + Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); nextInstance.onMessage(leaderMessage); return true; } @@ -86,7 +84,7 @@ public boolean sendLeaderMessage(int currentId, int leaderId) { @Override public void sendHeartbeatInvokeMessage(int currentId) { Instance nextInstance = this.findNextInstance(currentId); - Message heartbeatInvokeMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); + Message heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); nextInstance.onMessage(heartbeatInvokeMessage); } From 57c1adc7dfc9d7256d98e26d7e71261cd37f541b Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Mon, 16 Sep 2019 17:16:36 +0800 Subject: [PATCH 17/19] Add More java doc comments --- .../leaderelection/AbstractInstance.java | 23 +++++++++++---- .../AbstractMessageManager.java | 9 ++++++ .../com/iluwatar/leaderelection/Instance.java | 12 ++++++++ .../iluwatar/leaderelection/MessageType.java | 25 ++++++++++++++++- .../leaderelection/bully/BullyApp.java | 3 ++ .../leaderelection/bully/BullyInstance.java | 11 +++----- .../bully/BullyMessageManager.java | 28 +++++++++++++++++-- .../ring/RingMessageManager.java | 7 ++++- .../leaderelection/ring/RingInstanceTest.java | 2 +- .../ring/RingMessageManagerTest.java | 6 ++-- .../leaderelection/ring/RingMessageTest.java | 8 +++--- 11 files changed, 109 insertions(+), 25 deletions(-) diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java index 7e3b3349110e..a77b8374b5e0 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -29,6 +29,9 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +/** + * Abstract class of all the instance implementation classes. + */ public abstract class AbstractInstance implements Instance, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInstance.class); @@ -63,7 +66,8 @@ public void run() { } /** - * Message listening method of the instance. + * Once messages are sent to the certain instance, it will firstly be added to the queue and wait to be executed. + * @param message Message sent by other instances */ @Override public void onMessage(Message message) { @@ -71,8 +75,8 @@ public void onMessage(Message message) { } /** - * Check whether the certain instnace is alive or not. - * @return {@code true} if the instance is alive + * Check if the instance is alive or not. + * @return {@code true} if the instance is alive. */ @Override public boolean isAlive() { @@ -80,13 +84,18 @@ public boolean isAlive() { } /** - * Set the status of instance. + * Set the health status of the certain instance. + * @param alive {@code true} for alive. */ @Override public void setAlive(boolean alive) { this.alive = alive; } + /** + * Process the message according to its type. + * @param message Message polled from queue. + */ private void processMessage(Message message) { switch (message.getType()) { case ELECTION: @@ -101,7 +110,7 @@ private void processMessage(Message message) { LOGGER.info("Instance " + localId + " - Heartbeat Message handling..."); handleHeartbeatMessage(message); break; - case ELECTION_INVODE: + case ELECTION_INVOKE: LOGGER.info("Instance " + localId + " - Election Invoke Message handling..."); handleElectionInvokeMessage(); break; @@ -118,6 +127,10 @@ private void processMessage(Message message) { } } + /** + * Abstract methods to handle different types of message. These methods need to be implemented in concrete instance + * class to implement corresponding leader-selection pattern. + */ protected abstract void handleElectionMessage(Message message); protected abstract void handleElectionInvokeMessage(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java index 76975f2ce028..4384da25f79d 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java @@ -27,10 +27,19 @@ import java.util.Map; import java.util.stream.Collectors; +/** + * Abstract class of all the message manager classes. + */ public abstract class AbstractMessageManager implements MessageManager { + /** + * Contain all the instances in the system. Key is its ID, and value is the instance itself. + */ protected Map instanceMap; + /** + * Construtor of AbstractMessageManager + */ public AbstractMessageManager(Map instanceMap) { this.instanceMap = instanceMap; } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java index 78efd8a62a2b..abaa62791185 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -28,10 +28,22 @@ */ public interface Instance { + /** + * Check if the instance is alive or not. + * @return {@code true} if the instance is alive. + */ boolean isAlive(); + /** + * Set the health status of the certain instance. + * @param alive {@code true} for alive. + */ void setAlive(boolean alive); + /** + * Consume messages from other instances. + * @param message Message sent by other instances + */ void onMessage(Message message); } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java index 91663142c011..17f658ec4244 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -28,11 +28,34 @@ */ public enum MessageType { + /** + * Start the election. The content of the message stores ID(s) of the candidate instance(s). + */ ELECTION, + + /** + * Nodify the new leader. The content of the message should be the leader ID. + */ LEADER, + + /** + * Check health of current leader instance. + */ HEARTBEAT, - ELECTION_INVODE, + + /** + * Inform target instance to start election. + */ + ELECTION_INVOKE, + + /** + * Inform target instance to notify all the other instance that it is the new leader. + */ LEADER_INVOKE, + + /** + * Inform target instance to start heartbeat. + */ HEARTBEAT_INVOKE } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java index 0f76c574ad18..04985c6312d2 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -23,5 +23,8 @@ package com.iluwatar.leaderelection.bully; +/** + * + */ public class BullyApp { } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java index cb458c3213df..dee7da0702a8 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -49,9 +49,6 @@ public BullyInstance(MessageManager messageManager, int localId, int leaderId) { super(messageManager, localId, leaderId); } - @Override - protected void handleElectionMessage(Message message) {} - @Override protected void handleHeartbeatInvokeMessage() { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); @@ -85,13 +82,13 @@ protected void handleLeaderMessage(Message message) { } @Override - protected void handleLeaderInvokeMessage() { + protected void handleLeaderInvokeMessage() {} - } + @Override + protected void handleHeartbeatMessage(Message message) {} @Override - protected void handleHeartbeatMessage(Message message) { + protected void handleElectionMessage(Message message) {} - } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java index 2222ef4d3552..21d01796d178 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -32,12 +32,23 @@ import java.util.Map; import java.util.stream.Collectors; +/** + * Implementation of BullyMessageManager + */ public class BullyMessageManager extends AbstractMessageManager { + /** + * Constructor of BullyMessageManager. + */ public BullyMessageManager(Map instanceMap) { super(instanceMap); } + /** + * Send heartbeat message to current leader instance to check the health. + * @param leaderId leaderID + * @return {@code true} if the leader is alive. + */ @Override public boolean sendHeartbeatMessage(int leaderId) { Instance leaderInstance = instanceMap.get(leaderId); @@ -45,13 +56,19 @@ public boolean sendHeartbeatMessage(int leaderId) { return alive; } + /** + * Send election message to all the instances with smaller ID. + * @param currentId Instance ID of which sends this message. + * @param content Election message content. + * @return {@code true} if no alive instance has smaller ID, so that the election is accepted. + */ @Override public boolean sendElectionMessage(int currentId, String content) { List candidateList = findElectionCandidateInstanceList(currentId); if (candidateList.isEmpty()) { return true; } else { - Message electionMessage = new Message(MessageType.ELECTION_INVODE, ""); + Message electionMessage = new Message(MessageType.ELECTION_INVOKE, ""); candidateList.stream() .forEach((i) -> instanceMap.get(i).onMessage(electionMessage)); return false; @@ -60,12 +77,17 @@ public boolean sendElectionMessage(int currentId, String content) { @Override public boolean sendLeaderMessage(int currentId, int leaderId) { - return false; + Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); + instanceMap.keySet() + .stream() + .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); } @Override public void sendHeartbeatInvokeMessage(int currentId) { - + Instance nextInstance = this.findNextInstance(currentId); + Message heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); + nextInstance.onMessage(heartbeatInvokeMessage); } private List findElectionCandidateInstanceList(int currentId) { diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 2f26492801dd..257a9f764d97 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -31,7 +31,7 @@ import java.util.Map; /** - * Implementation of Ring message manager + * Implementation of RingMessageManager */ public class RingMessageManager extends AbstractMessageManager { @@ -58,6 +58,7 @@ public boolean sendHeartbeatMessage(int leaderId) { * Send election message to the next instance. * @param currentId currentID * @param content list contains all the IDs of instances which have received this election message. + * @return {@code true} if the election message is accepted by the target instance. */ @Override public boolean sendElectionMessage(int currentId, String content) { @@ -69,6 +70,9 @@ public boolean sendElectionMessage(int currentId, String content) { /** * Send leader message to the next instance. + * @param currentId Instance ID of which sends this message. + * @param leaderId Leader message content. + * @return {@code true} if the leader message is accepted by the target instance. */ @Override public boolean sendLeaderMessage(int currentId, int leaderId) { @@ -80,6 +84,7 @@ public boolean sendLeaderMessage(int currentId, int leaderId) { /** * Send heartbeat invoke message to the next instance. + * @param currentId Instance ID of which sends this message. */ @Override public void sendHeartbeatInvokeMessage(int currentId) { diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java index 8ba19f8db8e1..f56920e2bec1 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -42,7 +42,7 @@ public class RingInstanceTest { public void testOnMessage() { try { final RingInstance ringInstance = new RingInstance(null, 1, 1); - RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + Message ringMessage = new Message(MessageType.HEARTBEAT, ""); ringInstance.onMessage(ringMessage); Class ringInstanceClass = AbstractInstance.class; Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue"); diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java index b6c220ce5f79..3b34b65fab71 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -60,7 +60,7 @@ public void testSendElectionMessage() { MessageManager messageManager = new RingMessageManager(instanceMap); String messageContent = "2"; messageManager.sendElectionMessage(2, messageContent); - Message ringMessage = new RingMessage(MessageType.ELECTION, messageContent); + Message ringMessage = new Message(MessageType.ELECTION, messageContent); Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); @@ -86,7 +86,7 @@ public void testSendLeaderMessage() { MessageManager messageManager = new RingMessageManager(instanceMap); String messageContent = "3"; messageManager.sendLeaderMessage(2, 3); - Message ringMessage = new RingMessage(MessageType.LEADER, messageContent); + Message ringMessage = new Message(MessageType.LEADER, messageContent); Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); @@ -110,7 +110,7 @@ public void testSendHeartbeatInvokeMessage() { instanceMap.put(3, instance3); MessageManager messageManager = new RingMessageManager(instanceMap); messageManager.sendHeartbeatInvokeMessage(2); - Message ringMessage = new RingMessage(MessageType.HEARTBEAT_INVOKE, ""); + Message ringMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java index f1da148e45d7..8dd20eccdcc4 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java @@ -36,26 +36,26 @@ public class RingMessageTest { @Test public void testGetType() { - final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + final Message ringMessage = new Message(MessageType.HEARTBEAT, ""); assertEquals(ringMessage.getType(), MessageType.HEARTBEAT); } @Test public void testSetType() { - final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + final Message ringMessage = new Message(MessageType.HEARTBEAT, ""); ringMessage.setType(MessageType.ELECTION); assertEquals(ringMessage.getType(), MessageType.ELECTION); } @Test public void testGetContent() { - final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, "test"); + final Message ringMessage = new Message(MessageType.HEARTBEAT, "test"); assertEquals(ringMessage.getContent(), "test"); } @Test public void testSetContent() { - final RingMessage ringMessage = new RingMessage(MessageType.HEARTBEAT, ""); + final Message ringMessage = new Message(MessageType.HEARTBEAT, ""); ringMessage.setContent("test"); assertEquals(ringMessage.getContent(), "test"); } From d598aa0320c3e074a68f31982c1e8f4ed13e0e31 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Tue, 17 Sep 2019 11:44:15 +0800 Subject: [PATCH 18/19] Add unit test --- .../leaderelection/AbstractInstance.java | 2 + .../com/iluwatar/leaderelection/Message.java | 19 ++++ .../leaderelection/bully/BullyApp.java | 49 +++++++++- .../leaderelection/bully/BullyInstance.java | 61 +++++++++---- .../bully/BullyMessageManager.java | 17 ++++ .../leaderelection/ring/RingInstance.java | 5 +- .../RingMessageTest.java => MessageTest.java} | 30 ++----- .../leaderelection/bully/BullyAppTest.java | 39 ++++++++ .../bully/BullyMessageManagerTest.java | 89 +++++++++++++++++++ .../bully/BullyinstanceTest.java | 78 ++++++++++++++++ .../leaderelection/ring/RingAppTest.java | 2 +- .../leaderelection/ring/RingInstanceTest.java | 4 +- .../ring/RingMessageManagerTest.java | 5 +- 13 files changed, 353 insertions(+), 47 deletions(-) rename leader-election/src/test/java/com/iluwatar/leaderelection/{ring/RingMessageTest.java => MessageTest.java} (60%) create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java create mode 100644 leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java index a77b8374b5e0..5f153870a300 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -36,6 +36,8 @@ public abstract class AbstractInstance implements Instance, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractInstance.class); + protected static final int HEARTBEAT_INTERVAL = 5000; + protected MessageManager messageManager; protected Queue messageQueue; protected final int localId; diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index 25b0b6ff97fb..7ac79e4bcd46 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -23,6 +23,8 @@ package com.iluwatar.leaderelection; +import java.util.Objects; + /** * Message used to transport data between instances. */ @@ -54,4 +56,21 @@ public String getContent() { public void setContent(String content) { this.content = content; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Message message = (Message) o; + return type == message.type && Objects.equals(content, message.content); + } + + @Override + public int hashCode() { + return Objects.hash(type, content); + } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java index 04985c6312d2..7355b34456a9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -23,8 +23,55 @@ package com.iluwatar.leaderelection.bully; +import com.iluwatar.leaderelection.Instance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageManager; +import com.iluwatar.leaderelection.MessageType; + +import java.util.HashMap; +import java.util.Map; + /** - * + * Example of how to use bully leader election. Initially 5 instances is created in the clould + * system, and the instance with ID 1 is set as leader. After the system is started stop the + * leader instance, and the new leader will be elected. */ public class BullyApp { + + /** + * Program entry point + */ + public static void main(String[] args) { + + Map instanceMap = new HashMap<>(); + MessageManager messageManager = new BullyMessageManager(instanceMap); + + BullyInstance instance1 = new BullyInstance(messageManager, 1, 1); + BullyInstance instance2 = new BullyInstance(messageManager, 2, 1); + BullyInstance instance3 = new BullyInstance(messageManager, 3, 1); + BullyInstance instance4 = new BullyInstance(messageManager, 4, 1); + BullyInstance instance5 = new BullyInstance(messageManager, 5, 1); + + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instanceMap.put(5, instance5); + + instance4.onMessage(new Message(MessageType.HEARTBEAT_INVOKE, "")); + + Thread thread1 = new Thread(instance1); + Thread thread2 = new Thread(instance2); + Thread thread3 = new Thread(instance3); + Thread thread4 = new Thread(instance4); + Thread thread5 = new Thread(instance5); + + thread1.start(); + thread2.start(); + thread3.start(); + thread4.start(); + thread5.start(); + + instance1.setAlive(false); + } } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java index dee7da0702a8..70a9c60f341a 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -49,38 +49,67 @@ public BullyInstance(MessageManager messageManager, int localId, int leaderId) { super(messageManager, localId, leaderId); } + /** + * Process the heartbeat invoke message. After receiving the message, the instance will send a heartbeat + * to leader to check its health. If alive, it will inform the next instance to do the heartbeat. If not, + * it will start the election process. + */ @Override protected void handleHeartbeatInvokeMessage() { - boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); - if (isLeaderAlive) { - LOGGER.info("Instance " + localId + "- Leader is alive."); - messageManager.sendHeartbeatInvokeMessage(localId); - } else { - LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); - boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); - if (electionResult) { - LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); - messageManager.sendLeaderMessage(localId, localId); + try { + boolean isLeaderAlive = messageManager.sendHeartbeatMessage(leaderId); + if (isLeaderAlive) { + LOGGER.info("Instance " + localId + "- Leader is alive."); + Thread.sleep(HEARTBEAT_INTERVAL); + messageManager.sendHeartbeatInvokeMessage(localId); + } else { + LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); + boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); + messageManager.sendLeaderMessage(localId, localId); + } } + } catch (InterruptedException e) { + LOGGER.info("Instance " + localId + "- Interrupted."); } } + /** + * Process election invoke message. Send election message to all the instances with smaller ID. If any + * one of them is alive, do nothing. If no instance alive, send leader message to all the alive instance + * and restart heartbeat. + */ @Override protected void handleElectionInvokeMessage() { - LOGGER.info("Instance " + localId + "- Start election."); - boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); - if (electionResult) { - LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); - messageManager.sendLeaderMessage(localId, localId); + if (!isLeader()) { + LOGGER.info("Instance " + localId + "- Start election."); + boolean electionResult = messageManager.sendElectionMessage(localId, String.valueOf(localId)); + if (electionResult) { + LOGGER.info("Instance " + localId + "- Succeed in election. Start leader notification."); + leaderId = localId; + messageManager.sendLeaderMessage(localId, localId); + messageManager.sendHeartbeatInvokeMessage(localId); + } } } + /** + * Process leader message. Update local leader information. + */ @Override protected void handleLeaderMessage(Message message) { leaderId = Integer.valueOf(message.getContent()); LOGGER.info("Instance " + localId + " - Leader update done."); } + private boolean isLeader() { + return localId == leaderId; + } + + /** + * Not used in Bully instance. + */ @Override protected void handleLeaderInvokeMessage() {} @@ -89,6 +118,4 @@ protected void handleHeartbeatMessage(Message message) {} @Override protected void handleElectionMessage(Message message) {} - - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java index 21d01796d178..3fcadefba2de 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -75,14 +75,26 @@ public boolean sendElectionMessage(int currentId, String content) { } } + /** + * Send leader message to all the instances to notify the new leader. + * @param currentId Instance ID of which sends this message. + * @param leaderId Leader message content. + * @return {@code true} if the message is accepted. + */ @Override public boolean sendLeaderMessage(int currentId, int leaderId) { Message leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); instanceMap.keySet() .stream() + .filter((i) -> i != currentId) .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); + return false; } + /** + * Send heartbeat invoke message to the next instance. + * @param currentId Instance ID of which sends this message. + */ @Override public void sendHeartbeatInvokeMessage(int currentId) { Instance nextInstance = this.findNextInstance(currentId); @@ -90,6 +102,11 @@ public void sendHeartbeatInvokeMessage(int currentId) { nextInstance.onMessage(heartbeatInvokeMessage); } + /** + * Find all the alive instances with smaller ID than current instance. + * @param currentId ID of current instance. + * @return ID list of all the candidate instance. + */ private List findElectionCandidateInstanceList(int currentId) { return instanceMap.keySet() .stream() diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index 8028114aba95..e3472e4b4669 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -66,7 +66,7 @@ protected void handleHeartbeatInvokeMessage() { boolean isLeaderAlive = messageManager.sendHeartbeatMessage(this.leaderId); if (isLeaderAlive) { LOGGER.info("Instance " + localId + "- Leader is alive. Start next heartbeat in 5 second."); - Thread.sleep(5000); + Thread.sleep(HEARTBEAT_INTERVAL); messageManager.sendHeartbeatInvokeMessage(this.localId); } else { LOGGER.info("Instance " + localId + "- Leader is not alive. Start election."); @@ -118,6 +118,9 @@ protected void handleLeaderMessage(Message message) { } } + /** + * Not used in Ring instance. + */ @Override protected void handleLeaderInvokeMessage() {} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java similarity index 60% rename from leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java rename to leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java index 8dd20eccdcc4..3cd5b93d9fdd 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java @@ -21,42 +21,28 @@ * THE SOFTWARE. */ -package com.iluwatar.leaderelection.ring; +package com.iluwatar.leaderelection; -import com.iluwatar.leaderelection.Message; -import com.iluwatar.leaderelection.MessageType; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; /** - * RingMessage Test + * Message test case. */ -public class RingMessageTest { +public class MessageTest { @Test public void testGetType() { - final Message ringMessage = new Message(MessageType.HEARTBEAT, ""); - assertEquals(ringMessage.getType(), MessageType.HEARTBEAT); - } - - @Test - public void testSetType() { - final Message ringMessage = new Message(MessageType.HEARTBEAT, ""); - ringMessage.setType(MessageType.ELECTION); - assertEquals(ringMessage.getType(), MessageType.ELECTION); + Message message = new Message(MessageType.HEARTBEAT, ""); + assertEquals(MessageType.HEARTBEAT, message.getType()); } @Test public void testGetContent() { - final Message ringMessage = new Message(MessageType.HEARTBEAT, "test"); - assertEquals(ringMessage.getContent(), "test"); + String content = "test"; + Message message = new Message(MessageType.HEARTBEAT, content); + assertEquals(content, message.getContent()); } - @Test - public void testSetContent() { - final Message ringMessage = new Message(MessageType.HEARTBEAT, ""); - ringMessage.setContent("test"); - assertEquals(ringMessage.getContent(), "test"); - } } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java new file mode 100644 index 000000000000..b4b527d5a813 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java @@ -0,0 +1,39 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import org.junit.jupiter.api.Test; + +/** + * BullyApp unit test. + */ +public class BullyAppTest { + + @Test + public void test() { + String[] args = {}; + BullyApp.main(args); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java new file mode 100644 index 000000000000..d4dc6e2105c4 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java @@ -0,0 +1,89 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.*; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * BullyMessageManager unit test. + */ +public class BullyMessageManagerTest { + + @Test + public void testSendHeartbeatMessage() { + Instance instance1 = new BullyInstance(null, 1, 1); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + MessageManager messageManager = new BullyMessageManager(instanceMap); + assertTrue(messageManager.sendHeartbeatMessage(1)); + } + + @Test + public void testSendElectionMessageNotAccepted() { + try { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Instance instance4 = new BullyInstance(null, 1, 4); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instance1.setAlive(false); + MessageManager messageManager = new BullyMessageManager(instanceMap); + messageManager.sendElectionMessage(3, "3"); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message message2 = ((Queue) messageQueueField.get(instance2)).poll(); + int instance4QueueSize = ((Queue) messageQueueField.get(instance4)).size(); + Message expectedMessage = new Message(MessageType.ELECTION_INVOKE, ""); + assertEquals(message2, expectedMessage); + assertEquals(instance4QueueSize, 0); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("Error to access private field."); + } + } + + @Test + public void testSendLeaderMessage() { + + } + + @Test + public void testSendHeartbeatInvokeMessage() { + + } + + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java new file mode 100644 index 000000000000..7581a8af1247 --- /dev/null +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java @@ -0,0 +1,78 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 Ilkka Seppälä + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.leaderelection.bully; + +import com.iluwatar.leaderelection.AbstractInstance; +import com.iluwatar.leaderelection.Message; +import com.iluwatar.leaderelection.MessageType; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * BullyInstance unit test. + */ +public class BullyinstanceTest { + + @Test + public void testOnMessage() { + try { + final BullyInstance bullyInstance = new BullyInstance(null, 1, 1); + Message bullyMessage = new Message(MessageType.HEARTBEAT, ""); + bullyInstance.onMessage(bullyMessage); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + assertEquals(bullyMessage, ((Queue) messageQueueField.get(bullyInstance)).poll()); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("fail to access messasge queue."); + } + + } + + @Test + public void testIsAlive() { + try { + final BullyInstance bullyInstance = new BullyInstance(null, 1, 1); + Class instanceClass = AbstractInstance.class; + Field aliveField = instanceClass.getDeclaredField("alive"); + aliveField.setAccessible(true); + aliveField.set(bullyInstance, false); + assertFalse(bullyInstance.isAlive()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Fail to access field alive."); + } + } + + @Test + public void testSetAlive() { + final BullyInstance bullyInstance = new BullyInstance(null, 1, 1); + bullyInstance.setAlive(false); + assertFalse(bullyInstance.isAlive()); + } + +} diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java index ff271b4a8985..c6f50bc799fd 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; /** - * RingApp Test + * RingApp unit test. */ public class RingAppTest { diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java index f56920e2bec1..65c3916cc535 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -34,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.*; /** - * RingInstance Test + * RingInstance unit test. */ public class RingInstanceTest { @@ -48,7 +48,7 @@ public void testOnMessage() { Field messageQueueField = ringInstanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); assertEquals(ringMessage, ((Queue) messageQueueField.get(ringInstance)).poll()); - } catch (Exception e) { + } catch (IllegalAccessException | NoSuchFieldException e) { fail("fail to access messasge queue."); } } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java index 3b34b65fab71..3c60399dc7fc 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -34,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.*; /** - * RingMessageManager Test + * RingMessageManager unit test. */ public class RingMessageManagerTest { @@ -91,8 +91,7 @@ public void testSendLeaderMessage() { Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); Message ringMessageSent = ((Queue) messageQueueField.get(instance3)).poll(); - assertEquals(ringMessageSent.getType(), ringMessage.getType()); - assertEquals(ringMessageSent.getContent(), ringMessage.getContent()); + assertEquals(ringMessageSent, ringMessage); } catch (NoSuchFieldException | IllegalAccessException e) { fail("Error to access private field."); } From 1d70124d8dbc587f90115c6f974ff95486b06eb2 Mon Sep 17 00:00:00 2001 From: Azureyjt Date: Wed, 18 Sep 2019 09:30:36 +0800 Subject: [PATCH 19/19] Add unit tests --- .../bully/BullyMessageManagerTest.java | 68 ++++++++++++++++++- .../ring/RingMessageManagerTest.java | 1 - 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java index d4dc6e2105c4..3a8e35b54abe 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java @@ -24,6 +24,8 @@ package com.iluwatar.leaderelection.bully; import com.iluwatar.leaderelection.*; +import com.iluwatar.leaderelection.ring.RingInstance; +import com.iluwatar.leaderelection.ring.RingMessageManager; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; @@ -61,7 +63,7 @@ public void testSendElectionMessageNotAccepted() { instanceMap.put(4, instance4); instance1.setAlive(false); MessageManager messageManager = new BullyMessageManager(instanceMap); - messageManager.sendElectionMessage(3, "3"); + boolean result = messageManager.sendElectionMessage(3, "3"); Class instanceClass = AbstractInstance.class; Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); messageQueueField.setAccessible(true); @@ -70,19 +72,79 @@ public void testSendElectionMessageNotAccepted() { Message expectedMessage = new Message(MessageType.ELECTION_INVOKE, ""); assertEquals(message2, expectedMessage); assertEquals(instance4QueueSize, 0); + assertEquals(result, false); } catch (IllegalAccessException | NoSuchFieldException e) { fail("Error to access private field."); } } @Test - public void testSendLeaderMessage() { + public void testElectionMessageAccepted() { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Instance instance4 = new BullyInstance(null, 1, 4); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instance1.setAlive(false); + MessageManager messageManager = new BullyMessageManager(instanceMap); + boolean result = messageManager.sendElectionMessage(2, "2"); + assertEquals(result, true); + } + @Test + public void testSendLeaderMessage() { + try { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Instance instance4 = new BullyInstance(null, 1, 4); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + instanceMap.put(4, instance4); + instance1.setAlive(false); + MessageManager messageManager = new BullyMessageManager(instanceMap); + messageManager.sendLeaderMessage(2, 2); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message message3 = ((Queue) messageQueueField.get(instance3)).poll(); + Message message4 = ((Queue) messageQueueField.get(instance4)).poll(); + Message expectedMessage = new Message(MessageType.LEADER, "2"); + assertEquals(message3, expectedMessage); + assertEquals(message4, expectedMessage); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("Error to access private field."); + } } @Test public void testSendHeartbeatInvokeMessage() { - + try { + Instance instance1 = new BullyInstance(null, 1, 1); + Instance instance2 = new BullyInstance(null, 1, 2); + Instance instance3 = new BullyInstance(null, 1, 3); + Map instanceMap = new HashMap<>(); + instanceMap.put(1, instance1); + instanceMap.put(2, instance2); + instanceMap.put(3, instance3); + MessageManager messageManager = new BullyMessageManager(instanceMap); + messageManager.sendHeartbeatInvokeMessage(2); + Message message = new Message(MessageType.HEARTBEAT_INVOKE, ""); + Class instanceClass = AbstractInstance.class; + Field messageQueueField = instanceClass.getDeclaredField("messageQueue"); + messageQueueField.setAccessible(true); + Message messageSent = ((Queue) messageQueueField.get(instance3)).poll(); + assertEquals(messageSent.getType(), message.getType()); + assertEquals(messageSent.getContent(), message.getContent()); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Error to access private field."); + } } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java index 3c60399dc7fc..64d5af69f5bb 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -70,7 +70,6 @@ public void testSendElectionMessage() { } catch (NoSuchFieldException | IllegalAccessException e) { fail("Error to access private field."); } - } @Test