Mercurial > public > mercurial-scm > hg
comparison rust/hg-core/src/repo.rs @ 49202:2d0e22171ef9 stable
rhg: align the dirstate v2 writing algorithm with python
Use the same algorithm of file append as python does, where we do a manual
seek instead of relying on O_APPEND. (see the reasons in the inline comment)
author | Arseniy Alekseyev <aalekseyev@janestreet.com> |
---|---|
date | Thu, 19 May 2022 12:23:38 +0100 |
parents | f2ef6a4f918f |
children | 13dfad0f9f7a |
comparison
equal
deleted
inserted
replaced
49201:c29e79d11b01 | 49202:2d0e22171ef9 |
---|---|
462 }; | 462 }; |
463 | 463 |
464 let data_filename = format!("dirstate.{}", uuid); | 464 let data_filename = format!("dirstate.{}", uuid); |
465 let data_filename = self.hg_vfs().join(data_filename); | 465 let data_filename = self.hg_vfs().join(data_filename); |
466 let mut options = std::fs::OpenOptions::new(); | 466 let mut options = std::fs::OpenOptions::new(); |
467 if append { | 467 options.write(true); |
468 options.append(true); | 468 |
469 } else { | 469 // Why are we not using the O_APPEND flag when appending? |
470 options.write(true).create_new(true); | 470 // |
471 } | 471 // - O_APPEND makes it trickier to deal with garbage at the end of |
472 // the file, left by a previous uncommitted transaction. By | |
473 // starting the write at [old_data_size] we make sure we erase | |
474 // all such garbage. | |
475 // | |
476 // - O_APPEND requires to special-case 0-byte writes, whereas we | |
477 // don't need that. | |
478 // | |
479 // - Some OSes have bugs in implementation O_APPEND: | |
480 // revlog.py talks about a Solaris bug, but we also saw some ZFS | |
481 // bug: https://github.com/openzfs/zfs/pull/3124, | |
482 // https://github.com/openzfs/zfs/issues/13370 | |
483 // | |
484 if !append { | |
485 options.create_new(true); | |
486 } | |
487 | |
472 let data_size = (|| { | 488 let data_size = (|| { |
473 // TODO: loop and try another random ID if !append and this | 489 // TODO: loop and try another random ID if !append and this |
474 // returns `ErrorKind::AlreadyExists`? Collision chance of two | 490 // returns `ErrorKind::AlreadyExists`? Collision chance of two |
475 // random IDs is one in 2**32 | 491 // random IDs is one in 2**32 |
476 let mut file = options.open(&data_filename)?; | 492 let mut file = options.open(&data_filename)?; |
477 if data.is_empty() { | 493 if append { |
478 // If we're not appending anything, the data size is the | 494 file.seek(SeekFrom::Start(old_data_size as u64))?; |
479 // same as in the previous docket. It is *not* the file | |
480 // length, since it could have garbage at the end. | |
481 // We don't have to worry about it when we do have data | |
482 // to append since we rewrite the root node in this case. | |
483 Ok(old_data_size as u64) | |
484 } else { | |
485 file.write_all(&data)?; | |
486 file.flush()?; | |
487 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+ | |
488 file.seek(SeekFrom::Current(0)) | |
489 } | 495 } |
496 file.write_all(&data)?; | |
497 file.flush()?; | |
498 file.seek(SeekFrom::Current(0)) | |
490 })() | 499 })() |
491 .when_writing_file(&data_filename)?; | 500 .when_writing_file(&data_filename)?; |
492 | 501 |
493 let packed_dirstate = DirstateDocket::serialize( | 502 let packed_dirstate = DirstateDocket::serialize( |
494 parents, | 503 parents, |