comparison rust/hg-core/src/repo.rs @ 50239:491f3dd080eb stable

dirstate: deal with read-race for pure rust code path (rhg) If we cannot read the dirstate data, this is probably because a writing process wrote it under our feet. So refresh the docket and try again a handful of time.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 28 Feb 2023 19:36:46 +0100
parents 2be6d5782728
children 6cce0afc1454
comparison
equal deleted inserted replaced
50238:c9066fc609ef 50239:491f3dd080eb
22 use std::io::Seek; 22 use std::io::Seek;
23 use std::io::SeekFrom; 23 use std::io::SeekFrom;
24 use std::io::Write as IoWrite; 24 use std::io::Write as IoWrite;
25 use std::path::{Path, PathBuf}; 25 use std::path::{Path, PathBuf};
26 26
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28
27 /// A repository on disk 29 /// A repository on disk
28 pub struct Repo { 30 pub struct Repo {
29 working_directory: PathBuf, 31 working_directory: PathBuf,
30 dot_hg: PathBuf, 32 dot_hg: PathBuf,
31 store: PathBuf, 33 store: PathBuf,
305 } 307 }
306 } 308 }
307 309
308 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> { 310 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
309 if self.has_dirstate_v2() { 311 if self.has_dirstate_v2() {
310 self.read_docket_and_data_file() 312 // The v2 dirstate is split into a docket and a data file.
313 // Since we don't always take the `wlock` to read it
314 // (like in `hg status`), it is susceptible to races.
315 // A simple retry method should be enough since full rewrites
316 // only happen when too much garbage data is present and
317 // this race is unlikely.
318 let mut tries = 0;
319
320 while tries < V2_MAX_READ_ATTEMPTS {
321 tries += 1;
322 match self.read_docket_and_data_file() {
323 Ok(m) => {
324 return Ok(m);
325 }
326 Err(e) => match e {
327 DirstateError::Common(HgError::RaceDetected(
328 context,
329 )) => {
330 log::info!(
331 "dirstate read race detected {} (retry {}/{})",
332 context,
333 tries,
334 V2_MAX_READ_ATTEMPTS,
335 );
336 continue;
337 }
338 _ => return Err(e.into()),
339 },
340 }
341 }
342 let error = HgError::abort(
343 format!("dirstate read race happened {tries} times in a row"),
344 255,
345 None,
346 );
347 return Err(DirstateError::Common(error));
311 } else { 348 } else {
312 debug_wait_for_file_or_print( 349 debug_wait_for_file_or_print(
313 self.config(), 350 self.config(),
314 "dirstate.pre-read-file", 351 "dirstate.pre-read-file",
315 ); 352 );
316 let dirstate_file_contents = self.dirstate_file_contents()?; 353 let dirstate_file_contents = self.dirstate_file_contents()?;
317 if dirstate_file_contents.is_empty() { 354 return if dirstate_file_contents.is_empty() {
318 self.dirstate_parents.set(DirstateParents::NULL); 355 self.dirstate_parents.set(DirstateParents::NULL);
319 Ok(OwningDirstateMap::new_empty(Vec::new())) 356 Ok(OwningDirstateMap::new_empty(Vec::new()))
320 } else { 357 } else {
321 let (map, parents) = 358 let (map, parents) =
322 OwningDirstateMap::new_v1(dirstate_file_contents)?; 359 OwningDirstateMap::new_v1(dirstate_file_contents)?;
323 self.dirstate_parents.set(parents); 360 self.dirstate_parents.set(parents);
324 Ok(map) 361 Ok(map)
325 } 362 };
326 } 363 }
327 } 364 }
328 365
329 fn read_docket_and_data_file( 366 fn read_docket_and_data_file(
330 &self, 367 &self,
345 ); 382 );
346 self.dirstate_parents.set(docket.parents()); 383 self.dirstate_parents.set(docket.parents());
347 self.dirstate_data_file_uuid 384 self.dirstate_data_file_uuid
348 .set(Some(docket.uuid.to_owned())); 385 .set(Some(docket.uuid.to_owned()));
349 let data_size = docket.data_size(); 386 let data_size = docket.data_size();
387
388 let context = "between reading dirstate docket and data file";
389 let race_error = HgError::RaceDetected(context.into());
350 let metadata = docket.tree_metadata(); 390 let metadata = docket.tree_metadata();
391
351 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) { 392 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
352 // Don't mmap on NFS to prevent `SIGBUS` error on deletion 393 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
353 OwningDirstateMap::new_v2( 394 let contents = self.hg_vfs().read(docket.data_filename());
354 self.hg_vfs().read(docket.data_filename())?, 395 let contents = match contents {
355 data_size, 396 Ok(c) => c,
356 metadata, 397 Err(HgError::IoError { error, context }) => {
357 ) 398 match error.raw_os_error().expect("real os error") {
358 } else if let Some(data_mmap) = self 399 // 2 = ENOENT, No such file or directory
359 .hg_vfs() 400 // 116 = ESTALE, Stale NFS file handle
360 .mmap_open(docket.data_filename()) 401 //
361 .io_not_found_as_none()? 402 // TODO match on `error.kind()` when
362 { 403 // `ErrorKind::StaleNetworkFileHandle` is stable.
363 OwningDirstateMap::new_v2(data_mmap, data_size, metadata) 404 2 | 116 => {
364 } else { 405 // Race where the data file was deleted right after
365 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata) 406 // we read the docket, try again
407 return Err(race_error.into());
408 }
409 _ => {
410 return Err(
411 HgError::IoError { error, context }.into()
412 )
413 }
414 }
415 }
416 Err(e) => return Err(e.into()),
417 };
418 OwningDirstateMap::new_v2(contents, data_size, metadata)
419 } else {
420 match self
421 .hg_vfs()
422 .mmap_open(docket.data_filename())
423 .io_not_found_as_none()
424 {
425 Ok(Some(data_mmap)) => {
426 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
427 }
428 Ok(None) => {
429 // Race where the data file was deleted right after we
430 // read the docket, try again
431 return Err(race_error.into());
432 }
433 Err(e) => return Err(e.into()),
434 }
366 }?; 435 }?;
367 436
368 let write_mode_config = self 437 let write_mode_config = self
369 .config() 438 .config()
370 .get_str(b"devel", b"dirstate.v2.data_update_mode") 439 .get_str(b"devel", b"dirstate.v2.data_update_mode")