##// 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 /// Censored revision data.
47 /// Censored revision data.
48 CensoredNodeError,
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 /// Details about where an I/O error happened
54 /// Details about where an I/O error happened
@@ -111,6 +114,9 b' impl fmt::Display for HgError {'
111 write!(f, "encountered a censored node")
114 write!(f, "encountered a censored node")
112 }
115 }
113 HgError::ConfigValueParseError(error) => error.fmt(f),
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 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,
@@ -307,14 +309,49 b' impl Repo {'
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 {
@@ -322,7 +359,7 b' impl Repo {'
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
@@ -347,22 +384,54 b' impl Repo {'
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 => {
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 } else {
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 let write_mode_config = self
437 let write_mode_config = self
@@ -196,8 +196,12 b' The status process should return a consi'
196 $ cat $TESTTMP/status-race-lock.log
196 $ cat $TESTTMP/status-race-lock.log
197 #else
197 #else
198 $ cat $TESTTMP/status-race-lock.out
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 $ cat $TESTTMP/status-race-lock.log
204 $ cat $TESTTMP/status-race-lock.log
200 abort: dirstate-v2 parse error: not enough bytes on disk
201 #endif
205 #endif
202 #endif
206 #endif
203 #else
207 #else
@@ -305,8 +309,10 b' The status process should return a consi'
305 $ cat $TESTTMP/status-race-lock.log
309 $ cat $TESTTMP/status-race-lock.log
306 #else
310 #else
307 $ cat $TESTTMP/status-race-lock.out
311 $ cat $TESTTMP/status-race-lock.out
312 ? dir/n
313 ? p
314 ? q
308 $ cat $TESTTMP/status-race-lock.log
315 $ cat $TESTTMP/status-race-lock.log
309 abort: dirstate-v2 parse error: not enough bytes on disk
310 #endif
316 #endif
311 #endif
317 #endif
312 #else
318 #else
@@ -441,8 +447,11 b' The status process should return a consi'
441 $ cat $TESTTMP/status-race-lock.log
447 $ cat $TESTTMP/status-race-lock.log
442 #else
448 #else
443 $ cat $TESTTMP/status-race-lock.out
449 $ cat $TESTTMP/status-race-lock.out
450 A dir/o
451 ? dir/n
452 ? p
453 ? q
444 $ cat $TESTTMP/status-race-lock.log
454 $ cat $TESTTMP/status-race-lock.log
445 abort: dirstate-v2 parse error: not enough bytes on disk
446 #endif
455 #endif
447 #endif
456 #endif
448 #else
457 #else
@@ -543,8 +552,12 b' The status process should return a consi'
543 $ cat $TESTTMP/status-race-lock.log
552 $ cat $TESTTMP/status-race-lock.log
544 #else
553 #else
545 $ cat $TESTTMP/status-race-lock.out
554 $ cat $TESTTMP/status-race-lock.out
555 A dir/o
556 R dir/nested/m
557 ? dir/n
558 ? p
559 ? q
546 $ cat $TESTTMP/status-race-lock.log
560 $ cat $TESTTMP/status-race-lock.log
547 abort: dirstate-v2 parse error: not enough bytes on disk
548 #endif
561 #endif
549 #endif
562 #endif
550 #else
563 #else
General Comments 0
You need to be logged in to leave comments. Login now