Skip to content

Commit 5d1fe16

Browse files
authored
Fix run_system for adapter systems wrapping exclusive systems (#18406)
# Objective Fix panic in `run_system` when running an exclusive system wrapped in a `PipeSystem` or `AdapterSystem`. #18076 introduced a `System::run_without_applying_deferred` method. It normally calls `System::run_unsafe`, but `ExclusiveFunctionSystem::run_unsafe` panics, so it was overridden for that type. Unfortunately, `PipeSystem::run_without_applying_deferred` still calls `PipeSystem::run_unsafe`, which can then call `ExclusiveFunctionSystem::run_unsafe` and panic. ## Solution Make `ExclusiveFunctionSystem::run_unsafe` work instead of panicking. Clarify the safety requirements that make this sound. The alternative is to override `run_without_applying_deferred` in `PipeSystem`, `CombinatorSystem`, `AdapterSystem`, `InfallibleSystemWrapper`, and `InfallibleObserverWrapper`. That seems like a lot of extra code just to preserve a confusing special case! Remove some implementations of `System::run` that are no longer necessary with this change. This slightly changes the behavior of `PipeSystem` and `CombinatorSystem`: Currently `run` will call `apply_deferred` on the first system before running the second, but after this change it will only call it after *both* systems have run. The new behavior is consistent with `run_unsafe` and `run_without_applying_deferred`, and restores the behavior prior to #11823. The panic was originally necessary because [`run_unsafe` took `&World`](https://github.com/bevyengine/bevy/pull/6083/files#diff-708dfc60ec5eef432b20a6f471357a7ea9bfb254dc2f918d5ed4a66deb0e85baR90). Now that it takes `UnsafeWorldCell`, it is possible to make it work. See also Cart's concerns at #4166 (comment), although those also predate `UnsafeWorldCell`. And see #6698 for a previous bug caused by this panic.
1 parent 7a8dfda commit 5d1fe16

File tree

9 files changed

+20
-45
lines changed

9 files changed

+20
-45
lines changed

crates/bevy_ecs/src/observer/runner.rs

+1
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
402402
// - `update_archetype_component_access` is called first
403403
// - there are no outstanding references to world except a private component
404404
// - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld`
405+
// and is never exclusive
405406
// - system is the same type erased system from above
406407
unsafe {
407408
(*system).update_archetype_component_access(world);

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs

+4
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ impl ExecutorState {
452452

453453
// SAFETY:
454454
// - Caller ensured no other reference to this system exists.
455+
// - `system_task_metadata[system_index].is_exclusive` is `false`,
456+
// so `System::is_exclusive` returned `false` when we called it.
455457
// - `can_run` has been called, which calls `update_archetype_component_access` with this system.
456458
// - `can_run` returned true, so no systems with conflicting world access are running.
457459
unsafe {
@@ -608,6 +610,7 @@ impl ExecutorState {
608610

609611
/// # Safety
610612
/// - Caller must not alias systems that are running.
613+
/// - `is_exclusive` must have returned `false` for the specified system.
611614
/// - `world` must have permission to access the world data
612615
/// used by the specified system.
613616
/// - `update_archetype_component_access` must have been called with `world`
@@ -625,6 +628,7 @@ impl ExecutorState {
625628
// SAFETY:
626629
// - The caller ensures that we have permission to
627630
// access the world data used by the system.
631+
// - `is_exclusive` returned false
628632
// - `update_archetype_component_access` has been called.
629633
unsafe {
630634
if let Err(err) = __rust_begin_short_backtrace::run_unsafe(

crates/bevy_ecs/src/system/adapter_system.rs

-6
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,6 @@ where
162162
})
163163
}
164164

165-
#[inline]
166-
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut crate::prelude::World) -> Self::Out {
167-
self.func
168-
.adapt(input, |input| self.system.run(input, world))
169-
}
170-
171165
#[inline]
172166
fn apply_deferred(&mut self, world: &mut crate::prelude::World) {
173167
self.system.apply_deferred(world);

crates/bevy_ecs/src/system/combinator.rs

+1-18
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ where
176176
input,
177177
// SAFETY: The world accesses for both underlying systems have been registered,
178178
// so the caller will guarantee that no other systems will conflict with `a` or `b`.
179+
// If either system has `is_exclusive()`, then the combined system also has `is_exclusive`.
179180
// Since these closures are `!Send + !Sync + !'static`, they can never be called
180181
// in parallel, so their world accesses will not conflict with each other.
181182
// Additionally, `update_archetype_component_access` has been called,
@@ -186,19 +187,6 @@ where
186187
)
187188
}
188189

189-
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
190-
let world = world.as_unsafe_world_cell();
191-
Func::combine(
192-
input,
193-
// SAFETY: Since these closures are `!Send + !Sync + !'static`, they can never
194-
// be called in parallel. Since mutable access to `world` only exists within
195-
// the scope of either closure, we can be sure they will never alias one another.
196-
|input| self.a.run(input, unsafe { world.world_mut() }),
197-
// SAFETY: See the above safety comment.
198-
|input| self.b.run(input, unsafe { world.world_mut() }),
199-
)
200-
}
201-
202190
#[inline]
203191
fn apply_deferred(&mut self, world: &mut World) {
204192
self.a.apply_deferred(world);
@@ -419,11 +407,6 @@ where
419407
self.b.run_unsafe(value, world)
420408
}
421409

422-
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
423-
let value = self.a.run(input, world);
424-
self.b.run(value, world)
425-
}
426-
427410
fn apply_deferred(&mut self, world: &mut World) {
428411
self.a.apply_deferred(world);
429412
self.b.apply_deferred(world);

crates/bevy_ecs/src/system/exclusive_function_system.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,12 @@ where
112112

113113
#[inline]
114114
unsafe fn run_unsafe(
115-
&mut self,
116-
_input: SystemIn<'_, Self>,
117-
_world: UnsafeWorldCell,
118-
) -> Self::Out {
119-
panic!("Cannot run exclusive systems with a shared World reference");
120-
}
121-
122-
fn run_without_applying_deferred(
123115
&mut self,
124116
input: SystemIn<'_, Self>,
125-
world: &mut World,
117+
world: UnsafeWorldCell,
126118
) -> Self::Out {
119+
// SAFETY: The safety is upheld by the caller.
120+
let world = unsafe { world.world_mut() };
127121
world.last_change_tick_scope(self.system_meta.last_run, |world| {
128122
#[cfg(feature = "trace")]
129123
let _span_guard = self.system_meta.system_span.enter();

crates/bevy_ecs/src/system/observer_system.rs

-6
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,6 @@ where
138138
Ok(())
139139
}
140140

141-
#[inline]
142-
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
143-
self.observer.run(input, world);
144-
Ok(())
145-
}
146-
147141
#[inline]
148142
fn apply_deferred(&mut self, world: &mut World) {
149143
self.observer.apply_deferred(world);

crates/bevy_ecs/src/system/schedule_system.rs

-6
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,6 @@ impl<S: System<In = (), Out = ()>> System for InfallibleSystemWrapper<S> {
6565
Ok(())
6666
}
6767

68-
#[inline]
69-
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
70-
self.0.run(input, world);
71-
Ok(())
72-
}
73-
7468
#[inline]
7569
fn apply_deferred(&mut self, world: &mut World) {
7670
self.0.apply_deferred(world);

crates/bevy_ecs/src/system/system.rs

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ pub trait System: Send + Sync + 'static {
6969
/// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data
7070
/// registered in `archetype_component_access`. There must be no conflicting
7171
/// simultaneous accesses while the system is running.
72+
/// - If [`System::is_exclusive`] returns `true`, then it must be valid to call
73+
/// [`UnsafeWorldCell::world_mut`] on `world`.
7274
/// - The method [`System::update_archetype_component_access`] must be called at some
7375
/// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`]
7476
/// panics (or otherwise does not return for any reason), this method must not be called.

crates/bevy_ecs/src/system/system_registry.rs

+9
Original file line numberDiff line numberDiff line change
@@ -896,4 +896,13 @@ mod tests {
896896

897897
assert_eq!(INVOCATIONS_LEFT.get(), 0);
898898
}
899+
900+
#[test]
901+
fn run_system_exclusive_adapters() {
902+
let mut world = World::new();
903+
fn system(_: &mut World) {}
904+
world.run_system_cached(system).unwrap();
905+
world.run_system_cached(system.pipe(system)).unwrap();
906+
world.run_system_cached(system.map(|()| {})).unwrap();
907+
}
899908
}

0 commit comments

Comments
 (0)