Future<Option<uint64_t> > CoordinatorProcess::checkPromisePhase( const PromiseResponse& response) { if (!response.okay()) { // Lost an election, but can be retried. We save the proposal // number here so that most likely we will have a high enough // proposal number when we retry. CHECK_LE(proposal, response.proposal()); proposal = response.proposal(); return None(); } else { CHECK(response.has_position()); index = response.position(); // Need to "catch-up" local replica (i.e., fill in any unlearned // and/or missing positions) so that we can do local reads. // Usually we could do this lazily, however, a local learned // position might have been truncated, so we actually need to // catch-up the local replica all the way to the end of the log // before we can perform any up-to-date local reads. return getMissingPositions() .then(defer(self(), &Self::catchupMissingPositions, lambda::_1)) .then(defer(self(), &Self::updateIndexAfterElected)); } }
TEST(ReplicaTest, Promise) { const std::string path = utils::os::getcwd() + "/.log"; utils::os::rmdir(path); Replica replica(path); PromiseRequest request; PromiseResponse response; Future<PromiseResponse> future; request.set_id(2); future = protocol::promise(replica.pid(), request); future.await(2.0); ASSERT_TRUE(future.isReady()); response = future.get(); EXPECT_TRUE(response.okay()); EXPECT_EQ(2, response.id()); EXPECT_TRUE(response.has_position()); EXPECT_EQ(0, response.position()); EXPECT_FALSE(response.has_action()); request.set_id(1); future = protocol::promise(replica.pid(), request); future.await(2.0); ASSERT_TRUE(future.isReady()); response = future.get(); EXPECT_FALSE(response.okay()); EXPECT_EQ(1, response.id()); EXPECT_FALSE(response.has_position()); EXPECT_FALSE(response.has_action()); request.set_id(3); future = protocol::promise(replica.pid(), request); future.await(2.0); ASSERT_TRUE(future.isReady()); response = future.get(); EXPECT_TRUE(response.okay()); EXPECT_EQ(3, response.id()); EXPECT_TRUE(response.has_position()); EXPECT_EQ(0, response.position()); EXPECT_FALSE(response.has_action()); utils::os::rmdir(path); }
void received(const PromiseResponse& response) { if (response.has_type() && response.type() == PromiseResponse::IGNORED) { ignoresReceived++; // A quorum of replicas have ignored the request. if (ignoresReceived >= quorum) { LOG(INFO) << "Aborting explicit promise request because " << ignoresReceived << " ignores received"; // If the "type" is PromiseResponse::IGNORED, the rest of the // fields don't matter. PromiseResponse result; result.set_type(PromiseResponse::IGNORED); promise.set(result); terminate(self()); } return; } responsesReceived++; if (isRejectedPromise(response)) { // Failed to get the promise from a replica for this position // because it has been promised to a proposer with a higher // proposal number. The 'proposal' field in the response // specifies the proposal number. It is found to be larger than // the proposal number used in this phase. if (highestNackProposal.isNone() || highestNackProposal.get() < response.proposal()) { highestNackProposal = response.proposal(); } } else if (highestNackProposal.isSome()) { // We still want to wait for more potential NACK responses so we // can return the highest proposal number seen but we don't care // about any more ACK responses. } else { // The position has been promised to us so the 'proposal' field // should match the proposal we sent in the request. CHECK_EQ(response.proposal(), request.proposal()); if (response.has_action()) { CHECK_EQ(response.action().position(), position); if (response.action().has_learned() && response.action().learned()) { // Received a learned action. Note that there is no checking // that we get the _same_ learned action in the event we get // multiple responses with learned actions, we just take the // "first". In fact, there is a specific instance in which // learned actions will NOT be the same! In this instance, // one replica may return that the action is a learned no-op // because it knows the position has been truncated while // another replica (that hasn't learned the truncation yet) // might return the actual action at this position. Picking // either action is _correct_, since eventually we know this // position will be truncated. Fun! // TODO(neilc): Create a test case for this scenario. promise.set(response); // The remaining responses will be discarded in 'finalize'. terminate(self()); return; } else if (response.action().has_performed()) { // An action has already been performed in this position, we // need to save the action with the highest proposal number. if (highestAckAction.isNone() || (highestAckAction->performed() < response.action().performed())) { highestAckAction = response.action(); } } else { // Received a response for a position that had previously // been promised to some other proposer but an action had // not been performed or learned. The position is now // promised to us. No need to do anything here. } } else { // Received a response without an action associated with it. // This is the case when this proposer is the first to request // a promise for this log position. CHECK(response.has_position()); CHECK_EQ(response.position(), position); } } if (responsesReceived >= quorum) { // A quorum of replicas have replied. PromiseResponse result; if (highestNackProposal.isSome()) { result.set_type(PromiseResponse::REJECT); result.set_okay(false); result.set_proposal(highestNackProposal.get()); } else { result.set_type(PromiseResponse::ACCEPT); result.set_okay(true); if (highestAckAction.isSome()) { result.mutable_action()->CopyFrom(highestAckAction.get()); } } promise.set(result); terminate(self()); } }
TEST_F(ReplicaTest, Promise) { const string path = os::getcwd() + "/.log"; initializer.flags.path = path; initializer.execute(); Replica replica(path); PromiseRequest request; PromiseResponse response; Future<PromiseResponse> future; request.set_proposal(2); future = protocol::promise(replica.pid(), request); AWAIT_READY(future); response = future.get(); EXPECT_TRUE(response.okay()); EXPECT_EQ(2u, response.proposal()); EXPECT_TRUE(response.has_position()); EXPECT_EQ(0u, response.position()); EXPECT_FALSE(response.has_action()); request.set_proposal(1); future = protocol::promise(replica.pid(), request); AWAIT_READY(future); response = future.get(); EXPECT_FALSE(response.okay()); EXPECT_EQ(2u, response.proposal()); // Highest proposal seen so far. EXPECT_FALSE(response.has_position()); EXPECT_FALSE(response.has_action()); request.set_proposal(3); future = protocol::promise(replica.pid(), request); AWAIT_READY(future); response = future.get(); EXPECT_TRUE(response.okay()); EXPECT_EQ(3u, response.proposal()); EXPECT_TRUE(response.has_position()); EXPECT_EQ(0u, response.position()); EXPECT_FALSE(response.has_action()); }