comparison rust/hg-core/src/revlog/mod.rs @ 52181:3d797007905d

rust: populate mmaps in a separate thread if possible Same rationale as b619ba39d10a.
author Raphaël Gomès <rgomes@octobus.net>
date Thu, 01 Aug 2024 11:27:20 +0200
parents 1da6995835b4
children bd8081e9fd62
comparison
equal deleted inserted replaced
52180:1032bb0ef365 52181:3d797007905d
444 } 444 }
445 } 445 }
446 446
447 type IndexData = Box<dyn Deref<Target = [u8]> + Send + Sync>; 447 type IndexData = Box<dyn Deref<Target = [u8]> + Send + Sync>;
448 448
449 /// TODO We should check for version 5.14+ at runtime, but we either should
450 /// add the `nix` dependency to get it efficiently, or vendor the code to read
451 /// both of which are overkill for such a feature. If we need this dependency
452 /// for more things later, we'll use it here too.
453 #[cfg(target_os = "linux")]
454 fn can_advise_populate_read() -> bool {
455 true
456 }
457
458 #[cfg(not(target_os = "linux"))]
459 fn can_advise_populate_read() -> bool {
460 false
461 }
462
463 /// Call `madvise` on the mmap with `MADV_POPULATE_READ` in a separate thread
464 /// to populate the mmap in the background for a small perf improvement.
465 #[cfg(target_os = "linux")]
466 fn advise_populate_read_mmap(mmap: &memmap2::Mmap) {
467 const MADV_POPULATE_READ: i32 = 22;
468
469 // This is fine because the mmap is still referenced for at least
470 // the duration of this function, and the kernel will reject any wrong
471 // address.
472 let ptr = mmap.as_ptr() as u64;
473 let len = mmap.len();
474
475 // Fire and forget. The `JoinHandle` returned by `spawn` is dropped right
476 // after the call, the thread is thus detached. We don't care about success
477 // or failure here.
478 std::thread::spawn(move || unsafe {
479 // mmap's pointer is always page-aligned on Linux. In the case of
480 // file-based mmap (which is our use-case), the length should be
481 // correct. If not, it's not a safety concern as the kernel will just
482 // ignore unmapped pages and return ENOMEM, which we will promptly
483 // ignore, because we don't care about any errors.
484 libc::madvise(ptr as *mut libc::c_void, len, MADV_POPULATE_READ);
485 });
486 }
487
488 #[cfg(not(target_os = "linux"))]
489 fn advise_populate_read_mmap(mmap: &memmap2::Mmap) {}
490
449 /// Open the revlog [`Index`] at `index_path`, through the `store_vfs` and the 491 /// Open the revlog [`Index`] at `index_path`, through the `store_vfs` and the
450 /// given `options`. This controls whether (and how) we `mmap` the index file, 492 /// given `options`. This controls whether (and how) we `mmap` the index file,
451 /// and returns an empty buffer if the index does not exist on disk. 493 /// and returns an empty buffer if the index does not exist on disk.
452 /// This is only used when doing pure-Rust work, in Python contexts this is 494 /// This is only used when doing pure-Rust work, in Python contexts this is
453 /// unused at the time of writing. 495 /// unused at the time of writing.
463 { 505 {
464 let size = store_vfs.file_size(&file)?; 506 let size = store_vfs.file_size(&file)?;
465 if size >= threshold { 507 if size >= threshold {
466 // TODO madvise populate read in a background thread 508 // TODO madvise populate read in a background thread
467 let mut mmap_options = MmapOptions::new(); 509 let mut mmap_options = MmapOptions::new();
468 // This does nothing on platforms where it's not defined 510 if !can_advise_populate_read() {
469 mmap_options.populate(); 511 // Fall back to populating in the main thread if
512 // post-creation advice is not supported.
513 // Does nothing on platforms where it's not defined.
514 mmap_options.populate();
515 }
470 // Safety is "enforced" by locks and assuming other 516 // Safety is "enforced" by locks and assuming other
471 // processes are well-behaved. If any misbehaving or 517 // processes are well-behaved. If any misbehaving or
472 // malicious process does touch the index, it could lead 518 // malicious process does touch the index, it could lead
473 // to corruption. This is somewhat inherent to file-based 519 // to corruption. This is somewhat inherent to file-based
474 // `mmap`, though some platforms have some ways of 520 // `mmap`, though some platforms have some ways of
475 // mitigating. 521 // mitigating.
476 // TODO linux: set the immutable flag with `chattr(1)`? 522 // TODO linux: set the immutable flag with `chattr(1)`?
477 let mmap = unsafe { mmap_options.map(&file) } 523 let mmap = unsafe { mmap_options.map(&file) }
478 .when_reading_file(index_path)?; 524 .when_reading_file(index_path)?;
525
526 if can_advise_populate_read() {
527 advise_populate_read_mmap(&mmap);
528 }
529
479 Some(Box::new(mmap) as IndexData) 530 Some(Box::new(mmap) as IndexData)
480 } else { 531 } else {
481 None 532 None
482 } 533 }
483 } else { 534 } else {