Fix generation race condition with sync storage (#2638)

* Fix generation race condition with sync storage

* Document GenerationalPointer

* check generation before recycling generational box

* fix clippy

* make race_condition_regression test panic if it fails
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-07-23 10:30:57 -07:00
1 parent 672f4dfefc
commit c7c0f4481a
13 files changed
+766 -675

No files matched your search

+1 -1
View File
@@ -203,7 +203,7 @@ impl Drop for VNode {
for attrs in self.vnode.dynamic_attrs.iter() { for attrs in self.vnode.dynamic_attrs.iter() {
for attr in attrs.iter() { for attr in attrs.iter() {
if let AttributeValue::Listener(listener) = &attr.value { if let AttributeValue::Listener(listener) = &attr.value {
listener.callback.recycle(); listener.callback.manually_drop();
} }
} }
} }
-2
View File
@@ -17,8 +17,6 @@ rand = "0.8.5"
criterion = "0.3" criterion = "0.3"
[features] [features]
default = ["check_generation"]
check_generation = []
debug_borrows = [] debug_borrows = []
debug_ownership = [] debug_ownership = []
+135
View File
@@ -0,0 +1,135 @@
use crate::{
BorrowError, BorrowMutError, GenerationalLocation, GenerationalRefBorrowGuard,
GenerationalRefBorrowMutGuard,
};
pub(crate) struct StorageEntry<T> {
generation: u64,
pub data: Option<T>,
}
impl<T> Default for StorageEntry<T> {
fn default() -> Self {
Self {
generation: 0,
data: None,
}
}
}
impl<T> StorageEntry<T> {
pub fn valid(&self, location: &GenerationalLocation) -> bool {
self.generation == location.generation
}
pub fn increment_generation(&mut self) {
self.generation += 1;
}
pub fn generation(&self) -> u64 {
self.generation
}
}
#[derive(Default)]
pub(crate) struct MemoryLocationBorrowInfo(
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
parking_lot::RwLock<MemoryLocationBorrowInfoInner>,
);
impl MemoryLocationBorrowInfo {
pub(crate) fn borrow_mut_error(&self) -> BorrowMutError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
{
let borrow = self.0.read();
if let Some(borrowed_mut_at) = borrow.borrowed_mut_at.as_ref() {
BorrowMutError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError {
borrowed_mut_at,
})
} else {
BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError {
borrowed_at: borrow.borrowed_at.clone(),
})
}
}
#[cfg(not(any(debug_assertions, feature = "debug_borrows")))]
{
BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError {})
}
}
pub(crate) fn borrow_error(&self) -> BorrowError {
BorrowError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
borrowed_mut_at: self.0.read().borrowed_mut_at.unwrap(),
})
}
/// Start a new borrow
#[track_caller]
pub(crate) fn borrow_guard(&'static self) -> GenerationalRefBorrowGuard {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
let borrowed_at = std::panic::Location::caller();
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
{
let mut borrow = self.0.write();
borrow.borrowed_at.push(borrowed_at);
}
GenerationalRefBorrowGuard {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_from: self,
}
}
/// Start a new mutable borrow
#[track_caller]
pub(crate) fn borrow_mut_guard(&'static self) -> GenerationalRefBorrowMutGuard {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
let borrowed_mut_at = std::panic::Location::caller();
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
{
let mut borrow = self.0.write();
borrow.borrowed_mut_at = Some(borrowed_mut_at);
}
GenerationalRefBorrowMutGuard {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_from: self,
}
}
#[allow(unused)]
pub(crate) fn drop_borrow(&self, borrowed_at: &'static std::panic::Location<'static>) {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
{
let mut borrow = self.0.write();
borrow
.borrowed_at
.retain(|location| *location != borrowed_at);
}
}
#[allow(unused)]
pub(crate) fn drop_borrow_mut(&self, borrowed_mut_at: &'static std::panic::Location<'static>) {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
{
let mut borrow = self.0.write();
if borrow.borrowed_mut_at == Some(borrowed_mut_at) {
borrow.borrowed_mut_at = None;
}
}
}
}
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
#[derive(Default)]
struct MemoryLocationBorrowInfoInner {
borrowed_at: Vec<&'static std::panic::Location<'static>>,
borrowed_mut_at: Option<&'static std::panic::Location<'static>>,
}
+11
View File
@@ -2,6 +2,8 @@ use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;
use std::fmt::Display; use std::fmt::Display;
use crate::GenerationalLocation;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
/// An error that can occur when trying to borrow a value. /// An error that can occur when trying to borrow a value.
pub enum BorrowError { pub enum BorrowError {
@@ -61,6 +63,15 @@ impl ValueDroppedError {
created_at, created_at,
} }
} }
/// Create a new `ValueDroppedError` for a [`GenerationalLocation`].
#[allow(unused)]
pub(crate) fn new_for_location(location: GenerationalLocation) -> Self {
Self {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
created_at: location.created_at,
}
}
} }
impl Display for ValueDroppedError { impl Display for ValueDroppedError {
+116 -277
View File
@@ -14,6 +14,7 @@ pub use references::*;
pub use sync::SyncStorage; pub use sync::SyncStorage;
pub use unsync::UnsyncStorage; pub use unsync::UnsyncStorage;
mod entry;
mod error; mod error;
mod references; mod references;
mod sync; mod sync;
@@ -23,8 +24,7 @@ mod unsync;
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct GenerationalBoxId { pub struct GenerationalBoxId {
data_ptr: *const (), data_ptr: *const (),
#[cfg(any(debug_assertions, feature = "check_generation"))] generation: u64,
generation: u32,
} }
// Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer. // Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer.
@@ -33,35 +33,20 @@ unsafe impl Sync for GenerationalBoxId {}
impl Debug for GenerationalBoxId { impl Debug for GenerationalBoxId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[cfg(any(debug_assertions, feature = "check_generation"))]
f.write_fmt(format_args!("{:?}@{:?}", self.data_ptr, self.generation))?; f.write_fmt(format_args!("{:?}@{:?}", self.data_ptr, self.generation))?;
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
f.write_fmt(format_args!("{:?}", self.data_ptr))?;
Ok(()) Ok(())
} }
} }
/// The core Copy state type. The generational box will be dropped when the [Owner] is dropped. /// The core Copy state type. The generational box will be dropped when the [Owner] is dropped.
pub struct GenerationalBox<T, S: 'static = UnsyncStorage> { pub struct GenerationalBox<T, S: 'static = UnsyncStorage> {
raw: MemoryLocation<S>, raw: GenerationalPointer<S>,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: u32,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
_marker: PhantomData<T>, _marker: PhantomData<T>,
} }
impl<T, S: AnyStorage> Debug for GenerationalBox<T, S> { impl<T, S: AnyStorage> Debug for GenerationalBox<T, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[cfg(any(debug_assertions, feature = "check_generation"))] self.raw.fmt(f)
f.write_fmt(format_args!(
"{:?}@{:?}",
self.raw.0.data.data_ptr(),
self.generation
))?;
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
f.write_fmt(format_args!("{:?}", self.raw.0.data.data_ptr()))?;
Ok(())
} }
} }
@@ -70,73 +55,28 @@ impl<T, S: Storage<T>> GenerationalBox<T, S> {
/// a box that needs to be manually dropped with no owners. /// a box that needs to be manually dropped with no owners.
#[track_caller] #[track_caller]
pub fn leak(value: T) -> Self { pub fn leak(value: T) -> Self {
let mut location = S::claim(); let location = S::claim(std::panic::Location::caller());
location.replace_with_caller( location.set(value);
value, Self {
#[cfg(any(debug_assertions, feature = "debug_ownership"))] raw: location,
std::panic::Location::caller(), _marker: PhantomData,
)
}
#[inline(always)]
pub(crate) fn validate(&self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))]
{
self.raw
.0
.generation
.load(std::sync::atomic::Ordering::Relaxed)
== self.generation
}
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
{
true
} }
} }
/// Get the raw pointer to the value. /// Get the raw pointer to the value.
pub fn raw_ptr(&self) -> *const () { pub fn raw_ptr(&self) -> *const () {
self.raw.0.data.data_ptr() self.raw.storage.data_ptr()
} }
/// Get the id of the generational box. /// Get the id of the generational box.
pub fn id(&self) -> GenerationalBoxId { pub fn id(&self) -> GenerationalBoxId {
GenerationalBoxId { self.raw.id()
data_ptr: self.raw.0.data.data_ptr(),
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: self.generation,
}
} }
/// Try to read the value. Returns None if the value is no longer valid. /// Try to read the value. Returns an error if the value is no longer valid.
#[track_caller] #[track_caller]
pub fn try_read(&self) -> Result<S::Ref<'static, T>, BorrowError> { pub fn try_read(&self) -> Result<S::Ref<'static, T>, BorrowError> {
if !self.validate() { self.raw.try_read()
return Err(BorrowError::Dropped(ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
created_at: self.created_at,
}));
}
let result = self.raw.0.data.try_read(
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
GenerationalRefBorrowInfo {
borrowed_at: std::panic::Location::caller(),
borrowed_from: &self.raw.0.borrow,
created_at: self.created_at,
},
);
if result.is_ok() {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
self.raw
.0
.borrow
.borrowed_at
.write()
.push(std::panic::Location::caller());
}
result
} }
/// Read the value. Panics if the value is no longer valid. /// Read the value. Panics if the value is no longer valid.
@@ -148,28 +88,7 @@ impl<T, S: Storage<T>> GenerationalBox<T, S> {
/// Try to write the value. Returns None if the value is no longer valid. /// Try to write the value. Returns None if the value is no longer valid.
#[track_caller] #[track_caller]
pub fn try_write(&self) -> Result<S::Mut<'static, T>, BorrowMutError> { pub fn try_write(&self) -> Result<S::Mut<'static, T>, BorrowMutError> {
if !self.validate() { self.raw.try_write()
return Err(BorrowMutError::Dropped(ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
created_at: self.created_at,
}));
}
let result = self.raw.0.data.try_write(
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
GenerationalRefMutBorrowInfo {
borrowed_from: &self.raw.0.borrow,
created_at: self.created_at,
},
);
if result.is_ok() {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
{
*self.raw.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller());
}
}
result
} }
/// Write the value. Panics if the value is no longer valid. /// Write the value. Panics if the value is no longer valid.
@@ -180,42 +99,21 @@ impl<T, S: Storage<T>> GenerationalBox<T, S> {
/// Set the value. Panics if the value is no longer valid. /// Set the value. Panics if the value is no longer valid.
pub fn set(&self, value: T) { pub fn set(&self, value: T) {
self.validate().then(|| { S::set(self.raw, value);
self.raw.0.data.set(value);
});
} }
/// Returns true if the pointer is equal to the other pointer. /// Returns true if the pointer is equal to the other pointer.
pub fn ptr_eq(&self, other: &Self) -> bool { pub fn ptr_eq(&self, other: &Self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))] self.raw == other.raw
{
self.raw.0.data.data_ptr() == other.raw.0.data.data_ptr()
&& self.generation == other.generation
}
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
{
self.raw.0.data.data_ptr() == other.raw.0.data.data_ptr()
}
}
/// Recycle the generationalbox, dropping the value.
pub fn recycle(&self) {
if self.validate() {
<S as AnyStorage>::recycle(&self.raw);
}
} }
/// Drop the value out of the generational box and invalidate the generational box. /// Drop the value out of the generational box and invalidate the generational box.
/// This will return the value if the value was taken. /// This will return the value if the value was taken.
pub fn manually_drop(&self) -> Option<T> { pub fn manually_drop(&self) -> Option<T>
if self.validate() { where
// TODO: Next breaking release we should change the take method to automatically recycle the box T: 'static,
let value = Storage::take(&self.raw.0.data)?; {
<S as AnyStorage>::recycle(&self.raw); self.raw.take()
Some(value)
} else {
None
}
} }
} }
@@ -231,25 +129,20 @@ impl<T, S> Clone for GenerationalBox<T, S> {
pub trait Storage<Data = ()>: AnyStorage + 'static { pub trait Storage<Data = ()>: AnyStorage + 'static {
/// Try to read the value. Returns None if the value is no longer valid. /// Try to read the value. Returns None if the value is no longer valid.
fn try_read( fn try_read(
&'static self, location: GenerationalPointer<Self>,
#[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo,
) -> Result<Self::Ref<'static, Data>, BorrowError>; ) -> Result<Self::Ref<'static, Data>, BorrowError>;
/// Try to write the value. Returns None if the value is no longer valid. /// Try to write the value. Returns None if the value is no longer valid.
fn try_write( fn try_write(
&'static self, location: GenerationalPointer<Self>,
#[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo,
) -> Result<Self::Mut<'static, Data>, BorrowMutError>; ) -> Result<Self::Mut<'static, Data>, BorrowMutError>;
/// Set the value /// Set the value if the location is valid
fn set(&'static self, value: Data); fn set(location: GenerationalPointer<Self>, value: Data);
/// Take the value out of the storage. This will return the value if the value was taken.
fn take(&'static self) -> Option<Data>;
} }
/// A trait for any storage backing type. /// A trait for any storage backing type.
pub trait AnyStorage: Default { pub trait AnyStorage: Default + 'static {
/// The reference this storage type returns. /// The reference this storage type returns.
type Ref<'a, T: ?Sized + 'static>: Deref<Target = T>; type Ref<'a, T: ?Sized + 'static>: Deref<Target = T>;
/// The mutable reference this storage type returns. /// The mutable reference this storage type returns.
@@ -300,14 +193,11 @@ pub trait AnyStorage: Default {
/// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging. /// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging.
fn data_ptr(&self) -> *const (); fn data_ptr(&self) -> *const ();
/// Drop the value from the storage. This will return true if the value was taken.
fn manually_drop(&self) -> bool;
/// Recycle a memory location. This will drop the memory location and return it to the runtime. /// Recycle a memory location. This will drop the memory location and return it to the runtime.
fn recycle(location: &MemoryLocation<Self>); fn recycle(location: GenerationalPointer<Self>) -> Option<Box<dyn std::any::Any>>;
/// Claim a new memory location. This will either create a new memory location or recycle an old one. /// Claim a new memory location. This will either create a new memory location or recycle an old one.
fn claim() -> MemoryLocation<Self>; fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self>;
/// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates. /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
fn owner() -> Owner<Self> { fn owner() -> Owner<Self> {
@@ -317,142 +207,104 @@ pub trait AnyStorage: Default {
} }
} }
/// A dynamic memory location that can be used in a generational box. #[derive(Debug, Clone, Copy)]
pub struct MemoryLocation<S: 'static = UnsyncStorage>(&'static MemoryLocationInner<S>); pub(crate) struct GenerationalLocation {
/// The generation this location is associated with. Using the location after this generation is invalidated will return errors.
generation: u64,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
}
impl<S: 'static> Clone for MemoryLocation<S> { /// A pointer to a specific generational box and generation in that box.
pub struct GenerationalPointer<S: 'static = UnsyncStorage> {
/// The storage that is backing this location
storage: &'static S,
/// The location of the data
location: GenerationalLocation,
}
impl<S: AnyStorage + 'static> PartialEq for GenerationalPointer<S> {
fn eq(&self, other: &Self) -> bool {
self.storage.data_ptr() == other.storage.data_ptr()
&& self.location.generation == other.location.generation
}
}
impl<S: AnyStorage + 'static> Debug for GenerationalPointer<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{:?}@{:?}",
self.storage.data_ptr(),
self.location.generation
))
}
}
impl<S: 'static> Clone for GenerationalPointer<S> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<S: 'static> Copy for MemoryLocation<S> {} impl<S: 'static> Copy for GenerationalPointer<S> {}
#[cfg(any(debug_assertions, feature = "debug_borrows"))] impl<S> GenerationalPointer<S> {
#[derive(Debug, Default)] fn take<T: 'static>(self) -> Option<T>
struct MemoryLocationBorrowInfo {
pub(crate) borrowed_at: parking_lot::RwLock<Vec<&'static std::panic::Location<'static>>>,
pub(crate) borrowed_mut_at: parking_lot::RwLock<Option<&'static std::panic::Location<'static>>>,
}
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
impl MemoryLocationBorrowInfo {
fn borrow_mut_error(&self) -> BorrowMutError {
if let Some(borrowed_mut_at) = self.borrowed_mut_at.read().as_ref() {
BorrowMutError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at,
})
} else {
BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at: self.borrowed_at.read().clone(),
})
}
}
fn borrow_error(&self) -> BorrowError {
BorrowError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
borrowed_mut_at: self.borrowed_mut_at.read().unwrap(),
})
}
}
struct MemoryLocationInner<S = UnsyncStorage> {
data: S,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: std::sync::atomic::AtomicU32,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: MemoryLocationBorrowInfo,
}
impl<S> MemoryLocation<S> {
#[allow(unused)]
fn drop(&self)
where
S: AnyStorage,
{
self.0.data.manually_drop();
#[cfg(any(debug_assertions, feature = "check_generation"))]
{
let new_generation = self.0.generation.load(std::sync::atomic::Ordering::Relaxed) + 1;
self.0
.generation
.store(new_generation, std::sync::atomic::Ordering::Relaxed);
}
}
fn replace_with_caller<T>(
&mut self,
value: T,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
caller: &'static std::panic::Location<'static>,
) -> GenerationalBox<T, S>
where where
S: Storage<T>, S: Storage<T>,
{ {
self.0.data.set(value); S::recycle(self).map(|value| *(value.downcast().unwrap()))
GenerationalBox {
raw: *self,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: self.0.generation.load(std::sync::atomic::Ordering::Relaxed),
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: caller,
_marker: PhantomData,
}
}
}
// We track the generation along with the memory location so that when generational boxes are dropped early, we don't end up dropping the new occupant of the slot
struct LocationKey<S: 'static> {
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: u32,
location: MemoryLocation<S>,
}
impl<S: AnyStorage> LocationKey<S> {
fn exists(&self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))]
return self.generation
== self
.location
.0
.generation
.load(std::sync::atomic::Ordering::Relaxed);
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
return true;
} }
fn drop(self) { fn set<T>(self, value: T)
// If this is the same box we own, we can just drop it where
if self.exists() { S: Storage<T>,
S::recycle(&self.location); {
} S::set(self, value)
} }
}
impl<T, S: AnyStorage> From<GenerationalBox<T, S>> for LocationKey<S> { #[track_caller]
fn from(value: GenerationalBox<T, S>) -> Self { fn try_read<T>(self) -> Result<S::Ref<'static, T>, BorrowError>
Self { where
#[cfg(any(debug_assertions, feature = "check_generation"))] S: Storage<T>,
generation: value.generation, {
location: value.raw, S::try_read(self)
}
#[track_caller]
fn try_write<T>(self) -> Result<S::Mut<'static, T>, BorrowMutError>
where
S: Storage<T>,
{
S::try_write(self)
}
fn recycle(self)
where
S: AnyStorage,
{
S::recycle(self);
}
fn id(&self) -> GenerationalBoxId
where
S: AnyStorage,
{
GenerationalBoxId {
data_ptr: self.storage.data_ptr(),
generation: self.location.generation,
} }
} }
} }
struct OwnerInner<S: AnyStorage + 'static> { struct OwnerInner<S: AnyStorage + 'static> {
owned: Vec<LocationKey<S>>, owned: Vec<GenerationalPointer<S>>,
} }
impl<S: AnyStorage> Drop for OwnerInner<S> { impl<S: AnyStorage> Drop for OwnerInner<S> {
fn drop(&mut self) { fn drop(&mut self) {
for key in self.owned.drain(..) { for location in self.owned.drain(..) {
key.drop(); location.recycle();
} }
} }
} }
@@ -479,48 +331,35 @@ impl<S: AnyStorage> Owner<S> {
where where
S: Storage<T>, S: Storage<T>,
{ {
self.insert_with_caller( self.insert_with_caller(value, std::panic::Location::caller())
value,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
std::panic::Location::caller(),
)
} }
/// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped.
pub fn insert_with_caller<T: 'static>( pub fn insert_with_caller<T: 'static>(
&self, &self,
value: T, value: T,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
caller: &'static std::panic::Location<'static>, caller: &'static std::panic::Location<'static>,
) -> GenerationalBox<T, S> ) -> GenerationalBox<T, S>
where where
S: Storage<T>, S: Storage<T>,
{ {
let mut location = S::claim(); let location = S::claim(caller);
let key = location.replace_with_caller( location.set(value);
value, self.0.lock().owned.push(location);
#[cfg(any(debug_assertions, feature = "debug_borrows"))] GenerationalBox {
caller, raw: location,
); _marker: PhantomData,
self.0.lock().owned.push(key.into()); }
key
} }
/// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value. /// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value.
#[track_caller]
pub fn invalid<T: 'static>(&self) -> GenerationalBox<T, S> { pub fn invalid<T: 'static>(&self) -> GenerationalBox<T, S> {
let location = S::claim(); let location = S::claim(std::panic::Location::caller());
let generational_box = GenerationalBox { self.0.lock().owned.push(location);
GenerationalBox {
raw: location, raw: location,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: location
.0
.generation
.load(std::sync::atomic::Ordering::Relaxed),
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: std::panic::Location::caller(),
_marker: PhantomData, _marker: PhantomData,
}; }
self.0.lock().owned.push(generational_box.into());
generational_box
} }
} }
+49 -54
View File
@@ -6,21 +6,29 @@ use std::{
/// A reference to a value in a generational box. /// A reference to a value in a generational box.
pub struct GenerationalRef<R> { pub struct GenerationalRef<R> {
pub(crate) inner: R, pub(crate) inner: R,
#[cfg(any(debug_assertions, feature = "debug_borrows"))] guard: GenerationalRefBorrowGuard,
pub(crate) borrow: GenerationalRefBorrowInfo,
} }
impl<T: 'static, R: Deref<Target = T>> GenerationalRef<R> { impl<T: ?Sized + 'static, R: Deref<Target = T>> GenerationalRef<R> {
pub(crate) fn new( pub(crate) fn new(inner: R, guard: GenerationalRefBorrowGuard) -> Self {
inner: R, Self { inner, guard }
#[cfg(any(debug_assertions, feature = "debug_borrows"))] borrow: GenerationalRefBorrowInfo, }
) -> Self {
Self { /// Map the inner value to a new type
inner, pub fn map<R2, F: FnOnce(R) -> R2>(self, f: F) -> GenerationalRef<R2> {
#[cfg(any(debug_assertions, feature = "debug_borrows"))] GenerationalRef {
borrow, inner: f(self.inner),
guard: self.guard,
} }
} }
/// Try to map the inner value to a new type
pub fn try_map<R2, F: FnOnce(R) -> Option<R2>>(self, f: F) -> Option<GenerationalRef<R2>> {
f(self.inner).map(|inner| GenerationalRef {
inner,
guard: self.guard,
})
}
} }
impl<T: ?Sized + Debug, R: Deref<Target = T>> Debug for GenerationalRef<R> { impl<T: ?Sized + Debug, R: Deref<Target = T>> Debug for GenerationalRef<R> {
@@ -43,43 +51,46 @@ impl<T: ?Sized + 'static, R: Deref<Target = T>> Deref for GenerationalRef<R> {
} }
} }
#[cfg(any(debug_assertions, feature = "debug_borrows"))] pub(crate) struct GenerationalRefBorrowGuard {
/// Information about a borrow. #[cfg(any(debug_assertions, feature = "debug_borrows"))]
pub struct GenerationalRefBorrowInfo {
pub(crate) borrowed_at: &'static std::panic::Location<'static>, pub(crate) borrowed_at: &'static std::panic::Location<'static>,
pub(crate) borrowed_from: &'static crate::MemoryLocationBorrowInfo, #[cfg(any(debug_assertions, feature = "debug_borrows"))]
pub(crate) created_at: &'static std::panic::Location<'static>, pub(crate) borrowed_from: &'static crate::entry::MemoryLocationBorrowInfo,
} }
#[cfg(any(debug_assertions, feature = "debug_borrows"))] #[cfg(any(debug_assertions, feature = "debug_borrows"))]
impl Drop for GenerationalRefBorrowInfo { impl Drop for GenerationalRefBorrowGuard {
fn drop(&mut self) { fn drop(&mut self) {
self.borrowed_from self.borrowed_from.drop_borrow(self.borrowed_at);
.borrowed_at
.write()
.retain(|location| !std::ptr::eq(*location, self.borrowed_at as *const _));
} }
} }
/// A mutable reference to a value in a generational box. /// A mutable reference to a value in a generational box.
pub struct GenerationalRefMut<W> { pub struct GenerationalRefMut<W> {
pub(crate) inner: W, pub(crate) inner: W,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
pub(crate) borrow: GenerationalRefBorrowMutGuard, pub(crate) borrow: GenerationalRefBorrowMutGuard,
} }
impl<T, R: DerefMut<Target = T>> GenerationalRefMut<R> { impl<T: ?Sized + 'static, R: DerefMut<Target = T>> GenerationalRefMut<R> {
pub(crate) fn new( pub(crate) fn new(inner: R, borrow: GenerationalRefBorrowMutGuard) -> Self {
inner: R, Self { inner, borrow }
#[cfg(any(debug_assertions, feature = "debug_borrows"))] }
borrow: GenerationalRefMutBorrowInfo,
) -> Self { /// Map the inner value to a new type
Self { pub fn map<R2, F: FnOnce(R) -> R2>(self, f: F) -> GenerationalRefMut<R2> {
inner, GenerationalRefMut {
#[cfg(any(debug_assertions, feature = "debug_borrows"))] inner: f(self.inner),
borrow: borrow.into(), borrow: self.borrow,
} }
} }
/// Try to map the inner value to a new type
pub fn try_map<R2, F: FnOnce(R) -> Option<R2>>(self, f: F) -> Option<GenerationalRefMut<R2>> {
f(self.inner).map(|inner| GenerationalRefMut {
inner,
borrow: self.borrow,
})
}
} }
impl<T: ?Sized, W: DerefMut<Target = T>> Deref for GenerationalRefMut<W> { impl<T: ?Sized, W: DerefMut<Target = T>> Deref for GenerationalRefMut<W> {
@@ -96,33 +107,17 @@ impl<T: ?Sized, W: DerefMut<Target = T>> DerefMut for GenerationalRefMut<W> {
} }
} }
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
/// Information about a mutable borrow.
pub struct GenerationalRefMutBorrowInfo {
/// The location where the borrow occurred.
pub(crate) borrowed_from: &'static crate::MemoryLocationBorrowInfo,
pub(crate) created_at: &'static std::panic::Location<'static>,
}
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
pub(crate) struct GenerationalRefBorrowMutGuard { pub(crate) struct GenerationalRefBorrowMutGuard {
borrow_info: GenerationalRefMutBorrowInfo, #[cfg(any(debug_assertions, feature = "debug_borrows"))]
} /// The location where the borrow occurred.
pub(crate) borrowed_from: &'static crate::entry::MemoryLocationBorrowInfo,
#[cfg(any(debug_assertions, feature = "debug_borrows"))] #[cfg(any(debug_assertions, feature = "debug_borrows"))]
impl From<GenerationalRefMutBorrowInfo> for GenerationalRefBorrowMutGuard { pub(crate) borrowed_mut_at: &'static std::panic::Location<'static>,
fn from(borrow_info: GenerationalRefMutBorrowInfo) -> Self {
Self { borrow_info }
}
} }
#[cfg(any(debug_assertions, feature = "debug_borrows"))] #[cfg(any(debug_assertions, feature = "debug_borrows"))]
impl Drop for GenerationalRefBorrowMutGuard { impl Drop for GenerationalRefBorrowMutGuard {
fn drop(&mut self) { fn drop(&mut self) {
self.borrow_info self.borrowed_from.drop_borrow_mut(self.borrowed_mut_at);
.borrowed_from
.borrowed_mut_at
.write()
.take();
} }
} }
+105 -96
View File
@@ -4,18 +4,22 @@ use parking_lot::{
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use crate::{ use crate::{
entry::{MemoryLocationBorrowInfo, StorageEntry},
error::{self, ValueDroppedError}, error::{self, ValueDroppedError},
references::{GenerationalRef, GenerationalRefMut}, references::{GenerationalRef, GenerationalRefMut},
AnyStorage, MemoryLocation, MemoryLocationInner, Storage, AnyStorage, GenerationalLocation, GenerationalPointer, Storage,
}; };
/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads. /// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads.
#[derive(Default)] #[derive(Default)]
pub struct SyncStorage(RwLock<Option<Box<dyn std::any::Any + Send + Sync>>>); pub struct SyncStorage {
borrow_info: MemoryLocationBorrowInfo,
data: RwLock<StorageEntry<Box<dyn std::any::Any + Send + Sync>>>,
}
static SYNC_RUNTIME: OnceLock<Arc<Mutex<Vec<MemoryLocation<SyncStorage>>>>> = OnceLock::new(); static SYNC_RUNTIME: OnceLock<Arc<Mutex<Vec<&'static SyncStorage>>>> = OnceLock::new();
fn sync_runtime() -> &'static Arc<Mutex<Vec<MemoryLocation<SyncStorage>>>> { fn sync_runtime() -> &'static Arc<Mutex<Vec<&'static SyncStorage>>> {
SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new()))) SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
} }
@@ -35,129 +39,134 @@ impl AnyStorage for SyncStorage {
mut_ mut_
} }
fn map<T: ?Sized + 'static, U: ?Sized + 'static>(
ref_: Self::Ref<'_, T>,
f: impl FnOnce(&T) -> &U,
) -> Self::Ref<'_, U> {
ref_.map(|inner| MappedRwLockReadGuard::map(inner, f))
}
fn map_mut<T: ?Sized + 'static, U: ?Sized + 'static>(
mut_ref: Self::Mut<'_, T>,
f: impl FnOnce(&mut T) -> &mut U,
) -> Self::Mut<'_, U> {
mut_ref.map(|inner| MappedRwLockWriteGuard::map(inner, f))
}
fn try_map<I: ?Sized + 'static, U: ?Sized + 'static>( fn try_map<I: ?Sized + 'static, U: ?Sized + 'static>(
ref_: Self::Ref<'_, I>, ref_: Self::Ref<'_, I>,
f: impl FnOnce(&I) -> Option<&U>, f: impl FnOnce(&I) -> Option<&U>,
) -> Option<Self::Ref<'_, U>> { ) -> Option<Self::Ref<'_, U>> {
let GenerationalRef { ref_.try_map(|inner| MappedRwLockReadGuard::try_map(inner, f).ok())
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
..
} = ref_;
MappedRwLockReadGuard::try_map(inner, f)
.ok()
.map(|inner| GenerationalRef {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: crate::GenerationalRefBorrowInfo {
borrowed_at: borrow.borrowed_at,
borrowed_from: borrow.borrowed_from,
created_at: borrow.created_at,
},
})
} }
fn try_map_mut<I: ?Sized + 'static, U: ?Sized + 'static>( fn try_map_mut<I: ?Sized + 'static, U: ?Sized + 'static>(
mut_ref: Self::Mut<'_, I>, mut_ref: Self::Mut<'_, I>,
f: impl FnOnce(&mut I) -> Option<&mut U>, f: impl FnOnce(&mut I) -> Option<&mut U>,
) -> Option<Self::Mut<'_, U>> { ) -> Option<Self::Mut<'_, U>> {
let GenerationalRefMut { mut_ref.try_map(|inner| MappedRwLockWriteGuard::try_map(inner, f).ok())
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
..
} = mut_ref;
MappedRwLockWriteGuard::try_map(inner, f)
.ok()
.map(|inner| GenerationalRefMut {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
})
} }
fn data_ptr(&self) -> *const () { fn data_ptr(&self) -> *const () {
self.0.data_ptr() as *const () self.data.data_ptr() as *const ()
} }
fn manually_drop(&self) -> bool { #[track_caller]
self.0.write().take().is_some() #[allow(unused)]
} fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
match sync_runtime().lock().pop() {
fn claim() -> MemoryLocation<Self> { Some(mut storage) => {
sync_runtime().lock().pop().unwrap_or_else(|| { let location = GenerationalLocation {
let data: &'static MemoryLocationInner<Self> = generation: storage.data.read().generation(),
&*Box::leak(Box::new(MemoryLocationInner {
data: Self::default(),
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: 0.into(),
#[cfg(any(debug_assertions, feature = "debug_borrows"))] #[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: Default::default(), created_at: caller,
})); };
MemoryLocation(data) GenerationalPointer { storage, location }
}) }
None => {
let storage: &'static Self = &*Box::leak(Box::default());
let location = GenerationalLocation {
generation: 0,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
created_at: caller,
};
GenerationalPointer { storage, location }
}
}
} }
fn recycle(location: &MemoryLocation<Self>) { fn recycle(pointer: GenerationalPointer<Self>) -> Option<Box<dyn std::any::Any>> {
let location = *location; let mut borrow_mut = pointer.storage.data.write();
location.drop(); // First check if the generation is still valid
sync_runtime().lock().push(location); if !borrow_mut.valid(&pointer.location) {
return None;
}
borrow_mut.increment_generation();
let old_data = borrow_mut.data.take();
sync_runtime().lock().push(pointer.storage);
old_data.map(|data| data as Box<dyn std::any::Any>)
} }
} }
impl<T: Sync + Send + 'static> Storage<T> for SyncStorage { impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
#[track_caller]
fn try_read( fn try_read(
&'static self, pointer: GenerationalPointer<Self>,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at: crate::GenerationalRefBorrowInfo,
) -> Result<Self::Ref<'static, T>, error::BorrowError> { ) -> Result<Self::Ref<'static, T>, error::BorrowError> {
let read = self.0.read(); let read = pointer.storage.data.read();
RwLockReadGuard::try_map(read, |any| any.as_ref()?.downcast_ref()) let read = RwLockReadGuard::try_map(read, |any| {
.map_err(|_| { // Verify the generation is still correct
error::BorrowError::Dropped(ValueDroppedError { if !any.valid(&pointer.location) {
#[cfg(any(debug_assertions, feature = "debug_ownership"))] return None;
created_at: at.created_at, }
}) // Then try to downcast
}) any.data.as_ref()?.downcast_ref()
.map(|guard| { });
GenerationalRef::new( match read {
guard, Ok(guard) => Ok(GenerationalRef::new(
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at,
)
})
}
fn try_write(
&'static self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at: crate::GenerationalRefMutBorrowInfo,
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
let write = self.0.write();
match RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut()) {
Ok(guard) => Ok(GenerationalRefMut::new(
guard, guard,
#[cfg(any(debug_assertions, feature = "debug_ownership"))] pointer.storage.borrow_info.borrow_guard(),
at, )),
Err(_) => Err(error::BorrowError::Dropped(
ValueDroppedError::new_for_location(pointer.location),
)), )),
Err(_) => Err(error::BorrowMutError::Dropped(ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: at.created_at,
})),
} }
} }
fn set(&self, value: T) { #[track_caller]
*self.0.write() = Some(Box::new(value)); fn try_write(
pointer: GenerationalPointer<Self>,
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
let write = pointer.storage.data.write();
let write = RwLockWriteGuard::try_map(write, |any| {
// Verify the generation is still correct
if !any.valid(&pointer.location) {
return None;
}
// Then try to downcast
any.data.as_mut()?.downcast_mut()
});
match write {
Ok(guard) => Ok(GenerationalRefMut::new(
guard,
pointer.storage.borrow_info.borrow_mut_guard(),
)),
Err(_) => Err(error::BorrowMutError::Dropped(
ValueDroppedError::new_for_location(pointer.location),
)),
}
} }
fn take(&'static self) -> Option<T> { fn set(pointer: GenerationalPointer<Self>, value: T) {
self.0 let mut write = pointer.storage.data.write();
.write() // First check if the generation is still valid
.take() if !write.valid(&pointer.location) {
.and_then(|any| any.downcast().ok().map(|boxed| *boxed)) return;
}
write.data = Some(Box::new(value));
} }
} }
+135 -121
View File
@@ -1,87 +1,36 @@
use crate::{ use crate::{
entry::{MemoryLocationBorrowInfo, StorageEntry},
error, error,
references::{GenerationalRef, GenerationalRefMut}, references::{GenerationalRef, GenerationalRefMut},
AnyStorage, MemoryLocation, MemoryLocationInner, Storage, AnyStorage, BorrowError, BorrowMutError, GenerationalLocation, GenerationalPointer, Storage,
}; };
use std::cell::{Ref, RefCell, RefMut}; use std::cell::{Ref, RefCell, RefMut};
/// A unsync storage. This is the default storage type. thread_local! {
#[derive(Default)] static UNSYNC_RUNTIME: RefCell<Vec<&'static UnsyncStorage>> = const { RefCell::new(Vec::new()) };
pub struct UnsyncStorage(RefCell<Option<Box<dyn std::any::Any>>>);
impl<T: 'static> Storage<T> for UnsyncStorage {
fn try_read(
&'static self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at: crate::GenerationalRefBorrowInfo,
) -> Result<Self::Ref<'static, T>, error::BorrowError> {
let borrow = self.0.try_borrow();
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
let borrow = borrow.map_err(|_| at.borrowed_from.borrow_error())?;
#[cfg(not(any(debug_assertions, feature = "debug_ownership")))]
let borrow = borrow.map_err(|_| {
error::BorrowError::AlreadyBorrowedMut(error::AlreadyBorrowedMutError {})
})?;
match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref()) {
Ok(guard) => Ok(GenerationalRef::new(
guard,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at,
)),
Err(_) => Err(error::BorrowError::Dropped(error::ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: at.created_at,
})),
}
}
fn try_write(
&'static self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at: crate::GenerationalRefMutBorrowInfo,
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
let borrow = self.0.try_borrow_mut();
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
let borrow = borrow.map_err(|_| at.borrowed_from.borrow_mut_error())?;
#[cfg(not(any(debug_assertions, feature = "debug_ownership")))]
let borrow = borrow
.map_err(|_| error::BorrowMutError::AlreadyBorrowed(error::AlreadyBorrowedError {}))?;
RefMut::filter_map(borrow, |any| any.as_mut()?.downcast_mut())
.map_err(|_| {
error::BorrowMutError::Dropped(error::ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: at.created_at,
})
})
.map(|guard| {
GenerationalRefMut::new(
guard,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
at,
)
})
}
fn set(&self, value: T) {
*self.0.borrow_mut() = Some(Box::new(value));
}
fn take(&'static self) -> Option<T> {
self.0
.borrow_mut()
.take()
.map(|any| *any.downcast().unwrap())
}
} }
thread_local! { /// A unsync storage. This is the default storage type.
static UNSYNC_RUNTIME: RefCell<Vec<MemoryLocation<UnsyncStorage>>> = const { RefCell::new(Vec::new()) }; #[derive(Default)]
pub struct UnsyncStorage {
borrow_info: MemoryLocationBorrowInfo,
data: RefCell<StorageEntry<Box<dyn std::any::Any>>>,
}
impl UnsyncStorage {
fn try_borrow_mut(
&self,
) -> Result<RefMut<'_, StorageEntry<Box<dyn std::any::Any>>>, BorrowMutError> {
self.data
.try_borrow_mut()
.map_err(|_| self.borrow_info.borrow_mut_error())
}
fn try_borrow(&self) -> Result<Ref<'_, StorageEntry<Box<dyn std::any::Any>>>, BorrowError> {
self.data
.try_borrow()
.map_err(|_| self.borrow_info.borrow_error())
}
} }
impl AnyStorage for UnsyncStorage { impl AnyStorage for UnsyncStorage {
@@ -100,71 +49,136 @@ impl AnyStorage for UnsyncStorage {
mut_ mut_
} }
fn map<T: ?Sized + 'static, U: ?Sized + 'static>(
ref_: Self::Ref<'_, T>,
f: impl FnOnce(&T) -> &U,
) -> Self::Ref<'_, U> {
ref_.map(|inner| Ref::map(inner, f))
}
fn map_mut<T: ?Sized + 'static, U: ?Sized + 'static>(
mut_ref: Self::Mut<'_, T>,
f: impl FnOnce(&mut T) -> &mut U,
) -> Self::Mut<'_, U> {
mut_ref.map(|inner| RefMut::map(inner, f))
}
fn try_map<I: ?Sized + 'static, U: ?Sized + 'static>( fn try_map<I: ?Sized + 'static, U: ?Sized + 'static>(
_self: Self::Ref<'_, I>, _self: Self::Ref<'_, I>,
f: impl FnOnce(&I) -> Option<&U>, f: impl FnOnce(&I) -> Option<&U>,
) -> Option<Self::Ref<'_, U>> { ) -> Option<Self::Ref<'_, U>> {
let GenerationalRef { _self.try_map(|inner| Ref::filter_map(inner, f).ok())
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
..
} = _self;
Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
})
} }
fn try_map_mut<I: ?Sized + 'static, U: ?Sized + 'static>( fn try_map_mut<I: ?Sized + 'static, U: ?Sized + 'static>(
mut_ref: Self::Mut<'_, I>, mut_ref: Self::Mut<'_, I>,
f: impl FnOnce(&mut I) -> Option<&mut U>, f: impl FnOnce(&mut I) -> Option<&mut U>,
) -> Option<Self::Mut<'_, U>> { ) -> Option<Self::Mut<'_, U>> {
let GenerationalRefMut { mut_ref.try_map(|inner| RefMut::filter_map(inner, f).ok())
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
..
} = mut_ref;
RefMut::filter_map(inner, f)
.ok()
.map(|inner| GenerationalRefMut {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
})
} }
fn data_ptr(&self) -> *const () { fn data_ptr(&self) -> *const () {
self.0.as_ptr() as *const () self.data.as_ptr() as *const ()
} }
fn manually_drop(&self) -> bool { #[allow(unused)]
self.0.borrow_mut().take().is_some() fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
}
fn claim() -> MemoryLocation<Self> {
UNSYNC_RUNTIME.with(|runtime| { UNSYNC_RUNTIME.with(|runtime| {
if let Some(location) = runtime.borrow_mut().pop() { if let Some(storage) = runtime.borrow_mut().pop() {
location let location = GenerationalLocation {
generation: storage.data.borrow().generation(),
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
created_at: caller,
};
GenerationalPointer { storage, location }
} else { } else {
let data: &'static MemoryLocationInner = let data: &'static Self = &*Box::leak(Box::default());
&*Box::leak(Box::new(MemoryLocationInner { let location = GenerationalLocation {
data: Self::default(), generation: 0,
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "debug_borrows"))]
generation: 0.into(), created_at: caller,
#[cfg(any(debug_assertions, feature = "debug_borrows"))] };
borrow: Default::default(), GenerationalPointer {
})); storage: data,
MemoryLocation(data) location,
}
} }
}) })
} }
fn recycle(location: &MemoryLocation<Self>) { fn recycle(pointer: GenerationalPointer<Self>) -> Option<Box<dyn std::any::Any>> {
let location = *location; let mut borrow_mut = pointer.storage.data.borrow_mut();
location.drop();
UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(location)); // First check if the generation is still valid
if !borrow_mut.valid(&pointer.location) {
return None;
}
borrow_mut.increment_generation();
let old_data = borrow_mut.data.take();
UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage));
old_data
}
}
impl<T: 'static> Storage<T> for UnsyncStorage {
#[track_caller]
fn try_read(
pointer: GenerationalPointer<Self>,
) -> Result<Self::Ref<'static, T>, error::BorrowError> {
let read = pointer.storage.try_borrow()?;
let ref_ = Ref::filter_map(read, |any| {
// Verify the generation is still correct
if !any.valid(&pointer.location) {
return None;
}
// Then try to downcast
any.data.as_ref()?.downcast_ref()
});
match ref_ {
Ok(guard) => Ok(GenerationalRef::new(
guard,
pointer.storage.borrow_info.borrow_guard(),
)),
Err(_) => Err(error::BorrowError::Dropped(
error::ValueDroppedError::new_for_location(pointer.location),
)),
}
}
#[track_caller]
fn try_write(
pointer: GenerationalPointer<Self>,
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
let write = pointer.storage.try_borrow_mut()?;
let ref_mut = RefMut::filter_map(write, |any| {
// Verify the generation is still correct
if !any.valid(&pointer.location) {
return None;
}
// Then try to downcast
any.data.as_mut()?.downcast_mut()
});
match ref_mut {
Ok(guard) => Ok(GenerationalRefMut::new(
guard,
pointer.storage.borrow_info.borrow_mut_guard(),
)),
Err(_) => Err(error::BorrowMutError::Dropped(
error::ValueDroppedError::new_for_location(pointer.location),
)),
}
}
fn set(pointer: GenerationalPointer<Self>, value: T) {
let mut borrow_mut = pointer.storage.data.borrow_mut();
// First check if the generation is still valid
if !borrow_mut.valid(&pointer.location) {
return;
}
borrow_mut.data = Some(Box::new(value));
} }
} }
+95 -56
View File
@@ -1,4 +1,4 @@
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage}; use generational_box::{GenerationalBox, Storage, SyncStorage, UnsyncStorage};
/// # Example /// # Example
/// ///
@@ -14,88 +14,119 @@ fn compile_fail() {}
#[test] #[test]
fn reused() { fn reused() {
let first_ptr; fn reused_test<S: Storage<i32>>() {
{ let first_ptr;
let owner = UnsyncStorage::owner(); {
first_ptr = owner.insert(1).raw_ptr(); let owner = S::owner();
drop(owner); first_ptr = owner.insert(1).raw_ptr();
} drop(owner);
{ }
let owner = UnsyncStorage::owner(); {
let second_ptr = owner.insert(1234).raw_ptr(); let owner = S::owner();
assert_eq!(first_ptr, second_ptr); let second_ptr = owner.insert(1234).raw_ptr();
drop(owner); assert_eq!(first_ptr, second_ptr);
drop(owner);
}
} }
reused_test::<UnsyncStorage>();
reused_test::<SyncStorage>();
} }
#[test] #[test]
fn leaking_is_ok() { fn leaking_is_ok() {
let data = String::from("hello world"); fn leaking_is_ok_test<S: Storage<String>>() {
let key; let data = String::from("hello world");
{ let key;
// create an owner {
let owner = UnsyncStorage::owner(); // create an owner
// insert data into the store let owner = S::owner();
key = owner.insert(data); // insert data into the store
// don't drop the owner key = owner.insert(data);
std::mem::forget(owner); // don't drop the owner
std::mem::forget(owner);
}
assert_eq!(
key.try_read().as_deref().unwrap(),
&"hello world".to_string()
);
} }
assert_eq!(
key.try_read().as_deref().unwrap(), leaking_is_ok_test::<UnsyncStorage>();
&"hello world".to_string() leaking_is_ok_test::<SyncStorage>();
);
} }
#[test] #[test]
fn drops() { fn drops() {
let data = String::from("hello world"); fn drops_test<S: Storage<String>>() {
let key; let data = String::from("hello world");
{ let key;
// create an owner {
let owner = UnsyncStorage::owner(); // create an owner
// insert data into the store let owner = S::owner();
key = owner.insert(data); // insert data into the store
// drop the owner key = owner.insert(data);
// drop the owner
}
assert!(key.try_read().is_err());
} }
assert!(key.try_read().is_err());
drops_test::<UnsyncStorage>();
drops_test::<SyncStorage>();
} }
#[test] #[test]
fn works() { fn works() {
let owner = UnsyncStorage::owner(); fn works_test<S: Storage<i32>>() {
let key = owner.insert(1); let owner = S::owner();
let key = owner.insert(1);
assert_eq!(*key.read(), 1); assert_eq!(*key.read(), 1);
}
works_test::<UnsyncStorage>();
works_test::<SyncStorage>();
} }
#[test] #[test]
fn insert_while_reading() { fn insert_while_reading() {
let owner = UnsyncStorage::owner(); fn insert_while_reading_test<S: Storage<String> + Storage<&'static i32>>() {
let key; let owner = S::owner();
{ let key;
let data: String = "hello world".to_string(); {
key = owner.insert(data); let data: String = "hello world".to_string();
key = owner.insert(data);
}
let value = key.read();
owner.insert(&1);
assert_eq!(*value, "hello world");
} }
let value = key.read();
owner.insert(&1); insert_while_reading_test::<UnsyncStorage>();
assert_eq!(*value, "hello world"); insert_while_reading_test::<SyncStorage>();
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn panics() { fn panics() {
let owner = UnsyncStorage::owner(); fn panics_test<S: Storage<i32>>() {
let key = owner.insert(1); let owner = S::owner();
drop(owner);
assert_eq!(*key.read(), 1); let key = owner.insert(1);
drop(owner);
assert_eq!(*key.read(), 1);
}
panics_test::<UnsyncStorage>();
panics_test::<SyncStorage>();
} }
#[test] #[test]
fn fuzz() { fn fuzz() {
fn maybe_owner_scope( fn maybe_owner_scope<S: Storage<String>>(
valid_keys: &mut Vec<GenerationalBox<String>>, valid_keys: &mut Vec<GenerationalBox<String, S>>,
invalid_keys: &mut Vec<GenerationalBox<String>>, invalid_keys: &mut Vec<GenerationalBox<String, S>>,
path: &mut Vec<u8>, path: &mut Vec<u8>,
) { ) {
let branch_cutoff = 5; let branch_cutoff = 5;
@@ -106,28 +137,36 @@ fn fuzz() {
}; };
for i in 0..children { for i in 0..children {
let owner = UnsyncStorage::owner(); let owner = S::owner();
let key = owner.insert(format!("hello world {path:?}")); let key = owner.insert(format!("hello world {path:?}"));
println!("created new box {key:?}");
valid_keys.push(key); valid_keys.push(key);
path.push(i); path.push(i);
// read all keys // read all keys
println!("{:?}", path); println!("{:?}", path);
for key in valid_keys.iter() { for key in valid_keys.iter() {
println!("reading {key:?}");
let value = key.read(); let value = key.read();
println!("{:?}", &*value); println!("{:?}", &*value);
assert!(value.starts_with("hello world")); assert!(value.starts_with("hello world"));
} }
#[cfg(any(debug_assertions, feature = "check_generation"))]
for key in invalid_keys.iter() { for key in invalid_keys.iter() {
println!("reading invalid {key:?}");
assert!(key.try_read().is_err()); assert!(key.try_read().is_err());
} }
maybe_owner_scope(valid_keys, invalid_keys, path); maybe_owner_scope(valid_keys, invalid_keys, path);
invalid_keys.push(valid_keys.pop().unwrap()); let invalid = valid_keys.pop().unwrap();
println!("popping {invalid:?}");
invalid_keys.push(invalid);
path.pop(); path.pop();
} }
} }
for _ in 0..10 { for _ in 0..10 {
maybe_owner_scope(&mut Vec::new(), &mut Vec::new(), &mut Vec::new()); maybe_owner_scope::<UnsyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());
}
for _ in 0..10 {
maybe_owner_scope::<SyncStorage>(&mut Vec::new(), &mut Vec::new(), &mut Vec::new());
} }
} }
+75 -60
View File
@@ -1,38 +1,33 @@
use std::cell::{Ref, RefMut};
use generational_box::{ use generational_box::{
AlreadyBorrowedError, AlreadyBorrowedMutError, AnyStorage, BorrowError, BorrowMutError, AlreadyBorrowedError, AlreadyBorrowedMutError, BorrowError, BorrowMutError, GenerationalBox,
GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, UnsyncStorage, ValueDroppedError, Owner, Storage, SyncStorage, UnsyncStorage, ValueDroppedError,
}; };
#[track_caller] #[track_caller]
fn read_at_location( fn read_at_location<S: Storage<i32>>(
value: GenerationalBox<i32>, value: GenerationalBox<i32, S>,
) -> ( ) -> (S::Ref<'static, i32>, &'static std::panic::Location<'static>) {
GenerationalRef<Ref<'static, i32>>,
&'static std::panic::Location<'static>,
) {
let location = std::panic::Location::caller(); let location = std::panic::Location::caller();
let read = value.read(); let read = value.read();
(read, location) (read, location)
} }
#[track_caller] #[track_caller]
fn write_at_location( fn write_at_location<S: Storage<i32>>(
value: GenerationalBox<i32>, value: GenerationalBox<i32, S>,
) -> ( ) -> (S::Mut<'static, i32>, &'static std::panic::Location<'static>) {
GenerationalRefMut<RefMut<'static, i32>>,
&'static std::panic::Location<'static>,
) {
let location = std::panic::Location::caller(); let location = std::panic::Location::caller();
let write = value.write(); let write = value.write();
(write, location) (write, location)
} }
#[track_caller] #[track_caller]
fn create_at_location( fn create_at_location<S: Storage<i32>>(
owner: &Owner, owner: &Owner<S>,
) -> (GenerationalBox<i32>, &'static std::panic::Location<'static>) { ) -> (
GenerationalBox<i32, S>,
&'static std::panic::Location<'static>,
) {
let location = std::panic::Location::caller(); let location = std::panic::Location::caller();
let value = owner.insert(1); let value = owner.insert(1);
(value, location) (value, location)
@@ -40,65 +35,85 @@ fn create_at_location(
#[test] #[test]
fn read_while_writing_error() { fn read_while_writing_error() {
let owner = UnsyncStorage::owner(); fn read_while_writing_error_test<S: Storage<i32>>() {
let value = owner.insert(1); let owner = S::owner();
let value = owner.insert(1);
let (write, location) = write_at_location(value); let (write, location) = write_at_location(value);
assert_eq!( assert_eq!(
value.try_read().err(), value.try_read().err(),
Some(BorrowError::AlreadyBorrowedMut( Some(BorrowError::AlreadyBorrowedMut(
AlreadyBorrowedMutError::new(location) AlreadyBorrowedMutError::new(location)
)) ))
); );
drop(write); drop(write);
}
// For sync storage this will deadlock
read_while_writing_error_test::<UnsyncStorage>();
} }
#[test] #[test]
fn read_after_dropped_error() { fn read_after_dropped_error() {
let owner = UnsyncStorage::owner(); fn read_after_dropped_error_test<S: Storage<i32>>() {
let (value, location) = create_at_location(&owner); let owner = S::owner();
drop(owner); let (value, location) = create_at_location(&owner);
assert_eq!( drop(owner);
value.try_read().err(), assert_eq!(
Some(BorrowError::Dropped(ValueDroppedError::new(location))) value.try_read().err(),
); Some(BorrowError::Dropped(ValueDroppedError::new(location)))
);
}
read_after_dropped_error_test::<UnsyncStorage>();
read_after_dropped_error_test::<SyncStorage>();
} }
#[test] #[test]
fn write_while_writing_error() { fn write_while_writing_error() {
let owner = UnsyncStorage::owner(); fn write_while_writing_error_test<S: Storage<i32>>() {
let value = owner.insert(1); let owner = S::owner();
let value = owner.insert(1);
#[allow(unused)] #[allow(unused)]
let (write, location) = write_at_location(value); let (write, location) = write_at_location(value);
let write_result = value.try_write(); let write_result = value.try_write();
assert!(write_result.is_err()); assert!(write_result.is_err());
#[cfg(debug_assertions)] #[cfg(any(debug_assertions, feature = "debug_borrows"))]
assert_eq!( assert_eq!(
write_result.err(), write_result.err(),
Some(BorrowMutError::AlreadyBorrowedMut( Some(BorrowMutError::AlreadyBorrowedMut(
AlreadyBorrowedMutError::new(location) AlreadyBorrowedMutError::new(location)
)) ))
); );
drop(write); drop(write);
}
// For sync storage this will deadlock
write_while_writing_error_test::<UnsyncStorage>();
} }
#[test] #[test]
fn write_while_reading_error() { fn write_while_reading_error() {
let owner = UnsyncStorage::owner(); fn write_while_reading_error_test<S: Storage<i32>>() {
let value = owner.insert(1); let owner = S::owner();
let value = owner.insert(1);
let (read, location) = read_at_location(value); let (read, location) = read_at_location(value);
assert_eq!( assert_eq!(
value.try_write().err(), value.try_write().err(),
Some(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError::new( Some(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError::new(
vec![location] vec![location]
))) )))
); );
drop(read); drop(read);
}
// For sync storage this will deadlock
write_while_reading_error_test::<UnsyncStorage>();
} }
+42
View File
@@ -0,0 +1,42 @@
// Regression test for https://github.com/DioxusLabs/dioxus/issues/2636
use std::time::Duration;
use generational_box::{AnyStorage, GenerationalBox, SyncStorage};
#[test]
fn race_condition_regression() {
for _ in 0..100 {
let handle = {
let owner = SyncStorage::owner();
let key = owner.insert(1u64);
let handle = std::thread::spawn(move || reader(key));
std::thread::sleep(Duration::from_millis(10));
handle
// owner is dropped now
};
// owner is *recycled*
let owner = SyncStorage::owner();
let _key = owner.insert(2u64);
let _ = handle.join();
}
}
fn reader(key: GenerationalBox<u64, SyncStorage>) {
for _ in 0..1000000 {
match key.try_read() {
Ok(value) => {
if *value == 2 {
panic!("Read a new value with the old generation");
} else {
// fine
}
}
Err(err) => {
eprintln!("bailing out - {err:?}");
break;
}
}
}
}
+2 -6
View File
@@ -75,16 +75,12 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
pub(crate) fn new_with_caller( pub(crate) fn new_with_caller(
value: T, value: T,
#[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, caller: &'static std::panic::Location<'static>,
) -> Self { ) -> Self {
let owner = current_owner(); let owner = current_owner();
Self { Self {
value: owner.insert_with_caller( value: owner.insert_with_caller(value, caller),
value,
#[cfg(debug_assertions)]
caller,
),
origin_scope: current_scope_id().expect("in a virtual dom"), origin_scope: current_scope_id().expect("in a virtual dom"),
} }
} }
-2
View File
@@ -167,7 +167,6 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
/// use_hook(move || Signal::new_with_caller(function(), caller)) /// use_hook(move || Signal::new_with_caller(function(), caller))
/// } /// }
/// ``` /// ```
#[allow(unused)]
pub fn new_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self { pub fn new_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self {
Self { Self {
inner: CopyValue::new_with_caller( inner: CopyValue::new_with_caller(
@@ -175,7 +174,6 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
subscribers: Default::default(), subscribers: Default::default(),
value, value,
}, },
#[cfg(debug_assertions)]
caller, caller,
), ),
} }