1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! Trigger an action to RaftCore by an external caller.
use openraft_macros::since;
use crate::RaftTypeConfig;
use crate::core::raft_msg::external_command::ExternalCommand;
use crate::errors::AllowNextRevertError;
use crate::errors::Fatal;
use crate::raft::RaftInner;
use crate::type_config::TypeConfigExt;
use crate::type_config::alias::LogIdOf;
use crate::type_config::alias::VoteOf;
/// Trigger is an interface to trigger an action to RaftCore by external caller.
///
/// It is created with [`Raft::trigger()`].
///
/// For example, to trigger an election at once, you can use the following code
/// ```ignore
/// raft.trigger().elect(false).await?;
/// ```
///
/// Or to fire a heartbeat, building a snapshot, or purging logs:
///
/// ```ignore
/// raft.trigger().heartbeat().await?;
/// raft.trigger().snapshot().await?;
/// raft.trigger().purge_log().await?;
/// ```
///
/// [`Raft::trigger()`]: crate::Raft::trigger
pub struct Trigger<'r, C>
where C: RaftTypeConfig
{
raft_inner: &'r RaftInner<C>,
}
impl<'r, C> Trigger<'r, C>
where C: RaftTypeConfig
{
pub(in crate::raft) fn new(raft_inner: &'r RaftInner<C>) -> Self {
Self { raft_inner }
}
/// Trigger election at once and return at once.
///
/// With `pre_vote = false`, a real election starts immediately: the term is incremented and the
/// node votes for itself. This bypasses both `enable_elect` and Pre-Vote, so the term climbs
/// even when the node cannot win — which may step down a healthy leader of a lower term.
///
/// With `pre_vote = true`, a Pre-Vote round runs first and the real election proceeds only if a
/// quorum would grant a vote. A live leader holding its lease causes the Pre-Vote to be
/// declined, leaving the term untouched — use this to avoid disrupting a healthy leader
/// with a manual trigger. It does not require [`Config::enable_pre_vote`]; the caller opts
/// in per call.
///
/// Returns error when RaftCore has [`Fatal`] error, e.g., shut down or having storage error.
/// It is not affected by `Raft::enable_elect(false)`.
///
/// [`Config::enable_pre_vote`]: crate::Config::enable_pre_vote
pub async fn elect(&self, pre_vote: bool) -> Result<(), Fatal<C>> {
self.raft_inner.send_external_command(ExternalCommand::Elect { pre_vote }).await
}
/// Trigger a heartbeat at once and return at once.
///
/// Returns error when RaftCore has [`Fatal`] error, e.g., shut down or having storage error.
/// It is not affected by `Raft::enable_heartbeat(false)`.
pub async fn heartbeat(&self) -> Result<(), Fatal<C>> {
self.raft_inner.send_external_command(ExternalCommand::Heartbeat).await
}
/// Trigger to build a snapshot at once and return at once.
///
/// Returns error when RaftCore has [`Fatal`] error, e.g., shut down or having storage error.
pub async fn snapshot(&self) -> Result<(), Fatal<C>> {
self.raft_inner.send_external_command(ExternalCommand::Snapshot).await
}
/// Initiate the log purge up to and including the given `upto` log index.
///
/// Logs that are not included in a snapshot will **NOT** be purged.
/// In such a scenario it will delete as many logs as possible.
/// The [`max_in_snapshot_log_to_keep`] config is not taken into account
/// when purging logs.
///
/// It returns an error only when RaftCore has [`Fatal`] error, e.g., shut down or having
/// storage error.
///
/// Openraft won't purge logs at once, e.g., it may be delayed by several seconds, because if it
/// is a leader and a replication task has been replicating the logs to a follower, the logs
/// can't be purged until the replication task is finished.
///
/// [`max_in_snapshot_log_to_keep`]: `crate::Config::max_in_snapshot_log_to_keep`
pub async fn purge_log(&self, upto: u64) -> Result<(), Fatal<C>> {
self.raft_inner.send_external_command(ExternalCommand::PurgeLog { upto }).await
}
/// Submit a command to inform RaftCore to transfer leadership to the specified node.
///
/// If this node is not a Leader, it is just ignored.
pub async fn transfer_leader(&self, to: C::NodeId) -> Result<(), Fatal<C>> {
self.raft_inner.send_external_command(ExternalCommand::TriggerTransferLeader { to }).await
}
/// Request the RaftCore to allow to reset replication for a specific node when log revert is
/// detected.
///
/// - `allow=true`: This method instructs the RaftCore to allow the target node's log to revert
/// to a previous state for one time.
/// - `allow=false`: This method instructs the RaftCore to panic if the target node's log revert
///
/// This method returns a [`Fatal`] error if it failed to send the request to RaftCore, e.g.,
/// when RaftCore is shut down.
/// Otherwise, it returns an `Ok(Result<_,_>)`, the inner result is:
/// - `Ok(())` if the request is successfully processed,
/// - or `Err(AllowNextRevertError)` explaining why the request is rejected.
///
/// ### Behavior
///
/// - If this node is the Leader, it will attempt to replicate logs to the target node from the
/// beginning.
/// - If this node is not the Leader, the request is ignored.
/// - If the target node is not found, the request is ignored.
///
/// ### Automatic Replication Reset
///
/// When the [`Config::allow_log_reversion`] is enabled, the Leader automatically
/// resets replication if it detects that the target node's log has reverted. This
/// feature is primarily useful in testing environments.
///
/// ### Production Considerations
///
/// In production environments, state reversion is a critical issue that should not be
/// automatically handled. However, there may be scenarios where a Follower's data is
/// intentionally removed and needs to rejoin the cluster(without membership changes). In such
/// cases, the Leader should reinitialize replication for that node with the following steps:
/// - Shut down the target node.
/// - call [`Self::allow_next_revert`] on the Leader.
/// - Clear the target node's data directory.
/// - Restart the target node.
///
/// [`Config::allow_log_reversion`]: `crate::Config::allow_log_reversion`
pub async fn allow_next_revert(
&self,
to: &C::NodeId,
allow: bool,
) -> Result<Result<(), AllowNextRevertError<C>>, Fatal<C>> {
let (tx, rx) = C::oneshot();
self.raft_inner
.send_external_command(ExternalCommand::AllowNextRevert {
to: to.clone(),
allow,
tx,
})
.await?;
let res: Result<(), AllowNextRevertError<C>> = self.raft_inner.recv_msg(rx).await?;
Ok(res)
}
/// Recalculate the internal server state(Leader/Follower/Learner) based on the vote and the
/// membership config.
///
/// Usually this method is not used at all: the internal server state is always recalculated
/// automatically. The only exception is the step down of a Leader that is removed from the
/// membership config: openraft allows such a removed node to keep acting as a Leader. By
/// default it is stepped down automatically (see
/// [`Config::removed_leader_step_down`](crate::Config::removed_leader_step_down));
/// with the automated step down disabled, the application or the administrator decides when
/// to revert it to a learner by calling this method.
///
/// `vote` and `membership_log_id` are the expected current vote and the expected log id of
/// the effective membership config: the refresh is dropped if either differs from the
/// current state when the command is handled, so that a delayed command can not cause an
/// unexpected refresh. A `None` skips the corresponding check: with both `None` the server
/// state is refreshed unconditionally.
///
/// To step down a removed Leader, call this method after the membership config that removes
/// it is committed: a Leader keeps leading while that membership config is not yet
/// committed, because it is the one responsible for replicating it.
///
/// Returns error when RaftCore has [`Fatal`] error, e.g., shut down or having storage error.
#[since(version = "0.10.0")]
pub async fn refresh_server_state(
&self,
vote: Option<VoteOf<C>>,
membership_log_id: Option<LogIdOf<C>>,
) -> Result<(), Fatal<C>> {
let cmd = ExternalCommand::RefreshServerState {
vote,
membership_log_id,
};
self.raft_inner.send_external_command(cmd).await
}
}