##// END OF EJS Templates
dirstate: deal with read-race for pure rust code path (rhg)...
marmoute -
r51134:491f3dd0 stable
parent child Browse files
Show More
@@ -46,6 +46,9 b' pub enum HgError {'
46 46
47 47 /// Censored revision data.
48 48 CensoredNodeError,
49 /// A race condition has been detected. This *must* be handled locally
50 /// and not directly surface to the user.
51 RaceDetected(String),
49 52 }
50 53
51 54 /// Details about where an I/O error happened
@@ -111,6 +114,9 b' impl fmt::Display for HgError {'
111 114 write!(f, "encountered a censored node")
112 115 }
113 116 HgError::ConfigValueParseError(error) => error.fmt(f),
117 HgError::RaceDetected(context) => {
118 write!(f, "encountered a race condition {context}")
119 }
114 120 }
115 121 }
116 122 }
@@ -24,6 +24,8 b' use std::io::SeekFrom;'
24 24 use std::io::Write as IoWrite;
25 25 use std::path::{Path, PathBuf};
26 26
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28
27 29 /// A repository on disk
28 30 pub struct Repo {
29 31 working_directory: PathBuf,
@@ -307,14 +309,49 b' impl Repo {'
307 309
308 310 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
309 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 348 } else {
312 349 debug_wait_for_file_or_print(
313 350 self.config(),
314 351 "dirstate.pre-read-file",
315 352 );
316 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 355 self.dirstate_parents.set(DirstateParents::NULL);
319 356 Ok(OwningDirstateMap::new_empty(Vec::new()))
320 357 } else {
@@ -322,7 +359,7 b' impl Repo {'
322 359 OwningDirstateMap::new_v1(dirstate_file_contents)?;
323 360 self.dirstate_parents.set(parents);
324 361 Ok(map)
325 }
362 };
326 363 }
327 364 }
328 365
@@ -347,22 +384,54 b' impl Repo {'
347 384 self.dirstate_data_file_uuid
348 385 .set(Some(docket.uuid.to_owned()));
349 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 390 let metadata = docket.tree_metadata();
391
351 392 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
352 393 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
353 OwningDirstateMap::new_v2(
354 self.hg_vfs().read(docket.data_filename())?,
355 data_size,
356 metadata,
357 )
358 } else if let Some(data_mmap) = self
359 .hg_vfs()
360 .mmap_open(docket.data_filename())
361 .io_not_found_as_none()?
362 {
363 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
394 let contents = self.hg_vfs().read(docket.data_filename());
395 let contents = match contents {
396 Ok(c) => c,
397 Err(HgError::IoError { error, context }) => {
398 match error.raw_os_error().expect("real os error") {
399 // 2 = ENOENT, No such file or directory
400 // 116 = ESTALE, Stale NFS file handle
401 //
402 // TODO match on `error.kind()` when
403 // `ErrorKind::StaleNetworkFileHandle` is stable.
404 2 | 116 => {
405 // Race where the data file was deleted right after
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)
364 419 } else {
365 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
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 437 let write_mode_config = self
@@ -196,8 +196,12 b' The status process should return a consi'
196 196 $ cat $TESTTMP/status-race-lock.log
197 197 #else
198 198 $ cat $TESTTMP/status-race-lock.out
199 A dir/n
200 A dir/o
201 R dir/nested/m
202 ? p
203 ? q
199 204 $ cat $TESTTMP/status-race-lock.log
200 abort: dirstate-v2 parse error: not enough bytes on disk
201 205 #endif
202 206 #endif
203 207 #else
@@ -305,8 +309,10 b' The status process should return a consi'
305 309 $ cat $TESTTMP/status-race-lock.log
306 310 #else
307 311 $ cat $TESTTMP/status-race-lock.out
312 ? dir/n
313 ? p
314 ? q
308 315 $ cat $TESTTMP/status-race-lock.log
309 abort: dirstate-v2 parse error: not enough bytes on disk
310 316 #endif
311 317 #endif
312 318 #else
@@ -441,8 +447,11 b' The status process should return a consi'
441 447 $ cat $TESTTMP/status-race-lock.log
442 448 #else
443 449 $ cat $TESTTMP/status-race-lock.out
450 A dir/o
451 ? dir/n
452 ? p
453 ? q
444 454 $ cat $TESTTMP/status-race-lock.log
445 abort: dirstate-v2 parse error: not enough bytes on disk
446 455 #endif
447 456 #endif
448 457 #else
@@ -543,8 +552,12 b' The status process should return a consi'
543 552 $ cat $TESTTMP/status-race-lock.log
544 553 #else
545 554 $ cat $TESTTMP/status-race-lock.out
555 A dir/o
556 R dir/nested/m
557 ? dir/n
558 ? p
559 ? q
546 560 $ cat $TESTTMP/status-race-lock.log
547 abort: dirstate-v2 parse error: not enough bytes on disk
548 561 #endif
549 562 #endif
550 563 #else
General Comments 0
You need to be logged in to leave comments. Login now