mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-10 10:20:47 -06:00
#3357: preserve concurrent session log identity
This commit is contained in:
@@ -182,7 +182,7 @@ fn clean_exit(input: &mut impl Read) -> io::Result<bool> {
|
||||
}
|
||||
|
||||
enum SessionLog {
|
||||
Captured(File),
|
||||
Captured { file: File, marker: String },
|
||||
Unavailable(String),
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ impl SessionLog {
|
||||
Ok(file)
|
||||
})();
|
||||
match opened {
|
||||
Ok(file) => Self::Captured(file),
|
||||
Ok(file) => Self::Captured { file, marker: format!("{}\n", session_marker(id)) },
|
||||
Err(e) => Self::Unavailable(e.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,15 @@ impl SessionLog {
|
||||
fn write_tail(&mut self, report: &mut File) -> io::Result<()> {
|
||||
match self {
|
||||
Self::Unavailable(reason) => writeln!(report, "Recent log unavailable: {reason}"),
|
||||
Self::Captured(file) => {
|
||||
Self::Captured { file, marker } => {
|
||||
// An older concurrent launcher may have truncated the captured file.
|
||||
// Recheck its identity before copying any content into this report.
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
let mut header = [0u8; HEADER_BYTES];
|
||||
let prefix = header.get_mut(..marker.len()).ok_or_else(|| io::Error::other("session marker too long"))?;
|
||||
if file.read_exact(prefix).is_err() || prefix != marker.as_bytes() {
|
||||
return writeln!(report, "Recent log unavailable: session identity changed");
|
||||
}
|
||||
let size = file.metadata()?.len();
|
||||
file.seek(SeekFrom::Start(size.saturating_sub(LOG_BYTES as u64)))?;
|
||||
writeln!(report, "\nRecorded session log tail (last {LOG_BYTES} bytes at most; logging filters/size caps may omit later events):")?;
|
||||
@@ -350,6 +358,21 @@ mod tests {
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captured_log_rejects_a_session_overwritten_after_open() {
|
||||
let dir = folder();
|
||||
let path = dir.join("log");
|
||||
std::fs::write(&path, "crash-report session 1-2\noriginal session\n").unwrap();
|
||||
let mut log = SessionLog::open(Some(&path), "1-2");
|
||||
std::fs::write(&path, "crash-report session 3-4\nprivate other session\n").unwrap();
|
||||
let report = write_report(&dir, "1-2", &mut log, None).unwrap();
|
||||
let text = std::fs::read_to_string(report).unwrap();
|
||||
assert!(text.contains("Recent log unavailable"));
|
||||
assert!(!text.contains("private other session"));
|
||||
assert!(!text.contains("session 3-4"));
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_identity_requires_the_exact_first_line() {
|
||||
let dir = folder();
|
||||
|
||||
@@ -214,10 +214,16 @@ fn rotate(dir: &Path) -> std::io::Result<(PathBuf, File)> {
|
||||
}
|
||||
}
|
||||
let path = numbered(dir, 0);
|
||||
let file = File::create(&path)?;
|
||||
let file = create_session_file(&path)?;
|
||||
Ok((path, file))
|
||||
}
|
||||
|
||||
// Another process may create the current log after our rotation. Never truncate its
|
||||
// file: its writer and crash reporter may already hold the same inode open.
|
||||
fn create_session_file(path: &Path) -> std::io::Result<File> {
|
||||
std::fs::OpenOptions::new().write(true).create_new(true).open(path)
|
||||
}
|
||||
|
||||
/// Rotate the session logs in `dir` (the `Logs` folder, see [`log_dir`]) and write records to
|
||||
/// a fresh `effectcraft.log` there, first the ones logged so far; returns its path.
|
||||
pub fn attach_session_log(dir: &Path) -> Result<PathBuf, String> {
|
||||
@@ -428,6 +434,20 @@ mod tests {
|
||||
assert_eq!(timestamp(u64::MAX), "584556019-04-03T14:25:51.615Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_session_creation_does_not_truncate_the_winner() {
|
||||
let dir = temp_dir("concurrent-create");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join(SESSION_LOG);
|
||||
let mut winner = create_session_file(&path).unwrap();
|
||||
winner.write_all(b"crash-report session 1-2\nwinner content\n").unwrap();
|
||||
assert_eq!(create_session_file(&path).unwrap_err().kind(), std::io::ErrorKind::AlreadyExists);
|
||||
winner.write_all(b"still the same session\n").unwrap();
|
||||
assert_eq!(read(&path), "crash-report session 1-2\nwinner content\nstill the same session\n");
|
||||
drop(winner);
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
/// Each launch keeps the previous runs' logs, so the log of a run that crashed survives.
|
||||
#[test]
|
||||
fn rotation_keeps_the_previous_logs_and_starts_an_empty_file() {
|
||||
|
||||
Reference in New Issue
Block a user