mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
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:
parent
4947e07cb7
commit
f79e7657af
@ -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");
|
||||
|
@ -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)
|
||||
|
@ -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(())
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user