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") |