diff --git a/Cargo.toml b/Cargo.toml index 1dff26f46..4b94d262f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ maplit = "1.0.2" pin-utils = "0.1.0" pretty_assertions = "1.0.0" rand = "0.8" +rkyv = { version = "0.7.42", features=["validation"] } serde = { version="1.0.114", features=["derive", "rc"]} serde_json = "1.0.57" tempfile = { version = "3.4.0" } diff --git a/openraft/Cargo.toml b/openraft/Cargo.toml index b820865a3..13d22f031 100644 --- a/openraft/Cargo.toml +++ b/openraft/Cargo.toml @@ -25,6 +25,7 @@ pin-utils = { workspace = true } rand = { workspace = true } serde = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } +rkyv = { workspace = true, optional = true } clap = { workspace = true } tempfile = { workspace = true, optional = true } thiserror = { workspace = true } @@ -58,6 +59,10 @@ bt = ["anyerror/backtrace", "anyhow/backtrace"] # If you'd like to use `serde` to serialize messages. serde = ["dep:serde"] +# Add rkyv::Archive, rkyv:Deserialize and rkyv::Serialize derives and archive(check_bytes) to data types. +# If you'd like to use `rkyv` to serialize messages. +rkyv = ["dep:rkyv"] + # Turn on this feature it allows at most ONE quorum-granted leader for each term. # This is the way standard raft does, by making the LeaderId a partial order value. # diff --git a/openraft/src/change_members.rs b/openraft/src/change_members.rs index 8f234621d..2a44850ba 100644 --- a/openraft/src/change_members.rs +++ b/openraft/src/change_members.rs @@ -9,6 +9,11 @@ use crate::NodeId; #[derive(Debug, Clone)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub enum ChangeMembers { /// Upgrade learners to voters. /// diff --git a/openraft/src/core/server_state.rs b/openraft/src/core/server_state.rs index 14e3553f1..532d9932e 100644 --- a/openraft/src/core/server_state.rs +++ b/openraft/src/core/server_state.rs @@ -2,6 +2,11 @@ #[derive(Debug, Clone, Copy, Default)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub enum ServerState { /// The node is completely passive; replicating entries, but neither voting nor timing out. #[default] diff --git a/openraft/src/docs/feature_flags/feature-flags.md b/openraft/src/docs/feature_flags/feature-flags.md index c24a7aec3..cad1e8466 100644 --- a/openraft/src/docs/feature_flags/feature-flags.md +++ b/openraft/src/docs/feature_flags/feature-flags.md @@ -7,6 +7,9 @@ By default openraft enables no features. - `serde`: derives `serde::Serialize, serde::Deserialize` for type that are used in storage and network, such as `Vote` or `AppendEntriesRequest`. +- `rkyv`: derives `rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, rkyv::CheckBytes` + for types that are used in storage and network, such as `Vote` or `AppendEntriesRequest`. + - `single-term-leader`: allows only one leader to be elected in each `term`. This is the standard raft policy, which increases election confliction rate but reduce `LogId`(`(term, node_id, index)` to `(term, index)`) size. diff --git a/openraft/src/entry/mod.rs b/openraft/src/entry/mod.rs index 342d09c34..70e5666bd 100644 --- a/openraft/src/entry/mod.rs +++ b/openraft/src/entry/mod.rs @@ -17,6 +17,11 @@ pub use traits::RaftPayload; /// A Raft log entry. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct Entry where C: RaftTypeConfig { diff --git a/openraft/src/entry/payload.rs b/openraft/src/entry/payload.rs index 35f5846d8..1f8398919 100644 --- a/openraft/src/entry/payload.rs +++ b/openraft/src/entry/payload.rs @@ -9,6 +9,11 @@ use crate::RaftTypeConfig; /// Log entry payload variants. #[derive(PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub enum EntryPayload { /// An empty payload committed by a new cluster leader. Blank, diff --git a/openraft/src/error.rs b/openraft/src/error.rs index 0300f342e..fbd966fbe 100644 --- a/openraft/src/error.rs +++ b/openraft/src/error.rs @@ -28,6 +28,12 @@ use crate::Vote; derive(serde::Deserialize, serde::Serialize), serde(bound = "E:serde::Serialize + for <'d> serde::Deserialize<'d>") )] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] + pub enum RaftError where NID: NodeId { @@ -470,6 +476,35 @@ pub struct EmptyMembership {} #[error("infallible")] pub enum Infallible {} +#[cfg(feature = "rkyv")] +mod rkyv_serialization { + //! Manual implementations for the required `rkyv` traits since it is not + //! possible to derive them on an enum with no variant. All implementations + //! use `unreachable` since `Infallible` cannot be constructed. + + impl rkyv::Archive for super::Infallible { + type Archived = (); + type Resolver = (); + + #[inline] + unsafe fn resolve(&self, _: usize, _: Self::Resolver, _: *mut Self::Archived) { + unreachable!() + } + } + + impl rkyv::Serialize for super::Infallible { + fn serialize(&self, _: &mut S) -> Result { + unreachable!() + } + } + + impl rkyv::Deserialize for u16 { + fn deserialize(&self, _: &mut D) -> Result { + unreachable!() + } + } +} + /// A place holder to mark RaftError won't have a ForwardToLeader variant. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/openraft/src/log_id/mod.rs b/openraft/src/log_id/mod.rs index aa61b6b17..09f800fe7 100644 --- a/openraft/src/log_id/mod.rs +++ b/openraft/src/log_id/mod.rs @@ -22,6 +22,11 @@ use crate::NodeId; /// parts: a leader id, which refers to the leader that proposed this log, and an integer index. #[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct LogId { /// The id of the leader that proposed this log pub leader_id: CommittedLeaderId, diff --git a/openraft/src/membership/membership.rs b/openraft/src/membership/membership.rs index 8ef107114..ee0ae8e7a 100644 --- a/openraft/src/membership/membership.rs +++ b/openraft/src/membership/membership.rs @@ -21,6 +21,11 @@ use crate::NodeId; /// of a majority of every config. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct Membership where N: Node, diff --git a/openraft/src/membership/stored_membership.rs b/openraft/src/membership/stored_membership.rs index 9d580dc70..50bd7e114 100644 --- a/openraft/src/membership/stored_membership.rs +++ b/openraft/src/membership/stored_membership.rs @@ -19,6 +19,11 @@ use crate::NodeId; #[derive(Clone, Debug, Default)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct StoredMembership where N: Node, diff --git a/openraft/src/metrics/raft_metrics.rs b/openraft/src/metrics/raft_metrics.rs index e17c52318..ae00c9f86 100644 --- a/openraft/src/metrics/raft_metrics.rs +++ b/openraft/src/metrics/raft_metrics.rs @@ -15,6 +15,11 @@ use crate::Vote; /// A set of metrics describing the current state of a Raft node. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct RaftMetrics where NID: NodeId, diff --git a/openraft/src/raft.rs b/openraft/src/raft.rs index 3c662683b..427c57f13 100644 --- a/openraft/src/raft.rs +++ b/openraft/src/raft.rs @@ -1060,6 +1060,11 @@ impl fmt::Display for ExternalCommand { /// An RPC sent by a cluster leader to replicate log entries (§5.3), and as a heartbeat (§5.2). #[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct AppendEntriesRequest { pub vote: Vote, @@ -1110,6 +1115,11 @@ impl MessageSummary> for AppendEntrie #[derive(Debug)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub enum AppendEntriesResponse { /// Successfully replicated all log entries to the target node. Success, @@ -1168,6 +1178,11 @@ impl fmt::Display for AppendEntriesResponse { /// An RPC sent by candidates to gather votes (§5.2). #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct VoteRequest { pub vote: Vote, pub last_log_id: Option>, @@ -1194,6 +1209,11 @@ impl VoteRequest { /// The response to a `VoteRequest`. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct VoteResponse { /// vote after a node handling vote-request. /// Thus `resp.vote >= req.vote` always holds. @@ -1221,6 +1241,11 @@ impl MessageSummary> for VoteResponse { #[derive(Clone, Debug)] #[derive(PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct InstallSnapshotRequest { pub vote: Vote, @@ -1255,6 +1280,11 @@ impl MessageSummary> for InstallSna #[derive(derive_more::Display)] #[display(fmt = "{{vote:{}}}", vote)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct InstallSnapshotResponse { pub vote: Vote, } @@ -1265,6 +1295,11 @@ pub struct InstallSnapshotResponse { derive(serde::Deserialize, serde::Serialize), serde(bound = "C::R: AppDataResponse") )] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct ClientWriteResponse { /// The id of the log that is applied. pub log_id: LogId, diff --git a/openraft/src/storage/mod.rs b/openraft/src/storage/mod.rs index 3867f54a9..d20e155af 100644 --- a/openraft/src/storage/mod.rs +++ b/openraft/src/storage/mod.rs @@ -34,6 +34,11 @@ use crate::Vote; #[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct SnapshotMeta where NID: NodeId, diff --git a/openraft/src/vote/leader_id/leader_id_adv.rs b/openraft/src/vote/leader_id/leader_id_adv.rs index a738a5f90..833e567b9 100644 --- a/openraft/src/vote/leader_id/leader_id_adv.rs +++ b/openraft/src/vote/leader_id/leader_id_adv.rs @@ -14,6 +14,11 @@ use crate::NodeId; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct LeaderId where NID: NodeId { diff --git a/openraft/src/vote/leader_id/leader_id_std.rs b/openraft/src/vote/leader_id/leader_id_std.rs index f330183b4..c6ecb3bd1 100644 --- a/openraft/src/vote/leader_id/leader_id_std.rs +++ b/openraft/src/vote/leader_id/leader_id_std.rs @@ -6,6 +6,11 @@ use crate::NodeId; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct LeaderId where NID: NodeId { @@ -76,6 +81,11 @@ impl std::fmt::Display for LeaderId { #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] #[cfg_attr(feature = "serde", serde(transparent))] pub struct CommittedLeaderId { pub term: u64, diff --git a/openraft/src/vote/vote.rs b/openraft/src/vote/vote.rs index 78603a97d..d0ad7dd1a 100644 --- a/openraft/src/vote/vote.rs +++ b/openraft/src/vote/vote.rs @@ -9,6 +9,11 @@ use crate::NodeId; /// `Vote` represent the privilege of a node. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(bound = ""))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize), + archive(check_bytes) +)] pub struct Vote { /// The id of the node that tries to become the leader. pub leader_id: LeaderId,