app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
Share frozen native Rust mechanical development checkpoint and qualification scope
b2d7cdb
26d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
//! Main-thread lifecycle guards shared by the native adapter and fault tests.
use std::{
cell::{Cell, RefCell},
mem::ManuallyDrop,
};
/// Child SDK storage can borrow document/context pointers. Parent teardown must
/// remain blocked until its one explicit release has a confirmed outcome.
#[derive(Default)]
pub(crate) struct ChildLeaseFence(Cell<bool>);
impl ChildLeaseFence {
pub fn ensure_clear(&self) -> Result<(), String> {
if self.0.get() {
Err("child SDK lease outstanding or release unconfirmed; teardown refused".into())
} else {
Ok(())
}
}
pub fn begin(&self) -> Result<ChildLease<'_>, String> {
self.ensure_clear()?;
self.0.set(true);
Ok(ChildLease(self))
}
}
/// Not Clone; abandoning the token keeps the parent fence closed. No destructor
/// invokes SDK cleanup, including unwind/TLS paths.
pub(crate) struct ChildLease<'a>(&'a ChildLeaseFence);
impl ChildLease<'_> {
/// Only for an opening ABI whose failed return guarantees no acquired lease.
pub fn unopened(self) {
self.0 .0.set(false);
}
pub fn run<H, R>(
self,
handle: H,
work: impl FnOnce(&H) -> R,
release: impl FnOnce(H) -> Result<(), String>,
) -> Result<R, String> {
let observed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| work(&handle)));
let closed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| release(handle)));
match closed {
Ok(Ok(())) => self.0 .0.set(false),
Ok(Err(error)) => return Err(error),
Err(_) => return Err("child SDK release panicked; teardown refused".into()),
}
match observed {
Ok(result) => Ok(result),
Err(payload) => std::panic::resume_unwind(payload),
}
}
}
/// TLS destruction can occur under Windows loader constraints. Only an explicit
/// Fusion stop callback may recover this value and perform joins/SDK teardown.
/// A skipped stop deliberately retains it until the process exits.
pub(crate) struct StateSlot<T>(RefCell<Option<ManuallyDrop<T>>>);
impl<T> StateSlot<T> {
pub const fn new() -> Self {
Self(RefCell::new(None))
}
pub fn put(&self, value: T) -> Result<(), T> {
let mut slot = self.0.borrow_mut();
if slot.is_some() {
return Err(value);
}
*slot = Some(ManuallyDrop::new(value));
Ok(())
}
pub fn take(&self) -> Option<T> {
self.0.borrow_mut().take().map(ManuallyDrop::into_inner)
}
}
#[derive(Default)]
pub(crate) enum CloseOnce {
#[default]
Open,
Attempting,
Closed,
Failed(String),
}
impl CloseOnce {
pub fn ensure_open(&self) -> Result<(), String> {
match self {
Self::Open => Ok(()),
_ => Err("SDK resource close was already attempted".into()),
}
}
pub fn close(&mut self, close: impl FnOnce() -> Result<(), String>) -> Result<(), String> {
match self {
Self::Closed => return Ok(()),
Self::Failed(error) => return Err(error.clone()),
Self::Attempting => return Err("SDK close outcome unknown; retry refused".into()),
Self::Open => {}
}
// A panic or an uncertain native delete can never trigger a second delete.
*self = Self::Attempting;
match close() {
Ok(()) => {
*self = Self::Closed;
Ok(())
}
Err(error) => {
*self = Self::Failed(error.clone());
Err(error)
}
}
}
}
#[derive(Default)]
pub(crate) struct CallbackGate {
active: Cell<bool>,
stopping: Cell<bool>,
}
impl CallbackGate {
pub fn enter(&self) -> Option<CallbackFlight<'_>> {
if self.stopping.get() || self.active.replace(true) {
return None;
}
Some(CallbackFlight(self))
}
pub fn begin_stop(&self) -> Result<(), String> {
if self.active.get() {
return Err("callback is in flight; teardown deferred".into());
}
self.stopping.set(true);
Ok(())
}
}
pub(crate) struct CallbackFlight<'a>(&'a CallbackGate);
impl Drop for CallbackFlight<'_> {
fn drop(&mut self) {
self.0.active.set(false);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
struct Tracked(Arc<AtomicUsize>);
impl Drop for Tracked {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn child_work_panic_releases_once_before_resuming_unwind() {
let fence = ChildLeaseFence::default();
let calls = Cell::new(0);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let token = fence.begin().unwrap();
let _: Result<(), String> = token.run(
7,
|_| panic!("work panic after acquisition"),
|id| {
assert_eq!(id, 7);
calls.set(calls.get() + 1);
Ok(())
},
);
}));
assert!(result.is_err());
assert_eq!(calls.get(), 1);
fence.ensure_clear().unwrap();
fence.begin().unwrap().unopened();
fence.ensure_clear().unwrap();
}
#[test]
fn failed_or_panicked_child_release_keeps_parent_fenced_without_retry() {
for panic_release in [false, true] {
let fence = ChildLeaseFence::default();
let calls = Cell::new(0);
let token = fence.begin().unwrap();
let result = token.run(
(),
|_| 42,
|_| {
calls.set(calls.get() + 1);
if panic_release {
panic!("release panic");
}
Err("release outcome unknown".into())
},
);
assert!(result.is_err());
assert_eq!(calls.get(), 1);
assert!(fence.ensure_clear().is_err());
assert!(fence.begin().is_err());
assert_eq!(calls.get(), 1);
}
}
#[test]
fn abandoned_child_token_never_claims_cleanup_or_calls_a_destructor() {
let fence = ChildLeaseFence::default();
let token = fence.begin().unwrap();
drop(token);
assert!(fence.ensure_clear().is_err());
assert!(fence.begin().is_err());
}
#[test]
fn tls_exit_never_implicitly_joins_workers_or_releases_sdk() {
thread_local! {static SLOT:StateSlot<Tracked>=const{StateSlot::new()};}
let drops = Arc::new(AtomicUsize::new(0));
let worker_count = drops.clone();
std::thread::spawn(move || {
SLOT.with(|slot| assert!(slot.put(Tracked(worker_count)).is_ok()))
})
.join()
.unwrap();
assert_eq!(drops.load(Ordering::SeqCst), 0);
}
#[test]
fn only_explicit_take_releases_owned_state() {
let slot = StateSlot::new();
let drops = Arc::new(AtomicUsize::new(0));
assert!(slot.put(Tracked(drops.clone())).is_ok());
assert_eq!(drops.load(Ordering::SeqCst), 0);
drop(slot.take().unwrap());
assert!(slot.take().is_none());
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
#[test]
fn uncertain_close_is_never_repeated_or_reported_released() {
let mut close = CloseOnce::default();
assert!(close.close(|| Err("unknown delete result".into())).is_err());
assert!(close.ensure_open().is_err());
assert_eq!(
close
.close(|| panic!("must not retry native delete"))
.unwrap_err(),
"unknown delete result"
);
}
#[test]
fn confirmed_close_is_idempotent_without_native_retry() {
let mut close = CloseOnce::default();
close.close(|| Ok(())).unwrap();
close.close(|| panic!("must not delete twice")).unwrap();
assert!(close.ensure_open().is_err());
}
#[test]
fn panic_during_close_latches_unknown() {
let mut close = CloseOnce::default();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
close.close(|| panic!("injected"))
}));
assert!(result.is_err());
assert!(close
.close(|| panic!("must not retry after panic"))
.is_err());
}
#[test]
fn callback_refuses_reentrancy_and_defers_teardown_until_exit() {
let gate = CallbackGate::default();
let flight = gate.enter().unwrap();
assert!(gate.enter().is_none());
assert!(gate.begin_stop().is_err());
drop(flight);
// A refused stop must leave the running instance usable.
drop(gate.enter().unwrap());
gate.begin_stop().unwrap();
assert!(gate.enter().is_none());
}
#[test]
fn panic_unwinds_flight_before_stop() {
let gate = CallbackGate::default();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _flight = gate.enter().unwrap();
panic!("injected callback panic");
}));
assert!(result.is_err());
gate.begin_stop().unwrap();
}
}