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,