hrx/execution/
native_session.rs1use super::{GpuLane, Runtime, scheduler::NativeLease};
2use crate::{Error, Result};
3use std::{
4 cell::RefCell,
5 panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
6 sync::Arc,
7};
8
9thread_local! {
10 static ACTIVE: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
11}
12
13struct Entered(usize);
14impl Entered {
15 fn new(runtime: &Runtime) -> Result<Self> {
16 let id = Arc::as_ptr(&runtime.inner.core) as usize;
17 ACTIVE.with(|active| {
18 let mut active = active.borrow_mut();
19 if active.contains(&id) {
20 return Err(Error::Busy(
21 "recursive native session in the same runtime".into(),
22 ));
23 }
24 active.push(id);
25 Ok(Self(id))
26 })
27 }
28}
29impl Drop for Entered {
30 fn drop(&mut self) {
31 ACTIVE.with(|active| active.borrow_mut().retain(|id| *id != self.0));
32 }
33}
34
35pub struct NativeSession<T> {
44 runtime: Runtime,
45 state: Option<T>,
46 fence: fn(&mut T) -> Result<()>,
47}
48
49impl<T> NativeSession<T> {
50 pub unsafe fn new(runtime: &Runtime, state: T, fence: fn(&mut T) -> Result<()>) -> Self {
59 Self {
60 runtime: runtime.clone(),
61 state: Some(state),
62 fence,
63 }
64 }
65
66 pub fn state(&self) -> Result<&T> {
68 self.state
69 .as_ref()
70 .ok_or_else(|| Error::DeviceLost("native session is quarantined".into()))
71 }
72
73 pub unsafe fn run<R>(&mut self, stage: impl FnOnce(&mut T) -> R) -> Result<R> {
89 self.state()?;
90 let _entered = Entered::new(&self.runtime)?;
91 let _lane = NativeLease::acquire(&self.runtime.inner.core)?;
92 let started = std::time::Instant::now();
93 let state = self.state.as_mut().expect("checked before admission");
94 let outcome = catch_unwind(AssertUnwindSafe(|| stage(state)));
95 let completion = catch_unwind(AssertUnwindSafe(|| (self.fence)(state)));
96 let failed = !matches!(completion, Ok(Ok(())));
97 self.runtime.inner.core.tracer.record(
98 started,
99 started.elapsed(),
100 GpuLane::Compute as usize,
101 0,
102 failed || outcome.is_err(),
103 );
104 if failed {
105 std::mem::forget(self.state.take());
108 }
109 match outcome {
110 Err(panic) => resume_unwind(panic),
111 Ok(value) => match completion {
112 Err(panic) => resume_unwind(panic),
113 Ok(result) => result.map(|()| value),
114 },
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use std::sync::atomic::{AtomicUsize, Ordering};
123
124 struct State {
125 fences: Arc<AtomicUsize>,
126 drops: Arc<AtomicUsize>,
127 fail: bool,
128 panic: bool,
129 }
130 impl Drop for State {
131 fn drop(&mut self) {
132 self.drops.fetch_add(1, Ordering::SeqCst);
133 }
134 }
135 fn session(runtime: &Runtime) -> NativeSession<State> {
136 unsafe {
138 NativeSession::new(
139 runtime,
140 State {
141 fences: Arc::new(AtomicUsize::new(0)),
142 drops: Arc::new(AtomicUsize::new(0)),
143 fail: false,
144 panic: false,
145 },
146 |state| {
147 state.fences.fetch_add(1, Ordering::SeqCst);
148 assert!(!state.panic, "fence panic");
149 if state.fail {
150 Err(Error::DeviceLost("fence failed".into()))
151 } else {
152 Ok(())
153 }
154 },
155 )
156 }
157 }
158
159 #[test]
160 fn borrowed_stages_drain_errors_and_panics_then_reuse() {
161 let runtime = Runtime::new().unwrap();
162 let mut session = session(&runtime);
163 let drops = session.state().unwrap().drops.clone();
164 let mut output = 0;
165 unsafe {
167 session.run(|_| {
168 output = 42;
169 Err::<(), _>("client error")
170 })
171 }
172 .unwrap()
173 .unwrap_err();
174 assert_eq!(output, 42);
175 assert!(
176 catch_unwind(AssertUnwindSafe(|| unsafe {
177 session.run::<()>(|_| panic!("stage panic"))
178 }))
179 .is_err()
180 );
181 assert_eq!(unsafe { session.run(|_| 7) }.unwrap(), 7);
182 assert_eq!(session.state().unwrap().fences.load(Ordering::SeqCst), 3);
183 drop(session);
184 assert_eq!(drops.load(Ordering::SeqCst), 1);
185 }
186
187 #[test]
188 #[cfg_attr(miri, ignore = "deliberate quarantine leak")]
189 fn uncertain_fences_keep_owners_and_reject_retry() {
190 for panic in [false, true] {
191 let runtime = Runtime::new().unwrap();
192 let mut session = session(&runtime);
193 let drops = session.state().unwrap().drops.clone();
194 let fences = session.state().unwrap().fences.clone();
195 let result = catch_unwind(AssertUnwindSafe(|| unsafe {
196 session.run(|state| {
197 state.fail = true;
198 state.panic = panic;
199 })
200 }));
201 if panic {
202 assert!(result.is_err());
203 } else {
204 assert!(result.unwrap().is_err());
205 }
206 assert!(matches!(
207 unsafe { session.run::<()>(|_| unreachable!()) },
208 Err(Error::DeviceLost(_))
209 ));
210 drop(session);
211 assert_eq!(fences.load(Ordering::SeqCst), 1);
212 assert_eq!(drops.load(Ordering::SeqCst), 0);
213 let mut next = super::tests::session(&runtime);
215 unsafe { next.run(|_| ()) }.unwrap();
216 }
217 }
218
219 #[test]
220 fn recursive_admission_returns_busy_instead_of_deadlocking() {
221 let runtime = Runtime::new().unwrap();
222 let mut outer = session(&runtime);
223 let mut inner = session(&runtime);
224 let result = unsafe { outer.run(|_| inner.run(|_| ())) }.unwrap();
225 assert!(matches!(result, Err(Error::Busy(_))));
226 unsafe { inner.run(|_| ()) }.unwrap();
227 }
228}