Mercurial > public > mercurial-scm > hg
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") |