Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature guarded no_std support #518

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: CI
on:
pull_request:
push:
branches:
branches:
- master

env:
Expand Down Expand Up @@ -45,12 +45,16 @@ jobs:
run: cargo fmt --all -- --check
- name: check clippy
if: ${{ matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' }}
run: cargo clippy --all --all-targets -- -D clippy::all && cargo clippy --no-default-features --features prost-codec -- -D clippy::all
run: cargo clippy --all --all-targets -- -D clippy::all && cargo clippy --no-default-features --features prost-codec --features std -- -D clippy::all
- name: run tests with no-std enabled
# No-std implementation uses feature that can only be compiled by nightly version
if: ${{ matrix.rust == 'nightly' }}
run: cargo test --all --no-default-features --features=protobuf-codec --features=default-logger -- --nocapture
- run: cargo test --all -- --nocapture
# Validate benches still work.
- run: cargo bench --all -- --test
# Because failpoints inject failure in code path, which will affect all concurrently running tests, Hence they need to be synchronized, which make tests slow.
# Only package harness has failpoints tests.
- run: cargo test --tests --features failpoints --package harness -- --nocapture
# TODO: There is a bug in protobuf-build that cached size is not supported yet, so do not test harness.
- run: cargo test --no-default-features --features prost-codec -- --nocapture
- run: cargo test --no-default-features --features prost-codec --features std -- --nocapture
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ readme = "README.md"
homepage = "https://github.com/tikv/raft-rs"
documentation = "https://docs.rs/raft"
description = "The rust language implementation of Raft algorithm."
categories = ["algorithms", "database-implementations"]
categories = ["algorithms", "database-implementations", "no-std"]
edition = "2021"

[workspace]
members = ["proto", "harness", "datadriven"]

[features]
default = ["protobuf-codec", "default-logger"]
default = ["protobuf-codec", "default-logger", "std"]
std = []
# Enable failpoints
failpoints = ["fail/failpoints"]
protobuf-codec = ["raft-proto/protobuf-codec", "bytes"]
Expand All @@ -37,6 +38,8 @@ slog = "2.2"
slog-envlogger = { version = "2.1.0", optional = true }
slog-stdlog = { version = "4", optional = true }
slog-term = { version = "2.4.0", optional = true }
spin = { version = "0.9.8"}
hashbrown = { version = "0.14.0"}

[dev-dependencies]
criterion = "0.3"
Expand Down
2 changes: 1 addition & 1 deletion harness/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ categories = []
edition = "2018"

[features]
default = ["protobuf-codec", "raft/default-logger"]
default = ["protobuf-codec", "raft/default-logger", "raft/std"]
# Enable failpoints
failpoints = ["fail/failpoints"]
protobuf-codec = ["raft/protobuf-codec"]
Expand Down
4 changes: 2 additions & 2 deletions proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ pub mod util {
{
fn from((voters, learners): (Iter1, Iter2)) -> Self {
let mut conf_state = ConfState::default();
conf_state.mut_voters().extend(voters.into_iter());
conf_state.mut_learners().extend(learners.into_iter());
conf_state.mut_voters().extend(voters);
conf_state.mut_learners().extend(learners);
conf_state
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/confchange/changer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0.

use alloc::borrow::ToOwned;
use alloc::format;
use alloc::vec;
use alloc::vec::Vec;

use crate::eraftpb::{ConfChangeSingle, ConfChangeType};
use crate::tracker::{Configuration, ProgressMap, ProgressTracker};
use crate::{Error, Result};
Expand Down
4 changes: 3 additions & 1 deletion src/confchange/datadriven_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::fmt::Write;
use alloc::string::String;
use alloc::string::ToString;
use core::fmt::Write;

use crate::{default_logger, Changer, ProgressTracker};
use datadriven::{run_test, walk};
Expand Down
2 changes: 2 additions & 0 deletions src/confchange/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// TODO: remove following line
#![allow(dead_code)]

use alloc::vec::Vec;

use super::changer::Changer;
use crate::eraftpb::{ConfChangeSingle, ConfChangeType, ConfState};
use crate::tracker::ProgressTracker;
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use alloc::borrow::ToOwned;
use alloc::format;

pub use super::read_only::{ReadOnlyOption, ReadState};
use super::util::NO_LIMIT;
use super::{
Expand Down
146 changes: 108 additions & 38 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,110 @@
// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
use thiserror::Error;

use crate::StdError;
use alloc::boxed::Box;
use alloc::string::String;
use core::fmt;

/// The base error type for raft
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum Error {
/// An IO error occurred
#[error("{0}")]
Io(#[from] std::io::Error),
#[cfg(feature = "std")]
Io(std::io::Error),
/// A storage error occurred.
#[error("{0}")]
Store(#[from] StorageError),
Store(StorageError),
/// Raft cannot step the local message.
#[error("raft: cannot step raft local message")]
StepLocalMsg,
/// The raft peer is not found and thus cannot step.
#[error("raft: cannot step as peer not found")]
StepPeerNotFound,
/// The proposal of changes was dropped.
#[error("raft: proposal dropped")]
ProposalDropped,
/// The configuration is invalid.
#[error("{0}")]
ConfigInvalid(String),
/// A protobuf message codec failed in some manner.
#[error("protobuf codec error {0:?}")]
CodecError(#[from] protobuf::ProtobufError),
CodecError(protobuf::ProtobufError),
/// The node exists, but should not.
#[error("The node {id} already exists in the {set} set.")]
Exists {
/// The node id.
id: u64,
/// The node set.
set: &'static str,
},
/// The node does not exist, but should.
#[error("The node {id} is not in the {set} set.")]
NotExists {
/// The node id.
id: u64,
/// The node set.
set: &'static str,
},
/// ConfChange proposal is invalid.
#[error("{0}")]
ConfChangeError(String),
/// The request snapshot is dropped.
#[error("raft: request snapshot dropped")]
RequestSnapshotDropped,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
#[cfg(feature = "std")]
Error::Io(ref e) => write!(f, "{}", e),
Error::Store(ref e) => write!(f, "{}", e),
Error::StepLocalMsg => write!(f, "raft: cannot step raft local message"),
Error::StepPeerNotFound => write!(f, "raft: cannot step as peer not found"),
Error::ProposalDropped => write!(f, "raft: proposal dropped"),
Error::ConfigInvalid(ref m) => write!(f, "{}", m),
Error::CodecError(ref e) => write!(f, "protobuf codec error {0:?}", e),
Error::Exists { id, set } => {
write!(f, "The node {id} already exists in the {set} set.")
}
Error::NotExists { id, set } => {
write!(f, "The node {id} is not in the {set} set.")
}
Error::ConfChangeError(ref m) => write!(f, "{}", m),
Error::RequestSnapshotDropped => write!(f, "raft: request snapshot dropped"),
}
}
}

impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
#[cfg(feature = "std")]
Error::Io(ref e) => Some(e),
Error::Store(ref e) => Some(e),
Error::CodecError(ref e) => Some(e),
_ => None,
}
}
}

#[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
}
}

impl From<StorageError> for Error {
fn from(err: StorageError) -> Error {
Error::Store(err)
}
}

impl From<protobuf::ProtobufError> for Error {
fn from(err: protobuf::ProtobufError) -> Error {
Error::CodecError(err)
}
}

impl PartialEq for Error {
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_same_arms))]
fn eq(&self, other: &Error) -> bool {
match (self, other) {
(Error::StepPeerNotFound, Error::StepPeerNotFound) => true,
(Error::ProposalDropped, Error::ProposalDropped) => true,
(Error::Store(ref e1), Error::Store(ref e2)) => e1 == e2,
#[cfg(feature = "std")]
(Error::Io(ref e1), Error::Io(ref e2)) => e1.kind() == e2.kind(),
(Error::StepLocalMsg, Error::StepLocalMsg) => true,
(Error::ConfigInvalid(ref e1), Error::ConfigInvalid(ref e2)) => e1 == e2,
Expand All @@ -67,26 +116,41 @@ impl PartialEq for Error {
}

/// An error with the storage.
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum StorageError {
/// The storage was compacted and not accessible
#[error("log compacted")]
Compacted,
/// The log is not available.
#[error("log unavailable")]
Unavailable,
/// The log is being fetched.
#[error("log is temporarily unavailable")]
LogTemporarilyUnavailable,
/// The snapshot is out of date.
#[error("snapshot out of date")]
SnapshotOutOfDate,
/// The snapshot is being created.
#[error("snapshot is temporarily unavailable")]
SnapshotTemporarilyUnavailable,
/// Some other error occurred.
#[error("unknown error {0}")]
Other(#[from] Box<dyn std::error::Error + Sync + Send>),
Other(Box<dyn StdError + Sync + Send>),
}

impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
StorageError::Compacted => write!(f, "log compacted"),
StorageError::Unavailable => write!(f, "log unavailable"),
StorageError::LogTemporarilyUnavailable => write!(f, "log is temporarily unavailable"),
StorageError::SnapshotOutOfDate => write!(f, "snapshot out of date"),
StorageError::SnapshotTemporarilyUnavailable => {
write!(f, "snapshot is temporarily unavailable")
}
StorageError::Other(ref e) => write!(f, "unknown error {}", e),
}
}
}

impl StdError for StorageError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
None
}
}

impl PartialEq for StorageError {
Expand All @@ -113,13 +177,12 @@ impl PartialEq for StorageError {
}

/// A result type that wraps up the raft errors.
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T> = core::result::Result<T, Error>;

#[allow(clippy::eq_op)]
#[cfg(test)]
mod tests {
use super::*;
use std::io;

#[test]
fn test_error_equal() {
Expand All @@ -128,14 +191,6 @@ mod tests {
Error::Store(StorageError::Compacted),
Error::Store(StorageError::Compacted)
);
assert_eq!(
Error::Io(io::Error::new(io::ErrorKind::UnexpectedEof, "oh no!")),
Error::Io(io::Error::new(io::ErrorKind::UnexpectedEof, "oh yes!"))
);
assert_ne!(
Error::Io(io::Error::new(io::ErrorKind::NotFound, "error")),
Error::Io(io::Error::new(io::ErrorKind::BrokenPipe, "error"))
);
assert_eq!(Error::StepLocalMsg, Error::StepLocalMsg);
assert_eq!(
Error::ConfigInvalid(String::from("config error")),
Expand All @@ -145,16 +200,31 @@ mod tests {
Error::ConfigInvalid(String::from("config error")),
Error::ConfigInvalid(String::from("other error"))
);
assert_eq!(
Error::from(io::Error::new(io::ErrorKind::Other, "oh no!")),
Error::from(io::Error::new(io::ErrorKind::Other, "oh yes!"))
);
assert_ne!(
Error::StepPeerNotFound,
Error::Store(StorageError::Compacted)
);
}

#[cfg(feature = "std")]
#[test]
fn test_error_equal_std() {
use std::io;

assert_eq!(
Error::Io(io::Error::new(io::ErrorKind::UnexpectedEof, "oh no!")),
Error::Io(io::Error::new(io::ErrorKind::UnexpectedEof, "oh yes!"))
);
assert_ne!(
Error::Io(io::Error::new(io::ErrorKind::NotFound, "error")),
Error::Io(io::Error::new(io::ErrorKind::BrokenPipe, "error"))
);
assert_eq!(
Error::from(io::Error::new(io::ErrorKind::Other, "oh no!")),
Error::from(io::Error::new(io::ErrorKind::Other, "oh yes!"))
);
}

#[test]
fn test_storage_error_equal() {
assert_eq!(StorageError::Compacted, StorageError::Compacted);
Expand Down
Loading