Harden concurrency and resource lifecycle (#101)
Some checks failed
Native code generation / deterministic (push) Failing after 2m4s
Concurrency and resource soak audit / soak (push) Failing after 6m31s
Imaging and meshing gate / native (push) Failing after 2m52s
JPEG 2000 feature / linux (push) Successful in 2m45s
Release platform and feature matrix / audit (push) Successful in 35s
Native Rust workspace compile / compile (push) Failing after 54s
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
Some checks failed
Native code generation / deterministic (push) Failing after 2m4s
Concurrency and resource soak audit / soak (push) Failing after 6m31s
Imaging and meshing gate / native (push) Failing after 2m52s
JPEG 2000 feature / linux (push) Successful in 2m45s
Release platform and feature matrix / audit (push) Successful in 35s
Native Rust workspace compile / compile (push) Failing after 54s
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
This commit is contained in:
@@ -242,20 +242,25 @@ impl ClientRuntime {
|
||||
}
|
||||
|
||||
self.cancellation.cancel();
|
||||
let mut services = self
|
||||
.services
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
// Service shutdown is user-controlled code. Move registrations out
|
||||
// before invoking it so re-entrant lifecycle calls cannot deadlock on
|
||||
// the registry mutex.
|
||||
let mut services = std::mem::take(
|
||||
&mut *self
|
||||
.services
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner),
|
||||
);
|
||||
services.sort_by_key(|entry| (entry.service.shutdown_phase(), entry.insertion_order));
|
||||
let mut first_error = None;
|
||||
for entry in services.iter() {
|
||||
for entry in &services {
|
||||
if let Err(error) = entry.service.shutdown()
|
||||
&& first_error.is_none()
|
||||
{
|
||||
first_error = Some(error);
|
||||
}
|
||||
}
|
||||
services.clear();
|
||||
drop(services);
|
||||
clear_cached(&self.agent_throttle_sender);
|
||||
clear_cached(&self.inventory_manager);
|
||||
clear_cached(&self.inventory_ais_client);
|
||||
@@ -1623,6 +1628,41 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct ReentrantService {
|
||||
client: Mutex<Option<GridClient>>,
|
||||
calls: AtomicUsize,
|
||||
}
|
||||
|
||||
impl ClientService for ReentrantService {
|
||||
fn name(&self) -> &'static str {
|
||||
"reentrant"
|
||||
}
|
||||
|
||||
fn shutdown(&self) -> Result<(), ClientCoreError> {
|
||||
self.calls.fetch_add(1, Ordering::AcqRel);
|
||||
let result = self
|
||||
.client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.as_mut()
|
||||
.expect("client installed")
|
||||
.register_service(Arc::new(RecordingService {
|
||||
name: "late",
|
||||
phase: ShutdownPhase::Manager,
|
||||
order: Arc::new(Mutex::new(Vec::new())),
|
||||
calls: AtomicUsize::new(0),
|
||||
}));
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ClientCoreError::InvalidLifecycle {
|
||||
state: ClientLifecycleState::ShuttingDown,
|
||||
..
|
||||
})
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)] // Exact representable constants are the compatibility contract.
|
||||
fn defaults_match_reference_and_validate() {
|
||||
@@ -1762,4 +1802,29 @@ mod tests {
|
||||
assert_eq!(network.calls.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(manager.calls.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_does_not_hold_service_registry_lock_across_callbacks() {
|
||||
let service = Arc::new(ReentrantService {
|
||||
client: Mutex::new(None),
|
||||
calls: AtomicUsize::new(0),
|
||||
});
|
||||
let client = GridClient::builder()
|
||||
.with_service(service.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
*service
|
||||
.client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(client.clone());
|
||||
|
||||
client.shutdown().unwrap();
|
||||
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Disposed);
|
||||
assert_eq!(service.calls.load(Ordering::Acquire), 1);
|
||||
service
|
||||
.client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user