Skip to content

Commit

Permalink
Make it possible to return read guards with packed buffers (#263)
Browse files Browse the repository at this point in the history
This is all thanks to the magic of ref-guard mapping, which indeed
transfers pretty well into the land of single-threads.
  • Loading branch information
Byron committed Nov 27, 2021
1 parent 0e18753 commit f5c3c8f
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 20 deletions.
67 changes: 58 additions & 9 deletions git-features/src/threading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ mod _impl {
pub type MutableOnDemand<T> = parking_lot::RwLock<T>;
/// A synchronization primitive which provides read-write access right away.
pub type Mutable<T> = parking_lot::Mutex<T>;
/// A guarded reference suitable for safekeeping in a struct.
pub type RefGuard<'a, T> = parking_lot::RwLockReadGuard<'a, T>;
/// A mapped reference created from a RefGuard
pub type MappedRefGuard<'a, U> = parking_lot::MappedRwLockReadGuard<'a, U>;

/// Get an upgradable shared reference through a [`MutableOnDemand`] for read-only access.
///
Expand All @@ -21,7 +25,7 @@ mod _impl {
}

/// Get a shared reference through a [`MutableOnDemand`] for read-only access.
pub fn get_ref<T>(v: &MutableOnDemand<T>) -> parking_lot::RwLockReadGuard<'_, T> {
pub fn get_ref<T>(v: &MutableOnDemand<T>) -> RefGuard<'_, T> {
v.read()
}

Expand All @@ -36,11 +40,34 @@ mod _impl {
}

/// Upgrade a handle previously obtained with [`get_ref_upgradeable()`] to support mutation.
pub fn upgrade_ref_to_mut<T>(
v: parking_lot::RwLockUpgradableReadGuard<'_, T>,
) -> parking_lot::RwLockWriteGuard<'_, T> {
pub fn upgrade_ref_to_mut<'a, T>(
v: parking_lot::RwLockUpgradableReadGuard<'a, T>,
_orig: &'a MutableOnDemand<T>,
) -> parking_lot::RwLockWriteGuard<'a, T> {
parking_lot::RwLockUpgradableReadGuard::upgrade(v)
}

/// Downgrade a handle previously obtained with [`upgrade_ref_to_mut()`] to drop mutation support.
pub fn downgrade_mut_to_ref<'a, T>(
v: parking_lot::RwLockWriteGuard<'a, T>,
_orig: &'a MutableOnDemand<T>,
) -> RefGuard<'a, T> {
parking_lot::RwLockWriteGuard::downgrade(v)
}

/// Map a read guard into a sub-type it contains.
pub fn map_ref<T, U: ?Sized>(v: RefGuard<'_, T>, f: impl FnOnce(&T) -> &U) -> MappedRefGuard<'_, U> {
parking_lot::RwLockReadGuard::map(v, f)
}

/// Map an upgradable read guard into a sub-type it contains.
pub fn map_upgradable_ref<T, U: ?Sized>(
v: parking_lot::RwLockUpgradableReadGuard<'_, T>,
f: impl FnOnce(&T) -> &U,
) -> MappedRefGuard<'_, U> {
let v = parking_lot::RwLockUpgradableReadGuard::downgrade(v);
parking_lot::RwLockReadGuard::map(v, f)
}
}

#[cfg(not(feature = "parallel"))]
Expand All @@ -56,12 +83,16 @@ mod _impl {
pub type MutableOnDemand<T> = RefCell<T>;
/// A synchronization primitive which provides read-write access right away.
pub type Mutable<T> = RefCell<T>;
/// A guarded reference suitable for safekeeping in a struct.
pub type RefGuard<'a, T> = Ref<'a, T>;
/// A mapped reference created from a RefGuard
pub type MappedRefGuard<'a, U> = Ref<'a, U>;

/// Get an upgradable shared reference through a [`MutableOnDemand`] for read-only access.
///
/// This access can be upgraded using [`upgrade_ref_to_mut()`].
pub fn get_ref_upgradeable<T>(v: &RefCell<T>) -> RefMut<'_, T> {
v.borrow_mut()
pub fn get_ref_upgradeable<T>(v: &RefCell<T>) -> Ref<'_, T> {
v.borrow()
}

/// Get a shared reference through a [`MutableOnDemand`] for read-only access.
Expand All @@ -73,14 +104,32 @@ mod _impl {
pub fn lock<T>(v: &Mutable<T>) -> RefMut<'_, T> {
v.borrow_mut()
}

/// Get a mutable reference through a [`MutableOnDemand`] for read-write access.
pub fn get_ref<T>(v: &RefCell<T>) -> Ref<'_, T> {
pub fn get_ref<T>(v: &RefCell<T>) -> RefGuard<'_, T> {
v.borrow()
}

/// Upgrade a handle previously obtained with [`get_ref_upgradeable()`] to support mutation.
pub fn upgrade_ref_to_mut<T>(v: RefMut<'_, T>) -> RefMut<'_, T> {
v
pub fn upgrade_ref_to_mut<'a, T>(v: Ref<'a, T>, orig: &'a RefCell<T>) -> RefMut<'a, T> {
drop(v);
orig.borrow_mut()
}

/// Downgrade a handle previously obtained with [`upgrade_ref_to_mut()`] to drop mutation support.
pub fn downgrade_mut_to_ref<'a, T>(v: RefMut<'a, T>, orig: &'a RefCell<T>) -> RefGuard<'a, T> {
drop(v);
orig.borrow()
}

/// Map a read guard into a sub-type it contains.
pub fn map_ref<T, U: ?Sized>(v: RefGuard<'_, T>, f: impl FnOnce(&T) -> &U) -> MappedRefGuard<'_, U> {
Ref::map(v, f)
}

/// Map an upgradable read guard into a sub-type it contains.
pub fn map_upgradable_ref<T, U: ?Sized>(v: Ref<'_, T>, f: impl FnOnce(&T) -> &U) -> MappedRefGuard<'_, U> {
Ref::map(v, f)
}
}

Expand Down
39 changes: 28 additions & 11 deletions git-ref/src/store/file/packed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ pub mod transaction {

pub(crate) mod modifiable {
use crate::file;
use git_features::threading::{get_ref_upgradeable, upgrade_ref_to_mut};
use git_features::threading::{
downgrade_mut_to_ref, get_ref_upgradeable, map_ref, map_upgradable_ref, upgrade_ref_to_mut, MappedRefGuard,
};
use std::time::SystemTime;

#[derive(Debug, Default)]
Expand All @@ -69,39 +71,54 @@ pub(crate) mod modifiable {
}

impl file::Store {
pub(crate) fn assure_packed_refs_uptodate(&self) -> Result<(), crate::packed::buffer::open::Error> {
pub(crate) fn assure_packed_refs_uptodate(
&self,
) -> Result<MappedRefGuard<'_, Option<crate::packed::Buffer>>, crate::packed::buffer::open::Error> {
let packed_refs_modified_time = || self.packed_refs_path().metadata().and_then(|m| m.modified()).ok();
let state = get_ref_upgradeable(&self.packed);
if state.buffer.is_none() {
let mut state = upgrade_ref_to_mut(state);
let ro_buffer_guard = if state.buffer.is_none() {
let mut state = upgrade_ref_to_mut(state, &self.packed);
state.buffer = self.packed_buffer()?;
if state.buffer.is_some() {
state.modified = packed_refs_modified_time();
}
let state = downgrade_mut_to_ref(state, &self.packed);
map_ref(state, |state| &state.buffer)
} else {
let recent_modification = packed_refs_modified_time();
match (&state.modified, recent_modification) {
(None, None) => {}
(None, None) => map_upgradable_ref(state, |state| &state.buffer),
(Some(_), None) => {
let mut state = upgrade_ref_to_mut(state);
let mut state = upgrade_ref_to_mut(state, &self.packed);
state.buffer = None;
state.modified = None
state.modified = None;

let state = downgrade_mut_to_ref(state, &self.packed);
map_ref(state, |state| &state.buffer)
}
(Some(cached_time), Some(modified_time)) => {
if *cached_time < modified_time {
let mut state = upgrade_ref_to_mut(state);
let mut state = upgrade_ref_to_mut(state, &self.packed);
state.buffer = self.packed_buffer()?;
state.modified = Some(modified_time);

let state = downgrade_mut_to_ref(state, &self.packed);
map_ref(state, |state| &state.buffer)
} else {
map_upgradable_ref(state, |state| &state.buffer)
}
}
(None, Some(modified_time)) => {
let mut state = upgrade_ref_to_mut(state);
let mut state = upgrade_ref_to_mut(state, &self.packed);
state.buffer = self.packed_buffer()?;
state.modified = Some(modified_time);

let state = downgrade_mut_to_ref(state, &self.packed);
map_ref(state, |state| &state.buffer)
}
}
}
Ok(())
};
Ok(ro_buffer_guard)
}
}
}

0 comments on commit f5c3c8f

Please sign in to comment.