From a4894371bbd77e080e8358768230444ba3f48a17 Mon Sep 17 00:00:00 2001 From: Free Ekanayaka Date: Wed, 8 Aug 2018 11:42:57 +0200 Subject: [PATCH] Only send "heartbeat" AppendEntries requests during idle periods Reset the request_timeout timer when an "actual" AppendEntries request is received from the user, since that effectively already counts as "heartbeat" from the follower's perspective. --- src/raft_server.c | 9 ++++++- tests/test_server.c | 61 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/raft_server.c b/src/raft_server.c index c9e9c9a6..0a0f8d4c 100644 --- a/src/raft_server.c +++ b/src/raft_server.c @@ -61,7 +61,7 @@ void raft_randomize_election_timeout(raft_server_t* me_) { raft_server_private_t* me = (raft_server_private_t*)me_; - /* [election_timeout, 2 * election_timeout) */ + /* [election_timeout, 2 * election_timeout) (ยง9.3) */ me->election_timeout_rand = me->election_timeout + rand() % me->election_timeout; __log(me_, NULL, "randomize election timeout to %d", me->election_timeout_rand); } @@ -743,6 +743,13 @@ int raft_recv_entry(raft_server_t* me_, if (0 != e) return e; + /* Reset the heartbeat timer: for a full request_timeout period we'll be + * good and we won't need to contact followers again, since this was not an + * idle period ("Upon election: send initial empty AppendEntries RPCs + * (heartbeat) to each server; repeat during idle periods to prevent + * election timeouts"). */ + me->timeout_elapsed = 0; + for (i = 0; i < me->num_nodes; i++) { raft_node_t* node = me->nodes[i]; diff --git a/tests/test_server.c b/tests/test_server.c index 25d8dd59..31aee501 100644 --- a/tests/test_server.c +++ b/tests/test_server.c @@ -3843,6 +3843,67 @@ void TestRaft_leader_sends_empty_appendentries_every_request_timeout( CuAssertTrue(tc, NULL != ae); } +void TestRaft_leader_does_not_send_empty_appendentries_if_last_appendentries_is_recent( + CuTest * tc) +{ + raft_cbs_t funcs = { + .send_appendentries = sender_appendentries, + .log = NULL + }; + + void *sender = sender_new(NULL); + void *r = raft_new(); + raft_set_callbacks(r, &funcs, sender); + raft_add_node(r, NULL, 1, 1); + raft_add_node(r, NULL, 2, 0); + raft_add_node(r, NULL, 3, 0); + raft_set_election_timeout(r, 1000); + raft_set_request_timeout(r, 500); + CuAssertTrue(tc, 0 == raft_get_timeout_elapsed(r)); + + /* candidate to leader */ + raft_set_state(r, RAFT_STATE_CANDIDATE); + raft_become_leader(r); + + /* receive initial empty appendentries messages for both nodes */ + msg_appendentries_t* ae; + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL != ae); + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL != ae); + + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL == ae); + + /* let some time elapse, but not enough to trigger the request timeout */ + raft_periodic(r, 250); + + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL == ae); + + /* receive an entry from the user and send appendentries requests to both + * follower nodes */ + msg_entry_t mety = {}; + mety.id = 1; + mety.data.buf = "entry"; + mety.data.len = strlen("entry"); + + msg_entry_response_t cr; + raft_recv_entry(r, &mety, &cr); + + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL != ae); + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL != ae); + + /* let some more time elapse, this time it's past the initial request + * timeout, but the timer has been reset so we don't send an empty + * appendentries */ + raft_periodic(r, 251); + ae = sender_poll_msg_data(sender); + CuAssertTrue(tc, NULL == ae); +} + /* TODO: If a server receives a request with a stale term number, it rejects the request. */ #if 0 void T_estRaft_leader_sends_appendentries_when_receive_entry_msg(CuTest * tc)