Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Panic on dropping NonSend in non-origin thread. #6534

Closed
wants to merge 57 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bbb7e85
Abort on dropping NonSend in non-origin thread.
james7132 Nov 10, 2022
0f4b73d
Fix safety comments
james7132 Nov 10, 2022
e83e8e3
Invert is_send
james7132 Nov 10, 2022
e46ec0c
Factor out drop-abort
james7132 Nov 10, 2022
0898a54
Fix access, replace MainThreadValidator
james7132 Nov 16, 2022
e4d61c1
Update docs
james7132 Nov 16, 2022
944b83c
Switch to always panic, use ManuallyDrop
james7132 Nov 16, 2022
9b712df
Formatting
james7132 Nov 16, 2022
776ffaf
Merge branch 'main' into abort-on-nonsend-drop
james7132 Dec 1, 2022
ba27136
Split out into NonSendResources
james7132 Dec 2, 2022
c3708d4
Cleanup docs and type definitions
james7132 Dec 2, 2022
16ae491
Fix CI
james7132 Dec 2, 2022
cc4c611
Merge branch 'main' into abort-on-nonsend-drop
james7132 Dec 2, 2022
1a09486
Update safety docs.
james7132 Dec 2, 2022
f935aa2
Actually fix CI
james7132 Dec 2, 2022
f4281e6
Small cleanup
james7132 Dec 2, 2022
1692da8
Fix WorldCell's use of NonSend
james7132 Dec 2, 2022
2b11ea3
Fix bevy_window
james7132 Dec 2, 2022
638e95c
Use const generics to split on Send/!Send
james7132 Dec 2, 2022
c37f26e
Use const generics instead of duplicating code
james7132 Dec 2, 2022
ee5de65
Use the right branch
james7132 Dec 2, 2022
d195cb6
Add panic sections to World's API docs
james7132 Dec 3, 2022
93334a0
Apply suggestions from code review
james7132 Dec 3, 2022
8f72292
Remove non_send_scope
james7132 Dec 3, 2022
b3fffd9
Split insert_non_send_by_id from insert_resource_by_id
james7132 Dec 3, 2022
54e5a50
Fix safety comment and tests
james7132 Dec 3, 2022
5f6e91b
Exhaustively decompose Storages in check_change_ticks
james7132 Dec 3, 2022
cfb8173
Remove spurious is_send in docs
james7132 Dec 3, 2022
5900696
Update safety docs on initialize_resource_internal
james7132 Dec 3, 2022
95e575f
Check that Send resources have correct metadata on initialization
james7132 Dec 3, 2022
2bff28c
Formatting
james7132 Dec 3, 2022
6af4c4e
Undo changes to BlobVec, how the hell did this get in here
james7132 Dec 3, 2022
e056626
Fix docs link
james7132 Dec 3, 2022
58bcb66
Apply suggestions from code review
james7132 Dec 4, 2022
9db8041
Stronger assert
james7132 Dec 4, 2022
d713d84
Fix CI
james7132 Dec 4, 2022
314a86e
Apply suggestions from code review
james7132 Dec 5, 2022
e06677a
Small cleanup
james7132 Dec 5, 2022
de05d57
Update crates/bevy_ecs/src/world/mod.rs
james7132 Dec 5, 2022
b4d6303
Merge branch 'main' into abort-on-nonsend-drop
james7132 Dec 6, 2022
a9895c0
Merge branch 'abort-on-nonsend-drop' of github.com:james7132/bevy int…
james7132 Dec 7, 2022
db4c6d7
Apply suggestions from code review
james7132 Dec 8, 2022
7578306
Merge branch 'main' into abort-on-nonsend-drop
james7132 Dec 8, 2022
5e182fc
Move std::thread::panicking check to Drop impl
james7132 Dec 8, 2022
263ed85
Update safety comment about validate_access in Drop
james7132 Dec 8, 2022
e4a0a9a
Add the missing get_non_send_by_id APIs
james7132 Dec 8, 2022
5be8450
Formatting
james7132 Dec 8, 2022
587ea78
Add test for distinct storage and missing panic comments
james7132 Dec 8, 2022
68de55f
Make the safety comments on _by_id APIs more accurate
james7132 Dec 9, 2022
4e8412f
Reword panic docs.
james7132 Dec 12, 2022
ebe33dd
Merge branch 'main' into abort-on-nonsend-drop
james7132 Dec 12, 2022
87c88ab
Fix CI
james7132 Dec 12, 2022
6d39cd5
Merge branch 'main' into abort-on-nonsend-drop
james7132 Dec 19, 2022
8b8a735
Revert changes with NonSendMarker
james7132 Dec 19, 2022
6c8e988
Merge branch 'main' into abort-on-nonsend-drop
james7132 Jan 2, 2023
2c5be6d
Formatting
james7132 Jan 2, 2023
b236f9d
Merge branch 'main' into abort-on-nonsend-drop
james7132 Jan 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,7 @@ mod tests {

#[test]
#[should_panic(
expected = "attempted to access NonSend resource bevy_ecs::tests::NonSendA off of the main thread"
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
)]
fn non_send_resource_scope_from_different_thread() {
let mut world = World::default();
Expand All @@ -1353,6 +1353,25 @@ mod tests {
}
}

#[test]
#[should_panic(
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
)]
fn non_send_resource_drop_from_different_thread() {
let mut world = World::default();
world.insert_non_send_resource(NonSendA::default());

let thread = std::thread::spawn(move || {
// Dropping the non-send resource on a different thread
// Should result in a panic
drop(world);
});

if let Err(err) = thread.join() {
std::panic::resume_unwind(err);
}
}

#[test]
fn insert_overwrite_drop() {
let (dropck1, dropped1) = DropCk::new_pair();
Expand Down
118 changes: 93 additions & 25 deletions crates/bevy_ecs/src/storage/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,54 @@ use crate::archetype::ArchetypeComponentId;
use crate::component::{ComponentId, ComponentTicks, Components, TickCells};
use crate::storage::{Column, SparseSet};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
use std::cell::UnsafeCell;
use std::mem::ManuallyDrop;
use std::thread::ThreadId;

/// The type-erased backing storage and metadata for a single resource within a [`World`].
///
/// [`World`]: crate::world::World
pub struct ResourceData {
column: Column,
column: ManuallyDrop<Column>,
type_name: String,
id: ArchetypeComponentId,
origin_thread_id: Option<ThreadId>,
}

impl Drop for ResourceData {
fn drop(&mut self) {
if self.is_present() {
self.validate_access();
}
// SAFETY: Drop is only called once upon dropping the ResourceData
// and is inaccessible after this as the parent ResourceData has
// been dropped.
james7132 marked this conversation as resolved.
Show resolved Hide resolved
unsafe {
ManuallyDrop::drop(&mut self.column);
}
}
}

impl ResourceData {
#[inline]
fn validate_access(&self) {
// Avoid aborting due to double panicking on the same thread.
if std::thread::panicking() {
return;
}
if let Some(origin_thread_id) = self.origin_thread_id {
if origin_thread_id != std::thread::current().id() {
// Panic in tests, as testing for aborting is nearly impossible
panic!(
"Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",
self.type_name,
origin_thread_id,
std::thread::current().id()
);
}
}
}

/// Returns true if the resource is populated.
#[inline]
pub fn is_present(&self) -> bool {
Expand All @@ -25,9 +63,16 @@ impl ResourceData {
}

/// Gets a read-only pointer to the underlying resource, if available.
///
/// # Panics
/// This will panic if a value is present, the underlying type is
/// `!Send`, and is not accessed from the original thread it was inserted in.
#[inline]
pub fn get_data(&self) -> Option<Ptr<'_>> {
self.column.get_data(0)
self.column.get_data(0).map(|res| {
self.validate_access();
res
})
}

/// Gets a read-only reference to the change ticks of the underlying resource, if available.
Expand All @@ -36,80 +81,97 @@ impl ResourceData {
self.column.get_ticks(0)
}

/// # Panics
/// This will panic if a value is present, the underlying type is
/// `!Send`, and is not accessed from the original thread it was inserted in.
#[inline]
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> {
self.column.get(0)
self.column.get(0).map(|res| {
self.validate_access();
res
})
}

/// Inserts a value into the resource. If a value is already present
/// it will be replaced.
///
/// # Panics
/// This will panic if a value is present, the underlying type is
/// `!Send`, and is not accessed from the original thread it was inserted in.
///
/// # Safety
/// `value` must be valid for the underlying type for the resource.
///
/// The underlying type must be [`Send`] or be inserted from the main thread.
/// This can be validated with [`World::validate_non_send_access_untyped`].
///
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
#[inline]
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
if self.is_present() {
self.validate_access();
self.column.replace(0, value, change_tick);
} else {
self.origin_thread_id = self.origin_thread_id.map(|_| std::thread::current().id());
self.column.push(value, ComponentTicks::new(change_tick));
}
}

/// Inserts a value into the resource with a pre-existing change tick. If a
/// value is already present it will be replaced.
///
/// # Panics
/// This will panic if a value is present, the underlying type is
/// `!Send`, and is not accessed from the original thread it was inserted in.
///
/// # Safety
/// `value` must be valid for the underlying type for the resource.
///
/// The underlying type must be [`Send`] or be inserted from the main thread.
/// This can be validated with [`World::validate_non_send_access_untyped`].
///
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
#[inline]
pub(crate) unsafe fn insert_with_ticks(
&mut self,
value: OwningPtr<'_>,
change_ticks: ComponentTicks,
) {
if self.is_present() {
self.validate_access();
self.column.replace_untracked(0, value);
*self.column.get_added_ticks_unchecked(0).deref_mut() = change_ticks.added;
*self.column.get_changed_ticks_unchecked(0).deref_mut() = change_ticks.changed;
} else {
self.origin_thread_id = self.origin_thread_id.map(|_| std::thread::current().id());
self.column.push(value, change_ticks);
}
}

/// Removes a value from the resource, if present.
///
/// # Panics
/// This will panic if a value is present, the underlying type is
/// `!Send`, and is not accessed from the original thread it was inserted in.
///
/// # Safety
/// The underlying type must be [`Send`] or be removed from the main thread.
/// This can be validated with [`World::validate_non_send_access_untyped`].
/// The underlying type must be [`Send`] or be removed from the thread it was
/// inserted from.
///
/// The removed value must be used or dropped.
///
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
#[inline]
#[must_use = "The returned pointer to the removed component should be used or dropped"]
pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
self.column.swap_remove_and_forget(0)
self.is_present()
.then(|| self.validate_access())
.and_then(|_| self.column.swap_remove_and_forget(0))
}

/// Removes a value from the resource, if present, and drops it.
///
/// # Safety
/// The underlying type must be [`Send`] or be removed from the main thread.
/// This can be validated with [`World::validate_non_send_access_untyped`].
/// # Panics
/// This will panic if a value is present, the underlying type is
/// `!Send`, and is not accessed from the original thread it was inserted in.
///
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
/// # Safety
/// The underlying type must be [`Send`] or be removed from the thread it was
/// inserted from.
#[inline]
pub(crate) unsafe fn remove_and_drop(&mut self) {
self.column.clear();
if self.is_present() {
self.validate_access();
self.column.clear();
}
}
}

Expand Down Expand Up @@ -161,17 +223,23 @@ impl Resources {
///
/// # Panics
/// Will panic if `component_id` is not valid for the provided `components`
pub(crate) fn initialize_with(
///
/// # Safety
/// `is_send` must be accurate for the Resource that is being initialized.
pub(crate) unsafe fn initialize_with(
&mut self,
component_id: ComponentId,
components: &Components,
is_send: bool,
f: impl FnOnce() -> ArchetypeComponentId,
) -> &mut ResourceData {
self.resources.get_or_insert_with(component_id, || {
let component_info = components.get_info(component_id).unwrap();
ResourceData {
column: Column::with_capacity(component_info, 1),
column: ManuallyDrop::new(Column::with_capacity(component_info, 1)),
type_name: String::from(component_info.name()),
id: f(),
origin_thread_id: (!is_send).then(|| std::thread::current().id()),
}
})
}
Expand Down
4 changes: 0 additions & 4 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,6 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState<T> {
world: &'w World,
change_tick: u32,
) -> Self::Item {
world.validate_non_send_access::<T>();
let (ptr, ticks) = world
.get_resource_with_ticks(state.component_id)
.unwrap_or_else(|| {
Expand Down Expand Up @@ -1062,7 +1061,6 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState<T> {
world: &'w World,
change_tick: u32,
) -> Self::Item {
world.validate_non_send_access::<T>();
world
.get_resource_with_ticks(state.0.component_id)
.map(|(ptr, ticks)| NonSend {
Expand Down Expand Up @@ -1129,7 +1127,6 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState<T> {
world: &'w World,
change_tick: u32,
) -> Self::Item {
world.validate_non_send_access::<T>();
let (ptr, ticks) = world
.get_resource_with_ticks(state.component_id)
.unwrap_or_else(|| {
Expand Down Expand Up @@ -1173,7 +1170,6 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState<T> {
world: &'w World,
change_tick: u32,
) -> Self::Item {
world.validate_non_send_access::<T>();
world
.get_resource_with_ticks(state.0.component_id)
.map(|(ptr, ticks)| NonSendMut {
Expand Down
Loading