@@ -2382,6 +2382,7 @@ impl Drop for IndexRunLockGuard {
23822382 let _ = self.file.set_len(0);
23832383 let _ = self.file.rewind();
23842384 let _ = self.file.flush();
2385+ let _ = crate::search::asset_state::clear_index_run_lock_metadata_sidecar(&self._path);
23852386 let _ = self.file.unlock();
23862387 }
23872388}
@@ -2416,8 +2417,7 @@ impl IndexRunLockGuard {
24162417 self._path.display()
24172418 )
24182419 })?;
2419- writeln!(
2420- self.file,
2420+ let metadata = format!(
24212421 "pid={}\nstarted_at_ms={}\nupdated_at_ms={}\nlast_progress_at_ms={}\ndb_path={}\nmode={}\njob_id={}\njob_kind={}\nphase={}",
24222422 std::process::id(),
24232423 self.started_at_ms,
@@ -2428,14 +2428,19 @@ impl IndexRunLockGuard {
24282428 self.job_id,
24292429 self.job_kind.as_lock_value(),
24302430 mode.as_lock_value()
2431- )
2432- .with_context(|| format!("writing index-run metadata to {}", self._path.display()))?;
2431+ );
2432+ writeln!(self.file, "{metadata}")
2433+ .with_context(|| format!("writing index-run metadata to {}", self._path.display()))?;
24332434 self.file
24342435 .flush()
24352436 .with_context(|| format!("flushing index-run lock file {}", self._path.display()))?;
24362437 self.file
24372438 .sync_all()
24382439 .with_context(|| format!("syncing index-run lock file {}", self._path.display()))?;
2440+ crate::search::asset_state::write_index_run_lock_metadata_sidecar(
2441+ &self._path,
2442+ &format!("{metadata}\n"),
2443+ )?;
24392444 Ok(())
24402445 }
24412446
@@ -2565,11 +2570,28 @@ fn heartbeat_index_run_lock_with_lock_and_progress(
25652570 .map_err(|_| anyhow::anyhow!("index-run metadata write lock poisoned"))
25662571 })
25672572 .transpose()?;
2568- let lock_path = data_dir.join("index-run.lock" );
2573+ let lock_path = crate::search::asset_state::index_run_lock_path(data_dir );
25692574 let existing = match fs::read_to_string(&lock_path) {
25702575 Ok(contents) if !contents.is_empty() => contents,
25712576 Ok(_) => return Ok(()),
25722577 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
2578+ Err(err) if crate::search::asset_state::windows_lock_conflict(&err) => {
2579+ let sidecar_path =
2580+ crate::search::asset_state::index_run_lock_metadata_sidecar_path(&lock_path);
2581+ match fs::read_to_string(&sidecar_path) {
2582+ Ok(contents) if !contents.is_empty() => contents,
2583+ Ok(_) => return Ok(()),
2584+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
2585+ Err(err) => {
2586+ return Err(err).with_context(|| {
2587+ format!(
2588+ "reading index-run lock heartbeat sidecar {}",
2589+ sidecar_path.display()
2590+ )
2591+ });
2592+ }
2593+ }
2594+ }
25732595 Err(err) => {
25742596 return Err(err).with_context(|| {
25752597 format!("reading index-run lock heartbeat {}", lock_path.display())
@@ -2636,6 +2658,28 @@ fn heartbeat_index_run_lock_with_lock_and_progress(
26362658}
26372659
26382660fn write_index_run_lock_heartbeat_in_place(lock_path: &Path, refreshed: &str) -> Result<()> {
2661+ match write_index_run_lock_file_in_place(lock_path, refreshed) {
2662+ Ok(()) => {
2663+ crate::search::asset_state::write_index_run_lock_metadata_sidecar(lock_path, refreshed)
2664+ }
2665+ Err(err) => {
2666+ let lock_conflict = err.chain().any(|cause| {
2667+ cause
2668+ .downcast_ref::<std::io::Error>()
2669+ .is_some_and(crate::search::asset_state::windows_lock_conflict)
2670+ });
2671+ if lock_conflict {
2672+ crate::search::asset_state::write_index_run_lock_metadata_sidecar(
2673+ lock_path, refreshed,
2674+ )
2675+ } else {
2676+ Err(err)
2677+ }
2678+ }
2679+ }
2680+ }
2681+
2682+ fn write_index_run_lock_file_in_place(lock_path: &Path, refreshed: &str) -> Result<()> {
26392683 // Do not temp-file+rename `index-run.lock`: POSIX advisory locks attach
26402684 // to the existing file inode/open handle, and replacing the path would
26412685 // let another process lock a fresh inode while this process still holds
0 commit comments