Skip to content

Commit d71e6f1

Browse files
committed
fix: handle truncation while assigning initial offsets
Initial offsets never considered the current state of the file, instead just preferring to use the state saved offsets or defaulting to the value configured by the `MZ_LOOKBACK` option. This means truncation is handled downstream where the agent doesn't have enough contextual information to appropriately handle the case and potentially dropping logs. This change causes initial offsets to be ignored if they exceed the current length of the file, and instead use the default values. Ref: LOG-17217 Signed-off-by: Jacob Hull <jacob@planethull.com>
1 parent 5ff60e9 commit d71e6f1

File tree

3 files changed

+94
-6
lines changed

3 files changed

+94
-6
lines changed

bin/tests/it/cli.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,79 @@ fn test_append_and_move() {
262262
agent_handle.kill().expect("Could not kill process");
263263
}
264264

265+
#[tokio::test]
266+
#[cfg_attr(not(feature = "integration_tests"), ignore)]
267+
#[cfg(any(target_os = "windows", target_os = "linux"))]
268+
async fn test_reuse_inode() {
269+
let dir1 = tempdir().expect("Could not create temp dir").into_path();
270+
let dir2 = tempdir().expect("Could not create temp dir").into_path();
271+
let file_path = dir1.join("file1.log");
272+
let mv_path = dir2.join("file1.log");
273+
let total_lines = 500;
274+
275+
let db_dir = tempdir().unwrap().into_path();
276+
let (server, received, shutdown_handle, addr) = common::start_http_ingester();
277+
278+
let mut settings = AgentSettings::with_mock_ingester(dir1.to_str().unwrap(), &addr);
279+
settings.state_db_dir = Some(&db_dir);
280+
settings.lookback = Some("start");
281+
settings.exclusion_regex = Some(r"/var\w*");
282+
settings.log_level = Some("info,fs::cache=trace");
283+
284+
let (server_result, _) = tokio::join!(server, async {
285+
let mut agent_handle = common::spawn_agent(settings);
286+
let mut stderr_reader = BufReader::new(agent_handle.stderr.take().unwrap());
287+
288+
let _ = OpenOptions::new()
289+
.append(true)
290+
.create(true)
291+
.open(&file_path)
292+
.unwrap();
293+
common::wait_for_file_event("initialize", &file_path, &mut stderr_reader);
294+
common::append_to_file(&file_path, 300, 300).expect("Could not append");
295+
common::force_client_to_flush(&dir1).await;
296+
297+
// Move the file and truncate+append to simulate creating a new file with the same inode
298+
fs::rename(&file_path, &mv_path).expect("Could not rename file");
299+
common::truncate_file(&mv_path).expect("Could not truncate file");
300+
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
301+
common::append_to_file(&mv_path, 200, 200).expect("Could not append");
302+
fs::rename(&mv_path, &file_path).expect("Could not rename file");
303+
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
304+
305+
common::force_client_to_flush(&dir1).await;
306+
consume_output(stderr_reader.into_inner());
307+
308+
// Wait for agent to process lines
309+
tokio::time::sleep(tokio::time::Duration::from_millis(1_500)).await;
310+
311+
let mut file_info = FileInfo::default();
312+
let map_key = file_path.to_str().unwrap();
313+
314+
for _ in 0..10 {
315+
let map = received.lock().await;
316+
file_info = map.get(map_key).unwrap_or(&file_info).clone();
317+
// Avoid awaiting while holding the lock
318+
drop(map);
319+
320+
if file_info.lines < total_lines {
321+
// Wait for the data to be received by the mock ingester
322+
tokio::time::sleep(Duration::from_millis(500)).await;
323+
continue;
324+
}
325+
break;
326+
}
327+
328+
assert_eq!(file_info.values.len(), total_lines);
329+
330+
common::assert_agent_running(&mut agent_handle);
331+
332+
agent_handle.kill().expect("Could not kill process");
333+
shutdown_handle();
334+
});
335+
server_result.unwrap()
336+
}
337+
265338
#[test]
266339
#[cfg_attr(not(feature = "integration_tests"), ignore)]
267340
#[cfg(any(target_os = "windows", target_os = "linux"))]

bin/tests/it/common.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,9 @@ pub async fn force_client_to_flush(dir_path: &Path) {
6262
#[cfg(not(target_os = "macos"))]
6363
pub fn truncate_file(file_path: &Path) -> Result<(), std::io::Error> {
6464
OpenOptions::new()
65-
.read(true)
6665
.write(true)
67-
.open(file_path)?
68-
.set_len(0)?;
66+
.truncate(true)
67+
.open(file_path)?;
6968
Ok(())
7069
}
7170

common/fs/src/cache/mod.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -867,11 +867,11 @@ impl FileSystem {
867867
None
868868
}
869869
}
870+
let file_len = path.metadata().map(|m| m.len()).unwrap_or(0);
870871
let default_offset = match self.lookback_config {
871872
Lookback::Start => SpanVec::new(),
872873
Lookback::SmallFiles => {
873874
// Check the actual file len
874-
let file_len = path.metadata().map(|m| m.len()).unwrap_or(0);
875875
let smallfiles_offset = if file_len < 8192 {
876876
SpanVec::new()
877877
} else {
@@ -885,7 +885,6 @@ impl FileSystem {
885885
.unwrap_or_default(),
886886
Lookback::Tail => {
887887
let mut should_lookback = false;
888-
let file_len = path.metadata().map(|m| m.len()).unwrap_or(0);
889888

890889
if let Ok(metadata) = path.metadata() {
891890
if let Ok(file_create_time) = metadata.created() {
@@ -908,7 +907,24 @@ impl FileSystem {
908907
tail_offset
909908
}
910909
};
911-
_lookup_offset(&self.initial_offsets, &inode, path).unwrap_or(default_offset)
910+
911+
let offsets = match _lookup_offset(&self.initial_offsets, &inode, path) {
912+
Some(saved_offset) => {
913+
if saved_offset[0].end > file_len {
914+
warn!("inconsistent saved offsets");
915+
default_offset
916+
} else {
917+
saved_offset
918+
}
919+
}
920+
None => default_offset,
921+
};
922+
923+
info!(
924+
"initializing offset for {:?} to {:?} ({})",
925+
path, offsets, self.lookback_config
926+
);
927+
offsets
912928
}
913929

914930
pub fn resolve_valid_paths(&self, entry: &Entry, entries: &EntryMap) -> SmallVec<[PathBuf; 4]> {

0 commit comments

Comments
 (0)