multilog: stop writing poisoned per-log meta

Summary:
The poisoned meta makes investigation harder. ex. `debugdumpindexlog` won't
work on those logs.

Reviewed By: sfilipco

Differential Revision: D23488213

fbshipit-source-id: b33894d8c605694b6adf5afdaed45707fbd7357e
This commit is contained in:
Jun Wu 2020-09-03 13:46:20 -07:00 committed by Facebook GitHub Bot
parent 4947e07cb7
commit f79e7657af
4 changed files with 17 additions and 74 deletions

View File

@ -24,26 +24,15 @@ pub struct LogMetadata {
/// Used to detect non-append-only changes.
/// Conceptually similar to "create time".
pub(crate) epoch: u64,
/// Once set. Indicate this LogMetadata shouldn't be read.
pub(crate) poisoned: Option<&'static str>,
}
impl LogMetadata {
const HEADER: &'static [u8] = b"meta\0";
const POISONED_HEADER: &'static [u8] = b"pois\0";
/// Read metadata from a reader.
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut header = vec![0; Self::HEADER.len()];
reader.read_exact(&mut header)?;
if header == Self::POISONED_HEADER {
let message_len: usize = reader.read_vlq()?;
let mut message_bytes = vec![0u8; message_len];
reader.read_exact(&mut message_bytes[..])?;
let msg = String::from_utf8_lossy(&message_bytes);
return Err(io::Error::new(io::ErrorKind::AddrNotAvailable, msg));
}
if header != Self::HEADER {
let msg = "invalid metadata header";
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
@ -84,19 +73,11 @@ impl LogMetadata {
primary_len,
indexes,
epoch,
poisoned: None,
})
}
/// Write metadata to a writer.
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
if let Some(poisoned) = self.poisoned {
writer.write_all(Self::POISONED_HEADER)?;
writer.write_vlq(poisoned.as_bytes().len())?;
writer.write_all(poisoned.as_bytes())?;
return Ok(());
}
let mut buf = Vec::new();
buf.write_vlq(self.primary_len)?;
buf.write_vlq(self.indexes.len())?;
@ -138,17 +119,6 @@ impl LogMetadata {
primary_len: len,
indexes: BTreeMap::new(),
epoch: utils::epoch(),
poisoned: None,
}
}
/// Create a new poisoned LogMetadata.
pub(crate) fn new_poisoned(message: &'static str) -> Self {
Self {
primary_len: 0,
indexes: BTreeMap::new(),
epoch: 0,
poisoned: Some(message),
}
}
}
@ -162,7 +132,7 @@ mod tests {
quickcheck! {
fn test_roundtrip_meta(primary_len: u64, indexes: BTreeMap<String, u64>, epoch: u64) -> bool {
let mut buf = Vec::new();
let meta = LogMetadata { primary_len, indexes, epoch, poisoned: None };
let meta = LogMetadata { primary_len, indexes, epoch, };
meta.write(&mut buf).expect("write");
let mut cur = Cursor::new(buf);
let meta_read = LogMetadata::read(&mut cur).expect("read");
@ -171,7 +141,7 @@ mod tests {
fn test_roundtrip_meta_file(primary_len: u64, indexes: BTreeMap<String, u64>, epoch: u64) -> bool {
let dir = tempdir().unwrap();
let meta = LogMetadata { primary_len, indexes, epoch, poisoned: None };
let meta = LogMetadata { primary_len, indexes, epoch, };
let path = dir.path().join("meta");
meta.write_file(&path, false).expect("write_file");
let meta_read = LogMetadata::read_file(&path).expect("read_file");

View File

@ -1140,18 +1140,12 @@ impl Log {
path: &GenericPath,
create: bool,
) -> crate::Result<LogMetadata> {
Self::load_or_create_meta_internal(path, create, false)
}
/// Used by MultiLog. Write a dummy "meta" file that prevents accidental reading.
pub(crate) fn load_or_create_shared_meta(path: &GenericPath) -> crate::Result<LogMetadata> {
Self::load_or_create_meta_internal(path, true, true)
Self::load_or_create_meta_internal(path, create)
}
pub(crate) fn load_or_create_meta_internal(
path: &GenericPath,
create: bool,
is_shared: bool,
) -> crate::Result<LogMetadata> {
match path.read_meta() {
Err(err) => {
@ -1169,25 +1163,6 @@ impl Log {
let meta = LogMetadata::new_with_primary_len(PRIMARY_START_OFFSET);
// An empty meta file is easy to recreate. No need to use fsync.
path.write_meta(&meta, false)?;
if is_shared {
// If meta is intended to be shared, write a poisoned one to the
// filesystem to prevent loading. But return the clean one.
// The filesystem layout looks like:
// - multilog/this-log/meta # poisoned
// - multilog/multimeta # the right one to use
let poisoned_meta = LogMetadata::new_poisoned(
"This Log is managed by MultiLog. Direct access is forbidden!",
);
if let GenericPath::SharedMeta { .. } = path {
// This path being poisoned should be the path being wrapped in
// a `GenericPath::SharedMeta`, not the shared path itself.
panic!("bug: GenericPath::SharedMeta shouldn't be used here.");
}
path.write_meta(&poisoned_meta, false)?;
} else {
// An empty meta file is easy to recreate. No need to use fsync.
path.write_meta(&meta, false)?;
}
Ok(meta)
} else {
Err(err)

View File

@ -113,8 +113,15 @@ impl GenericPath {
Ok(())
}
GenericPath::SharedMeta {
meta: shared_meta, ..
meta: shared_meta,
path,
} => {
// Update the normal "meta" file for easy investigation.
// The meta file is not used directly, though.
if let GenericPath::Filesystem(dir) = path.as_ref() {
let meta_path = dir.join(META_FILE);
meta.write_file(&meta_path, fsync)?;
}
let mut shared_meta = shared_meta.lock().unwrap();
*shared_meta = meta.clone();
Ok(())

View File

@ -110,7 +110,7 @@ impl OpenOptions {
if !multimeta.metas.contains_key(name_ref) {
// Create a new Log if it does not exist in MultiMeta.
utils::mkdir_p(&fspath)?;
let meta = log::Log::load_or_create_shared_meta(&fspath.as_path().into())?;
let meta = log::Log::load_or_create_meta(&fspath.as_path().into(), true)?;
let meta = Arc::new(Mutex::new(meta));
multimeta.metas.insert(name.to_string(), meta);
}
@ -322,27 +322,18 @@ mod tests {
}
#[test]
fn test_individual_log_cannot_be_opened_directly() {
fn test_individual_log_can_be_opened_directly() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path();
let mut mlog = simple_multilog(path);
assert_eq!(
log::OpenOptions::new()
.open(path.join("a"))
.unwrap_err()
.to_string()
.lines()
.last()
.unwrap(),
"- This Log is managed by MultiLog. Direct access is forbidden!"
);
log::OpenOptions::new().open(path.join("b")).unwrap_err();
log::OpenOptions::new().open(path.join("a")).unwrap();
log::OpenOptions::new().open(path.join("b")).unwrap();
// It's still an error after individual log flush.
// After flush - still readable.
mlog[0].append(b"1").unwrap();
mlog[0].flush().unwrap();
log::OpenOptions::new().open(path.join("a")).unwrap_err();
log::OpenOptions::new().open(path.join("a")).unwrap();
}
#[test]