rust/hg-core/src/repo.rs
changeset 50252 a6b8b1ab9116
parent 49937 750409505286
parent 50245 dbe09fb038fc
child 50658 1e2c6cda2309
equal deleted inserted replaced
50248:2fbc109fd58a 50252:a6b8b1ab9116
     1 use crate::changelog::Changelog;
     1 use crate::changelog::Changelog;
     2 use crate::config::{Config, ConfigError, ConfigParseError};
     2 use crate::config::{Config, ConfigError, ConfigParseError};
     3 use crate::dirstate::DirstateParents;
     3 use crate::dirstate::DirstateParents;
       
     4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
     4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
     5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
     5 use crate::dirstate_tree::owning::OwningDirstateMap;
     6 use crate::dirstate_tree::owning::OwningDirstateMap;
     6 use crate::errors::HgResultExt;
     7 use crate::errors::HgResultExt;
     7 use crate::errors::{HgError, IoResultExt};
     8 use crate::errors::{HgError, IoResultExt};
     8 use crate::lock::{try_with_lock_no_wait, LockError};
     9 use crate::lock::{try_with_lock_no_wait, LockError};
     9 use crate::manifest::{Manifest, Manifestlog};
    10 use crate::manifest::{Manifest, Manifestlog};
    10 use crate::revlog::filelog::Filelog;
    11 use crate::revlog::filelog::Filelog;
    11 use crate::revlog::RevlogError;
    12 use crate::revlog::RevlogError;
       
    13 use crate::utils::debug::debug_wait_for_file_or_print;
    12 use crate::utils::files::get_path_from_bytes;
    14 use crate::utils::files::get_path_from_bytes;
    13 use crate::utils::hg_path::HgPath;
    15 use crate::utils::hg_path::HgPath;
    14 use crate::utils::SliceExt;
    16 use crate::utils::SliceExt;
    15 use crate::vfs::{is_dir, is_file, Vfs};
    17 use crate::vfs::{is_dir, is_file, Vfs};
    16 use crate::{requirements, NodePrefix};
    18 use crate::{requirements, NodePrefix};
    20 use std::io::Seek;
    22 use std::io::Seek;
    21 use std::io::SeekFrom;
    23 use std::io::SeekFrom;
    22 use std::io::Write as IoWrite;
    24 use std::io::Write as IoWrite;
    23 use std::path::{Path, PathBuf};
    25 use std::path::{Path, PathBuf};
    24 
    26 
       
    27 const V2_MAX_READ_ATTEMPTS: usize = 5;
       
    28 
       
    29 type DirstateMapIdentity = (Option<u64>, Option<Vec<u8>>, usize);
       
    30 
    25 /// A repository on disk
    31 /// A repository on disk
    26 pub struct Repo {
    32 pub struct Repo {
    27     working_directory: PathBuf,
    33     working_directory: PathBuf,
    28     dot_hg: PathBuf,
    34     dot_hg: PathBuf,
    29     store: PathBuf,
    35     store: PathBuf,
    30     requirements: HashSet<String>,
    36     requirements: HashSet<String>,
    31     config: Config,
    37     config: Config,
    32     dirstate_parents: LazyCell<DirstateParents>,
    38     dirstate_parents: LazyCell<DirstateParents>,
    33     dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
       
    34     dirstate_map: LazyCell<OwningDirstateMap>,
    39     dirstate_map: LazyCell<OwningDirstateMap>,
    35     changelog: LazyCell<Changelog>,
    40     changelog: LazyCell<Changelog>,
    36     manifestlog: LazyCell<Manifestlog>,
    41     manifestlog: LazyCell<Manifestlog>,
    37 }
    42 }
    38 
    43 
   178             working_directory,
   183             working_directory,
   179             store: store_path,
   184             store: store_path,
   180             dot_hg,
   185             dot_hg,
   181             config: repo_config,
   186             config: repo_config,
   182             dirstate_parents: LazyCell::new(),
   187             dirstate_parents: LazyCell::new(),
   183             dirstate_data_file_uuid: LazyCell::new(),
       
   184             dirstate_map: LazyCell::new(),
   188             dirstate_map: LazyCell::new(),
   185             changelog: LazyCell::new(),
   189             changelog: LazyCell::new(),
   186             manifestlog: LazyCell::new(),
   190             manifestlog: LazyCell::new(),
   187         };
   191         };
   188 
   192 
   252             .read("dirstate")
   256             .read("dirstate")
   253             .io_not_found_as_none()?
   257             .io_not_found_as_none()?
   254             .unwrap_or_default())
   258             .unwrap_or_default())
   255     }
   259     }
   256 
   260 
       
   261     fn dirstate_identity(&self) -> Result<Option<u64>, HgError> {
       
   262         use std::os::unix::fs::MetadataExt;
       
   263         Ok(self
       
   264             .hg_vfs()
       
   265             .symlink_metadata("dirstate")
       
   266             .io_not_found_as_none()?
       
   267             .map(|meta| meta.ino()))
       
   268     }
       
   269 
   257     pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
   270     pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
   258         Ok(*self
   271         Ok(*self
   259             .dirstate_parents
   272             .dirstate_parents
   260             .get_or_init(|| self.read_dirstate_parents())?)
   273             .get_or_init(|| self.read_dirstate_parents())?)
   261     }
   274     }
   262 
   275 
   263     fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
   276     fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
   264         let dirstate = self.dirstate_file_contents()?;
   277         let dirstate = self.dirstate_file_contents()?;
   265         let parents = if dirstate.is_empty() {
   278         let parents = if dirstate.is_empty() {
   266             if self.has_dirstate_v2() {
       
   267                 self.dirstate_data_file_uuid.set(None);
       
   268             }
       
   269             DirstateParents::NULL
   279             DirstateParents::NULL
   270         } else if self.has_dirstate_v2() {
   280         } else if self.has_dirstate_v2() {
   271             let docket =
   281             let docket =
   272                 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
   282                 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
   273             self.dirstate_data_file_uuid
       
   274                 .set(Some(docket.uuid.to_owned()));
       
   275             docket.parents()
   283             docket.parents()
   276         } else {
   284         } else {
   277             *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
   285             *crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
   278         };
   286         };
   279         self.dirstate_parents.set(parents);
   287         self.dirstate_parents.set(parents);
   280         Ok(parents)
   288         Ok(parents)
   281     }
   289     }
   282 
   290 
   283     fn read_dirstate_data_file_uuid(
   291     /// Returns the information read from the dirstate docket necessary to
   284         &self,
   292     /// check if the data file has been updated/deleted by another process
   285     ) -> Result<Option<Vec<u8>>, HgError> {
   293     /// since we last read the dirstate.
       
   294     /// Namely, the inode, data file uuid and the data size.
       
   295     fn get_dirstate_data_file_integrity(
       
   296         &self,
       
   297     ) -> Result<DirstateMapIdentity, HgError> {
   286         assert!(
   298         assert!(
   287             self.has_dirstate_v2(),
   299             self.has_dirstate_v2(),
   288             "accessing dirstate data file ID without dirstate-v2"
   300             "accessing dirstate data file ID without dirstate-v2"
   289         );
   301         );
       
   302         // Get the identity before the contents since we could have a race
       
   303         // between the two. Having an identity that is too old is fine, but
       
   304         // one that is younger than the content change is bad.
       
   305         let identity = self.dirstate_identity()?;
   290         let dirstate = self.dirstate_file_contents()?;
   306         let dirstate = self.dirstate_file_contents()?;
   291         if dirstate.is_empty() {
   307         if dirstate.is_empty() {
   292             self.dirstate_parents.set(DirstateParents::NULL);
   308             self.dirstate_parents.set(DirstateParents::NULL);
   293             Ok(None)
   309             Ok((identity, None, 0))
   294         } else {
   310         } else {
   295             let docket =
   311             let docket =
   296                 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
   312                 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
   297             self.dirstate_parents.set(docket.parents());
   313             self.dirstate_parents.set(docket.parents());
   298             Ok(Some(docket.uuid.to_owned()))
   314             Ok((identity, Some(docket.uuid.to_owned()), docket.data_size()))
   299         }
   315         }
   300     }
   316     }
   301 
   317 
   302     fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
   318     fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
       
   319         if self.has_dirstate_v2() {
       
   320             // The v2 dirstate is split into a docket and a data file.
       
   321             // Since we don't always take the `wlock` to read it
       
   322             // (like in `hg status`), it is susceptible to races.
       
   323             // A simple retry method should be enough since full rewrites
       
   324             // only happen when too much garbage data is present and
       
   325             // this race is unlikely.
       
   326             let mut tries = 0;
       
   327 
       
   328             while tries < V2_MAX_READ_ATTEMPTS {
       
   329                 tries += 1;
       
   330                 match self.read_docket_and_data_file() {
       
   331                     Ok(m) => {
       
   332                         return Ok(m);
       
   333                     }
       
   334                     Err(e) => match e {
       
   335                         DirstateError::Common(HgError::RaceDetected(
       
   336                             context,
       
   337                         )) => {
       
   338                             log::info!(
       
   339                                 "dirstate read race detected {} (retry {}/{})",
       
   340                                 context,
       
   341                                 tries,
       
   342                                 V2_MAX_READ_ATTEMPTS,
       
   343                             );
       
   344                             continue;
       
   345                         }
       
   346                         _ => return Err(e),
       
   347                     },
       
   348                 }
       
   349             }
       
   350             let error = HgError::abort(
       
   351                 format!("dirstate read race happened {tries} times in a row"),
       
   352                 255,
       
   353                 None,
       
   354             );
       
   355             Err(DirstateError::Common(error))
       
   356         } else {
       
   357             debug_wait_for_file_or_print(
       
   358                 self.config(),
       
   359                 "dirstate.pre-read-file",
       
   360             );
       
   361             let identity = self.dirstate_identity()?;
       
   362             let dirstate_file_contents = self.dirstate_file_contents()?;
       
   363             if dirstate_file_contents.is_empty() {
       
   364                 self.dirstate_parents.set(DirstateParents::NULL);
       
   365                 Ok(OwningDirstateMap::new_empty(Vec::new()))
       
   366             } else {
       
   367                 let (map, parents) = OwningDirstateMap::new_v1(
       
   368                     dirstate_file_contents,
       
   369                     identity,
       
   370                 )?;
       
   371                 self.dirstate_parents.set(parents);
       
   372                 Ok(map)
       
   373             }
       
   374         }
       
   375     }
       
   376 
       
   377     fn read_docket_and_data_file(
       
   378         &self,
       
   379     ) -> Result<OwningDirstateMap, DirstateError> {
       
   380         debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
   303         let dirstate_file_contents = self.dirstate_file_contents()?;
   381         let dirstate_file_contents = self.dirstate_file_contents()?;
       
   382         let identity = self.dirstate_identity()?;
   304         if dirstate_file_contents.is_empty() {
   383         if dirstate_file_contents.is_empty() {
   305             self.dirstate_parents.set(DirstateParents::NULL);
   384             self.dirstate_parents.set(DirstateParents::NULL);
   306             if self.has_dirstate_v2() {
   385             return Ok(OwningDirstateMap::new_empty(Vec::new()));
   307                 self.dirstate_data_file_uuid.set(None);
   386         }
   308             }
   387         let docket = crate::dirstate_tree::on_disk::read_docket(
   309             Ok(OwningDirstateMap::new_empty(Vec::new()))
   388             &dirstate_file_contents,
   310         } else if self.has_dirstate_v2() {
   389         )?;
   311             let docket = crate::dirstate_tree::on_disk::read_docket(
   390         debug_wait_for_file_or_print(
   312                 &dirstate_file_contents,
   391             self.config(),
   313             )?;
   392             "dirstate.post-docket-read-file",
   314             self.dirstate_parents.set(docket.parents());
   393         );
   315             self.dirstate_data_file_uuid
   394         self.dirstate_parents.set(docket.parents());
   316                 .set(Some(docket.uuid.to_owned()));
   395         let uuid = docket.uuid.to_owned();
   317             let data_size = docket.data_size();
   396         let data_size = docket.data_size();
   318             let metadata = docket.tree_metadata();
   397 
   319             if let Some(data_mmap) = self
   398         let context = "between reading dirstate docket and data file";
       
   399         let race_error = HgError::RaceDetected(context.into());
       
   400         let metadata = docket.tree_metadata();
       
   401 
       
   402         let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
       
   403             // Don't mmap on NFS to prevent `SIGBUS` error on deletion
       
   404             let contents = self.hg_vfs().read(docket.data_filename());
       
   405             let contents = match contents {
       
   406                 Ok(c) => c,
       
   407                 Err(HgError::IoError { error, context }) => {
       
   408                     match error.raw_os_error().expect("real os error") {
       
   409                         // 2 = ENOENT, No such file or directory
       
   410                         // 116 = ESTALE, Stale NFS file handle
       
   411                         //
       
   412                         // TODO match on `error.kind()` when
       
   413                         // `ErrorKind::StaleNetworkFileHandle` is stable.
       
   414                         2 | 116 => {
       
   415                             // Race where the data file was deleted right after
       
   416                             // we read the docket, try again
       
   417                             return Err(race_error.into());
       
   418                         }
       
   419                         _ => {
       
   420                             return Err(
       
   421                                 HgError::IoError { error, context }.into()
       
   422                             )
       
   423                         }
       
   424                     }
       
   425                 }
       
   426                 Err(e) => return Err(e.into()),
       
   427             };
       
   428             OwningDirstateMap::new_v2(
       
   429                 contents, data_size, metadata, uuid, identity,
       
   430             )
       
   431         } else {
       
   432             match self
   320                 .hg_vfs()
   433                 .hg_vfs()
   321                 .mmap_open(docket.data_filename())
   434                 .mmap_open(docket.data_filename())
   322                 .io_not_found_as_none()?
   435                 .io_not_found_as_none()
   323             {
   436             {
   324                 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
   437                 Ok(Some(data_mmap)) => OwningDirstateMap::new_v2(
   325             } else {
   438                     data_mmap, data_size, metadata, uuid, identity,
   326                 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
   439                 ),
   327             }
   440                 Ok(None) => {
   328         } else {
   441                     // Race where the data file was deleted right after we
   329             let (map, parents) =
   442                     // read the docket, try again
   330                 OwningDirstateMap::new_v1(dirstate_file_contents)?;
   443                     return Err(race_error.into());
   331             self.dirstate_parents.set(parents);
   444                 }
   332             Ok(map)
   445                 Err(e) => return Err(e.into()),
   333         }
   446             }
       
   447         }?;
       
   448 
       
   449         let write_mode_config = self
       
   450             .config()
       
   451             .get_str(b"devel", b"dirstate.v2.data_update_mode")
       
   452             .unwrap_or(Some("auto"))
       
   453             .unwrap_or("auto"); // don't bother for devel options
       
   454         let write_mode = match write_mode_config {
       
   455             "auto" => DirstateMapWriteMode::Auto,
       
   456             "force-new" => DirstateMapWriteMode::ForceNewDataFile,
       
   457             "force-append" => DirstateMapWriteMode::ForceAppend,
       
   458             _ => DirstateMapWriteMode::Auto,
       
   459         };
       
   460 
       
   461         map.with_dmap_mut(|m| m.set_write_mode(write_mode));
       
   462 
       
   463         Ok(map)
   334     }
   464     }
   335 
   465 
   336     pub fn dirstate_map(
   466     pub fn dirstate_map(
   337         &self,
   467         &self,
   338     ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
   468     ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
   419         let map = self.dirstate_map()?;
   549         let map = self.dirstate_map()?;
   420         // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
   550         // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
   421         // it’s unset
   551         // it’s unset
   422         let parents = self.dirstate_parents()?;
   552         let parents = self.dirstate_parents()?;
   423         let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
   553         let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
   424             let uuid_opt = self
   554             let (identity, uuid, data_size) =
   425                 .dirstate_data_file_uuid
   555                 self.get_dirstate_data_file_integrity()?;
   426                 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
   556             let identity_changed = identity != map.old_identity();
   427             let uuid_opt = uuid_opt.as_ref();
   557             let uuid_changed = uuid.as_deref() != map.old_uuid();
   428             let can_append = uuid_opt.is_some();
   558             let data_length_changed = data_size != map.old_data_size();
       
   559 
       
   560             if identity_changed || uuid_changed || data_length_changed {
       
   561                 // If any of identity, uuid or length have changed since
       
   562                 // last disk read, don't write.
       
   563                 // This is fine because either we're in a command that doesn't
       
   564                 // write anything too important (like `hg status`), or we're in
       
   565                 // `hg add` and we're supposed to have taken the lock before
       
   566                 // reading anyway.
       
   567                 //
       
   568                 // TODO complain loudly if we've changed anything important
       
   569                 // without taking the lock.
       
   570                 // (see `hg help config.format.use-dirstate-tracked-hint`)
       
   571                 log::debug!(
       
   572                     "dirstate has changed since last read, not updating."
       
   573                 );
       
   574                 return Ok(());
       
   575             }
       
   576 
       
   577             let uuid_opt = map.old_uuid();
       
   578             let write_mode = if uuid_opt.is_some() {
       
   579                 DirstateMapWriteMode::Auto
       
   580             } else {
       
   581                 DirstateMapWriteMode::ForceNewDataFile
       
   582             };
   429             let (data, tree_metadata, append, old_data_size) =
   583             let (data, tree_metadata, append, old_data_size) =
   430                 map.pack_v2(can_append)?;
   584                 map.pack_v2(write_mode)?;
   431 
   585 
   432             // Reuse the uuid, or generate a new one, keeping the old for
   586             // Reuse the uuid, or generate a new one, keeping the old for
   433             // deletion.
   587             // deletion.
   434             let (uuid, old_uuid) = match uuid_opt {
   588             let (uuid, old_uuid) = match uuid_opt {
   435                 Some(uuid) => {
   589                 Some(uuid) => {
   468             //   revlog.py talks about a Solaris bug, but we also saw some ZFS
   622             //   revlog.py talks about a Solaris bug, but we also saw some ZFS
   469             //   bug: https://github.com/openzfs/zfs/pull/3124,
   623             //   bug: https://github.com/openzfs/zfs/pull/3124,
   470             //   https://github.com/openzfs/zfs/issues/13370
   624             //   https://github.com/openzfs/zfs/issues/13370
   471             //
   625             //
   472             if !append {
   626             if !append {
       
   627                 log::trace!("creating a new dirstate data file");
   473                 options.create_new(true);
   628                 options.create_new(true);
       
   629             } else {
       
   630                 log::trace!("appending to the dirstate data file");
   474             }
   631             }
   475 
   632 
   476             let data_size = (|| {
   633             let data_size = (|| {
   477                 // TODO: loop and try another random ID if !append and this
   634                 // TODO: loop and try another random ID if !append and this
   478                 // returns `ErrorKind::AlreadyExists`? Collision chance of two
   635                 // returns `ErrorKind::AlreadyExists`? Collision chance of two
   497                 HgError::corrupted("overflow in dirstate docket serialization")
   654                 HgError::corrupted("overflow in dirstate docket serialization")
   498             })?;
   655             })?;
   499 
   656 
   500             (packed_dirstate, old_uuid)
   657             (packed_dirstate, old_uuid)
   501         } else {
   658         } else {
       
   659             let identity = self.dirstate_identity()?;
       
   660             if identity != map.old_identity() {
       
   661                 // If identity changed since last disk read, don't write.
       
   662                 // This is fine because either we're in a command that doesn't
       
   663                 // write anything too important (like `hg status`), or we're in
       
   664                 // `hg add` and we're supposed to have taken the lock before
       
   665                 // reading anyway.
       
   666                 //
       
   667                 // TODO complain loudly if we've changed anything important
       
   668                 // without taking the lock.
       
   669                 // (see `hg help config.format.use-dirstate-tracked-hint`)
       
   670                 log::debug!(
       
   671                     "dirstate has changed since last read, not updating."
       
   672                 );
       
   673                 return Ok(());
       
   674             }
   502             (map.pack_v1(parents)?, None)
   675             (map.pack_v1(parents)?, None)
   503         };
   676         };
   504 
   677 
   505         let vfs = self.hg_vfs();
   678         let vfs = self.hg_vfs();
   506         vfs.atomic_write("dirstate", &packed_dirstate)?;
   679         vfs.atomic_write("dirstate", &packed_dirstate)?;