bevyengine/bevy · critical
Attempted to access or drop non-send data {} from thread {:?
Error message
Attempted to access or drop non-send data {} from thread {:?} on a thread {:?}. This is not allowed. Aborting. What it means
NonSend<T> resources are not Send: bevy only permits them to be accessed - and dropped - on the exact thread they were created on. This panic fires from NonSend's validate_access when the recorded origin thread id does not match std::thread::current().id(), meaning non-send data crossed threads through a path that promised it would not. In no_std (single-threaded) builds the check is skipped.
Source
Thrown at crates/bevy_ecs/src/storage/non_send.rs:78
self.data.drop(1, self.is_present().into());
}
}
}
impl NonSendData {
/// The only row in the underlying `BlobArray`.
const ROW: usize = 0;
/// Validates that the access to `NonSendData` is only done on the thread they were created from.
///
/// # Panics
/// This will panic if called from a different thread than the one it was inserted from.
#[inline]
fn validate_access(&self) {
#[cfg(feature = "std")]
if self.origin_thread_id != Some(std::thread::current().id()) {
// Panic in tests, as testing for aborting is nearly impossible
panic!(
"Attempted to access or drop non-send data {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",
self.type_name,
self.origin_thread_id,
std::thread::current().id()
);
}
// TODO: Handle no_std non-send.
// Currently, no_std is single-threaded only, so this is safe to ignore.
// To support no_std multithreading, an alternative will be required.
// Remove the #[expect] attribute above when this is addressed.
}
/// Returns true if the data is populated.
#[inline]
pub fn is_present(&self) -> bool {
self.is_present
}View on GitHub (pinned to 396ca72708)
Solutions
- Create, access, and drop the World/NonSend resource on the same thread - typically main.
- If the data must be touched elsewhere, send ownership to the origin thread via a channel, or restructure it into Send data (raw handle plus thread-local context).
- In tests, construct and tear down the App inside the same test body instead of moving it between threads.
Example fix
// before
let app = thread::spawn(|| { let mut a = App::new(); a.insert_non_send_resource(Handle::new()); a }).join().unwrap();
drop(app); // NonSend dropped on the wrong thread -> panic
// after
let mut app = App::new();
app.insert_non_send_resource(Handle::new());
// insert, access and drop all happen on this same thread Defensive patterns
Strategy: validation
Prevention
- Keep the World/App lifecycle on one thread; send data between threads, never the World.
- Use NonSend<T> params only in schedules that run on the creating (main) thread; use plain resources for Send data.
- In multithreaded test harnesses, construct and drop the App inside the same test body.
When it happens
Trigger: Dropping the World (or removing the NonSend resource) on a different thread than where it was inserted; accessing NonSend data from a schedule running on worker threads; constructing an App in one thread and dropping it in another (tests, FFI hosts, embedded runtimes).
Common situations: Test harnesses that build an App inside thread::spawn and then drop it on main; engines/runtimes that relocate work between threads; window/GPU handles stored as NonSend touched after the main thread changed.
Related errors
- Cannot call `ReflectComponent::reflect_mut` on component {na
- Cannot call `ReflectComponent::reflect_unchecked_mut` on com
- Resource entity {} of {} has been despawned, when it's not s
- System with key {:?} does not exist in the schedule
- System set with key {:?} does not exist in the schedule
AI-assisted analysis of bevyengine/bevy@396ca72708 (2026-08-20).
Data as JSON: /api/errors/5c2bbd0ac4c343f3.
Report an issue: GitHub.