diff --git a/openraft/src/membership/membership.rs b/openraft/src/membership/membership.rs index 65c8479e5..2d62fdd79 100644 --- a/openraft/src/membership/membership.rs +++ b/openraft/src/membership/membership.rs @@ -306,7 +306,7 @@ where ) -> Result> { tracing::debug!(change = debug(&change), "{}", func_name!()); - let last = self.get_joint_config().last().unwrap().clone(); + let last = self.get_joint_config().last().cloned().unwrap_or_default(); let new_membership = match change { ChangeMembers::AddVoterIds(add_voter_ids) => { diff --git a/openraft/src/quorum/coherent_impl.rs b/openraft/src/quorum/coherent_impl.rs index 3964aacb9..51ac7ec73 100644 --- a/openraft/src/quorum/coherent_impl.rs +++ b/openraft/src/quorum/coherent_impl.rs @@ -69,7 +69,12 @@ where if self.is_coherent_with(&other) { Joint::from(vec![other]) } else { - Joint::from(vec![self.children().last().unwrap().clone(), other]) + let last = self.children().last(); + if let Some(last) = last { + Joint::from(vec![last.clone(), other]) + } else { + Joint::from(vec![other]) + } } } } diff --git a/tests/tests/membership/main.rs b/tests/tests/membership/main.rs index 09f04eca4..2f491cd99 100644 --- a/tests/tests/membership/main.rs +++ b/tests/tests/membership/main.rs @@ -19,6 +19,7 @@ mod t31_add_remove_follower; mod t31_remove_leader; mod t31_removed_follower; mod t51_remove_unreachable_follower; +mod t52_change_membership_on_uninitialized_node; mod t99_issue_471_adding_learner_uses_uninit_leader_id; mod t99_issue_584_replication_state_reverted; mod t99_new_leader_auto_commit_uniform_config; diff --git a/tests/tests/membership/t52_change_membership_on_uninitialized_node.rs b/tests/tests/membership/t52_change_membership_on_uninitialized_node.rs new file mode 100644 index 000000000..4be118d84 --- /dev/null +++ b/tests/tests/membership/t52_change_membership_on_uninitialized_node.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use anyhow::Result; +use maplit::btreemap; +use openraft::ChangeMembers; +use openraft::Config; + +use crate::fixtures::init_default_ut_tracing; +use crate::fixtures::RaftRouter; + +/// Call `Raft::change_membership()` on an uninitialized node should not panic due to empty +/// membership. +#[async_entry::test(worker_threads = 8, init = "init_default_ut_tracing()", tracing_span = "debug")] +async fn change_membership_on_uninitialized_node() -> Result<()> { + let config = Arc::new( + Config { + enable_heartbeat: false, + ..Default::default() + } + .validate()?, + ); + + let mut router = RaftRouter::new(config.clone()); + router.new_raft_node(0).await; + + let n0 = router.get_raft_handle(&0)?; + let res = n0.change_membership(ChangeMembers::AddVoters(btreemap! {0=>()}), false).await; + tracing::info!("{:?}", res); + + let err = res.unwrap_err(); + tracing::info!("{}", err); + + assert!(err.to_string().contains("forward request to")); + + Ok(()) +}