mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-05 19:47:47 +01:00
* rust: apply clippy --fix to both crates The mechanical part of a clippy sweep: `cargo clippy --all-targets --fix` on seaweed-volume and the seaweed-worker workspace, hand-reviewed. Both manifests declare their MSRV (1.91.1 and 1.94.1), so every suggestion clippy applied is within it: the collapsible_if sites become let chains (1.88, edition 2024), `% n == 0` becomes is_multiple_of (1.87), chunks_exact with a constant becomes as_chunks (1.88), repeat().take() becomes repeat_n (1.82), and io::Error::new(Other, ..) becomes io::Error::other (1.74). The rest is redundant clones, borrows, casts, closures and field names. Nothing here changes behaviour. The three let_and_return sites in needle_map.rs and store_ec.rs deserve a note: the `let result = ..; result` shape was a deliberate edition-2021 workaround to drop a redb guard before the table it borrows. Edition 2024 drops tail-expression temporaries before locals, which is why clippy now flags it, and the two comments that described the workaround say so instead. Manual edits on top of the tool output: the blocks clippy rewrote are re-indented the way rustfmt lays them out (only those blocks — the crate is not rustfmt-clean and a whole-crate fmt would bury this diff), the blank lines let_and_return left behind are removed, and the CRC legacy_value test compares against a literal worked out from the original shift formula rather than restating rotate_right. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjZY429aVU74SLDmo1wiuU * rust: clear the clippy warnings --fix cannot apply, and say why the rest stay Hand fixes for the lints clippy only reports. Behaviour is unchanged throughout; each rewrite is the one clippy names. - needless_range_loop (7): index loops over shard vectors become iterator loops. Where the old code indexed `v[..n]` the new loop iterates `v[..n]` so an undersized vector still panics the same way. - field_reassign_with_default (6): struct literals with `..Default`. - redundant_pattern_matching (3): `if let Err(_) = guard.check()` becomes `.is_err()`, which also releases the read guard at the end of the condition instead of at the end of the block. - manual_strip (2), manual_checked_ops, format_in_format_args, redundant_locals, wrong_self_convention (to_vif takes self by value, so it is into_vif; CompactEntry is Copy, so to_needle_value takes self). - type_complexity (2): `OrphanShardLoad` and `RawNeedleEntry` name two tuples that were spelled out inline. - new_without_default: CompactNeedleMap gets a Default that calls new(). - suspicious_open_options: a test helper spells out `.truncate(false)`, which is what `.create(true).write(true)` already did. What stays, and the attribute that says so: - too_many_arguments (10): `#[expect]` on each function. Folding 8–15 parameters into a struct is a design change, not a lint fix. - await_holding_lock / readonly_write_lock: one test holds the store write guard across a sleep on purpose, as a barrier that parks the copy task at the mount block. `#[expect(.., reason = ..)]` records it. - module_inception: needle/needle.rs mirrors the Go package layout. Two lints become crate-wide policy in `[lints.clippy]`, with the reason next to each: result_large_err, because every RPC path returns tonic::Status (176 bytes) and boxing it would change every handler signature; and needless_update, because `..Default::default()` on a protobuf message literal is what lets a proto gain a field without touching every constructor (all 11 sites are pb messages). The worker workspace gets the same table and its members opt in with `lints.workspace = true`; its generated plugin.rs also allows large_enum_variant on prost's oneof enums. Both crates are now clean under `cargo clippy --all-targets -- -D warnings`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjZY429aVU74SLDmo1wiuU * rust volume: use the std APIs the 1.91 MSRV already pays for The crate declares rust-version 1.91.1, so a few things the code still worked around are plain std now. All of them come from the 1.85–1.91 release notes; nothing here needs a newer toolchain than the manifest already requires. - std::sync::LazyLock (1.80) replaces the lazy_static! block in metrics.rs, and the lazy_static dependency goes. Every use site reads the same through Deref, so no caller changes. - Duration::from_mins / from_hours (1.91) replace `from_secs(v * 60)` and `from_secs(v * 3600)` in the option parser and the shard-location refresh TTLs. One difference for the parser: an absurd count that overflows u64 seconds now panics in release builds too, where the multiplication used to wrap. - Result::flatten (1.89) replaces `.and_then(|r| r)` on the replication join handle. - OsStr::display (1.87) replaces `to_string_lossy()` where the name was only being formatted; the output is byte-identical. - `#[allow]` becomes `#[expect]` (1.81) on the suppressions that are meant to be permanent, so a suppression that stops being needed becomes a warning rather than lingering. Doing that found four that already had: dead_code on ChunkManifest, base_name and last_io_error, and too_many_arguments on read_from_data_shards, which is down to seven parameters. Those attributes are deleted. The three allows that depend on cfg (a unix-only mutation, a linux-only field set, a profiling-only parameter) stay as allow, because expect would be unfulfilled on the other platforms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjZY429aVU74SLDmo1wiuU * ci: add a commented-out clippy step to both Rust workflows Both crates are warning-free under `cargo clippy --all-targets -D warnings` now. Whether that becomes a gate is a policy call, so the step is present but commented out; uncommenting it is the whole change. The comment points at the `[lints.clippy]` table where crate-wide exceptions are recorded, so the gate does not become a reason to sprinkle allows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjZY429aVU74SLDmo1wiuU * rust volume: guard parse_duration against overflow panics Duration::from_mins/from_hours panic when the count overflows u64 seconds. Use checked_mul so an oversized CLI value falls back to the parser default instead of crashing volume startup. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
255 lines
7.5 KiB
Rust
255 lines
7.5 KiB
Rust
//! EcVolumeShard: a single shard file (.ec00-.ec13) of an erasure-coded volume.
|
|
|
|
use std::fs::{self, File, OpenOptions};
|
|
use std::io::{self, Write};
|
|
|
|
use crate::storage::types::*;
|
|
use crate::storage::volume_open::open_volume_file;
|
|
|
|
pub const DATA_SHARDS_COUNT: usize = 10;
|
|
pub const PARITY_SHARDS_COUNT: usize = 4;
|
|
pub const TOTAL_SHARDS_COUNT: usize = DATA_SHARDS_COUNT + PARITY_SHARDS_COUNT;
|
|
pub const MAX_SHARD_COUNT: usize = 32;
|
|
pub const MIN_TOTAL_DISKS: usize = TOTAL_SHARDS_COUNT / PARITY_SHARDS_COUNT + 1;
|
|
pub const ERASURE_CODING_LARGE_BLOCK_SIZE: usize = 1024 * 1024 * 1024; // 1GB
|
|
pub const ERASURE_CODING_SMALL_BLOCK_SIZE: usize = 1024 * 1024; // 1MB
|
|
|
|
pub type ShardId = u8;
|
|
|
|
/// A single erasure-coded shard file.
|
|
pub struct EcVolumeShard {
|
|
pub volume_id: VolumeId,
|
|
pub shard_id: ShardId,
|
|
pub collection: String,
|
|
pub dir: String,
|
|
pub disk_type: DiskType,
|
|
ecd_file: Option<File>,
|
|
ecd_file_size: i64,
|
|
}
|
|
|
|
impl EcVolumeShard {
|
|
/// Create a new shard reference (does not open the file).
|
|
pub fn new(dir: &str, collection: &str, volume_id: VolumeId, shard_id: ShardId) -> Self {
|
|
EcVolumeShard {
|
|
volume_id,
|
|
shard_id,
|
|
collection: collection.to_string(),
|
|
dir: dir.to_string(),
|
|
disk_type: DiskType::default(),
|
|
ecd_file: None,
|
|
ecd_file_size: 0,
|
|
}
|
|
}
|
|
|
|
/// Shard file name, e.g. "dir/collection_42.ec03"
|
|
pub fn file_name(&self) -> String {
|
|
let base =
|
|
crate::storage::volume::volume_file_name(&self.dir, &self.collection, self.volume_id);
|
|
format!("{}.ec{:02}", base, self.shard_id)
|
|
}
|
|
|
|
/// Open the shard file for reading.
|
|
pub fn open(&mut self) -> io::Result<()> {
|
|
let path = self.file_name();
|
|
let file = open_volume_file(OpenOptions::new().read(true), &path)?;
|
|
self.ecd_file_size = file.metadata()?.len() as i64;
|
|
self.ecd_file = Some(file);
|
|
Ok(())
|
|
}
|
|
|
|
/// Create the shard file for writing.
|
|
pub fn create(&mut self) -> io::Result<()> {
|
|
let path = self.file_name();
|
|
let file = open_volume_file(
|
|
OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true),
|
|
&path,
|
|
)?;
|
|
self.ecd_file = Some(file);
|
|
self.ecd_file_size = 0;
|
|
Ok(())
|
|
}
|
|
|
|
/// Read data at a specific offset.
|
|
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
let file = self
|
|
.ecd_file
|
|
.as_ref()
|
|
.ok_or_else(|| io::Error::other("shard file not open"))?;
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::FileExt;
|
|
file.read_at(buf, offset)
|
|
}
|
|
|
|
#[cfg(not(unix))]
|
|
{
|
|
use std::io::{Read, Seek, SeekFrom};
|
|
// File::read_at is unix-only; fall back to seek + read.
|
|
// We need a mutable reference for seek/read, so clone the handle.
|
|
let mut f = file.try_clone()?;
|
|
f.seek(SeekFrom::Start(offset))?;
|
|
f.read(buf)
|
|
}
|
|
}
|
|
|
|
/// Write data to the shard file (appends).
|
|
pub fn write_all(&mut self, data: &[u8]) -> io::Result<()> {
|
|
let file = self
|
|
.ecd_file
|
|
.as_mut()
|
|
.ok_or_else(|| io::Error::other("shard file not open"))?;
|
|
file.write_all(data)?;
|
|
self.ecd_file_size += data.len() as i64;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn file_size(&self) -> i64 {
|
|
self.ecd_file_size
|
|
}
|
|
|
|
/// A duplicate of the mounted shard handle, for a reader that has to
|
|
/// outlive the store guard.
|
|
///
|
|
/// This is the same descriptor `read_at` serves from, so it carries the
|
|
/// `O_NOATIME` from `open_volume_file` and keeps pointing at the shard
|
|
/// that was mounted, whatever later happens to the path. `dup` shares the
|
|
/// kernel file offset, which is why every read through it must be
|
|
/// positional (`read_at`), never seek-based.
|
|
pub fn try_clone_file(&self) -> io::Result<File> {
|
|
self.ecd_file
|
|
.as_ref()
|
|
.ok_or_else(|| io::Error::other("shard file not open"))?
|
|
.try_clone()
|
|
}
|
|
|
|
/// Protobuf descriptor for this shard. Mirrors Go's ToEcShardInfo.
|
|
pub fn to_ec_shard_info(&self) -> crate::pb::volume_server_pb::EcShardInfo {
|
|
crate::pb::volume_server_pb::EcShardInfo {
|
|
shard_id: self.shard_id as u32,
|
|
size: self.file_size(),
|
|
collection: self.collection.clone(),
|
|
volume_id: self.volume_id.0,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Close the shard file.
|
|
pub fn close(&mut self) {
|
|
if let Some(ref file) = self.ecd_file {
|
|
let _ = file.sync_all();
|
|
}
|
|
self.ecd_file = None;
|
|
}
|
|
|
|
/// Delete the shard file from disk.
|
|
pub fn destroy(&mut self) {
|
|
self.close();
|
|
let _ = fs::remove_file(self.file_name());
|
|
}
|
|
}
|
|
|
|
/// ShardBits: bitmap tracking which shards are present.
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
pub struct ShardBits(pub u32);
|
|
|
|
impl ShardBits {
|
|
pub fn add_shard_id(&mut self, id: ShardId) {
|
|
assert!((id as usize) < 32, "shard id {} out of bounds (max 31)", id,);
|
|
self.0 |= 1 << id;
|
|
}
|
|
|
|
pub fn remove_shard_id(&mut self, id: ShardId) {
|
|
assert!((id as usize) < 32, "shard id {} out of bounds (max 31)", id,);
|
|
self.0 &= !(1 << id);
|
|
}
|
|
|
|
pub fn has_shard_id(&self, id: ShardId) -> bool {
|
|
if (id as usize) >= 32 {
|
|
return false;
|
|
}
|
|
self.0 & (1 << id) != 0
|
|
}
|
|
|
|
pub fn shard_id_count(&self) -> usize {
|
|
self.0.count_ones() as usize
|
|
}
|
|
|
|
/// Iterator over present shard IDs.
|
|
pub fn shard_ids(&self) -> Vec<ShardId> {
|
|
let mut ids = Vec::with_capacity(self.shard_id_count());
|
|
for i in 0..32 {
|
|
if self.has_shard_id(i) {
|
|
ids.push(i);
|
|
}
|
|
}
|
|
ids
|
|
}
|
|
|
|
pub fn minus(&self, other: ShardBits) -> ShardBits {
|
|
ShardBits(self.0 & !other.0)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_shard_bits() {
|
|
let mut bits = ShardBits::default();
|
|
assert_eq!(bits.shard_id_count(), 0);
|
|
|
|
bits.add_shard_id(0);
|
|
bits.add_shard_id(3);
|
|
bits.add_shard_id(13);
|
|
assert_eq!(bits.shard_id_count(), 3);
|
|
assert!(bits.has_shard_id(0));
|
|
assert!(bits.has_shard_id(3));
|
|
assert!(!bits.has_shard_id(1));
|
|
|
|
bits.remove_shard_id(3);
|
|
assert!(!bits.has_shard_id(3));
|
|
assert_eq!(bits.shard_id_count(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_shard_bits_ids() {
|
|
let mut bits = ShardBits::default();
|
|
bits.add_shard_id(1);
|
|
bits.add_shard_id(5);
|
|
bits.add_shard_id(9);
|
|
assert_eq!(bits.shard_ids(), vec![1, 5, 9]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_shard_bits_minus() {
|
|
let mut a = ShardBits::default();
|
|
a.add_shard_id(0);
|
|
a.add_shard_id(1);
|
|
a.add_shard_id(2);
|
|
|
|
let mut b = ShardBits::default();
|
|
b.add_shard_id(1);
|
|
|
|
let c = a.minus(b);
|
|
assert_eq!(c.shard_ids(), vec![0, 2]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_shard_file_name() {
|
|
let shard = EcVolumeShard::new("/data", "pics", VolumeId(42), 3);
|
|
assert_eq!(shard.file_name(), "/data/pics_42.ec03");
|
|
}
|
|
|
|
#[test]
|
|
fn test_shard_file_name_no_collection() {
|
|
let shard = EcVolumeShard::new("/data", "", VolumeId(7), 13);
|
|
assert_eq!(shard.file_name(), "/data/7.ec13");
|
|
}
|
|
}
|