diff options
| -rw-r--r-- | src/main/java/protocols/implementations/VSRaftProtocol.java | 11 | ||||
| -rw-r--r-- | src/test/java/protocols/implementations/VSRaftProtocolTest.java | 38 |
2 files changed, 40 insertions, 9 deletions
diff --git a/src/main/java/protocols/implementations/VSRaftProtocol.java b/src/main/java/protocols/implementations/VSRaftProtocol.java index eaf63a7..6257538 100644 --- a/src/main/java/protocols/implementations/VSRaftProtocol.java +++ b/src/main/java/protocols/implementations/VSRaftProtocol.java @@ -33,6 +33,9 @@ public class VSRaftProtocol extends VSAbstractProtocol { /** The local time when the last heartbeat was observed. */ private long lastHeartbeatTime; + /** The randomized local deadline for the next election timeout. */ + private long electionDeadline; + /** PIDs which still have to acknowledge the current operation. */ private ArrayList<Integer> ackPids; @@ -105,9 +108,9 @@ public class VSRaftProtocol extends VSAbstractProtocol { * @see protocols.VSAbstractProtocol#onClientSchedule() */ public void onClientSchedule() { - long elapsedSinceHeartbeat = process.getTime() - lastHeartbeatTime; + long currentTime = process.getTime(); - if (!isLeader && elapsedSinceHeartbeat >= getLong("electionTimeout")) { + if (!isLeader && currentTime >= electionDeadline) { startElection(); } } @@ -137,6 +140,7 @@ public class VSRaftProtocol extends VSAbstractProtocol { isLeader = false; isCandidate = false; lastHeartbeatTime = 0; + electionDeadline = 0; logIndex = 0; commitIndex = 0; @@ -183,10 +187,11 @@ public class VSRaftProtocol extends VSAbstractProtocol { long jitterPercentage = Math.abs(process.getRandomPercentage()); long jitter = (getLong("electionJitter") * jitterPercentage) / 100L; boolean previousContextIsServer = currentContextIsServer(); + electionDeadline = process.getTime() + getLong("electionTimeout") + jitter; currentContextIsServer(false); removeSchedules(); - scheduleAt(process.getTime() + getLong("electionTimeout") + jitter); + scheduleAt(electionDeadline); currentContextIsServer(previousContextIsServer); } diff --git a/src/test/java/protocols/implementations/VSRaftProtocolTest.java b/src/test/java/protocols/implementations/VSRaftProtocolTest.java index 0ae52ba..8028711 100644 --- a/src/test/java/protocols/implementations/VSRaftProtocolTest.java +++ b/src/test/java/protocols/implementations/VSRaftProtocolTest.java @@ -120,7 +120,7 @@ class VSRaftProtocolTest { } @Test - void testOnClientInitSchedulesRandomizedElectionTimeout() { + void testOnClientInitSchedulesRandomizedElectionTimeout() throws Exception { protocol.currentContextIsServer(false); ArgumentCaptor<VSTask> taskCaptor = ArgumentCaptor.forClass(VSTask.class); @@ -132,6 +132,7 @@ class VSRaftProtocolTest { VSTask task = taskCaptor.getValue(); assertEquals(4600L, task.getTaskTime()); + assertEquals(4600L, getLongField("electionDeadline")); assertFalse(((VSProtocolScheduleEvent) task.getEvent()).isServerSchedule()); } @@ -140,7 +141,7 @@ class VSRaftProtocolTest { protocol.currentContextIsServer(false); protocol.onClientInit(); clearInvocations(mockProcess, mockTaskManager); - when(mockProcess.getTime()).thenReturn(4200L); + when(mockProcess.getTime()).thenReturn(4700L, 4700L, 4700L); ArgumentCaptor<VSMessage> messageCaptor = ArgumentCaptor.forClass(VSMessage.class); @@ -160,7 +161,8 @@ class VSRaftProtocolTest { assertFalse(getBooleanField("isLeader")); assertEquals(1, getIntField("votesReceived")); assertEquals(7, getIntField("votedFor")); - assertEquals(8700L, taskCaptor.getValue().getTaskTime()); + assertEquals(9200L, taskCaptor.getValue().getTaskTime()); + assertEquals(9200L, getLongField("electionDeadline")); assertFalse( ((VSProtocolScheduleEvent) taskCaptor.getValue().getEvent()) .isServerSchedule()); @@ -186,14 +188,31 @@ class VSRaftProtocolTest { } @Test + void testOnClientScheduleDoesNotStartElectionInJitterWindow() throws Exception { + protocol.currentContextIsServer(false); + protocol.onClientInit(); + clearInvocations(mockProcess, mockTaskManager); + when(mockProcess.getTime()).thenReturn(4500L); + + protocol.onClientSchedule(); + + verify(mockProcess, never()).sendMessage(any()); + verify(mockTaskManager, never()).removeAllTasks(any()); + verify(mockTaskManager, never()).addTask(any()); + assertFalse(getBooleanField("isCandidate")); + assertEquals(0, getIntField("currentTerm")); + assertEquals(4600L, getLongField("electionDeadline")); + } + + @Test void testCandidateTimeoutStartsNewElectionAndReschedules() throws Exception { protocol.currentContextIsServer(false); protocol.onClientInit(); - when(mockProcess.getTime()).thenReturn(4200L, 4200L, 4200L); + when(mockProcess.getTime()).thenReturn(4700L, 4700L, 4700L); protocol.onClientSchedule(); clearInvocations(mockProcess, mockTaskManager); - when(mockProcess.getTime()).thenReturn(8401L, 8401L, 8401L); + when(mockProcess.getTime()).thenReturn(9401L, 9401L, 9401L); ArgumentCaptor<VSMessage> messageCaptor = ArgumentCaptor.forClass(VSMessage.class); @@ -210,7 +229,8 @@ class VSRaftProtocolTest { assertEquals(2, voteRequest.getInteger("term")); assertEquals(2, getIntField("currentTerm")); assertEquals(1, getIntField("votesReceived")); - assertEquals(12901L, taskCaptor.getValue().getTaskTime()); + assertEquals(13901L, taskCaptor.getValue().getTaskTime()); + assertEquals(13901L, getLongField("electionDeadline")); } @Test @@ -261,4 +281,10 @@ class VSRaftProtocolTest { field.setAccessible(true); return field.getBoolean(protocol); } + + private long getLongField(String fieldName) throws Exception { + Field field = VSRaftProtocol.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field.getLong(protocol); + } } |
