add a macro to declare thread unblock callbacks

This commit is contained in:
Ralf Jung 2024-05-26 19:14:56 +02:00
parent e6bb468b53
commit 2e89443b93
6 changed files with 257 additions and 250 deletions

View file

@ -75,7 +75,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn init_once_enqueue_and_block(
&mut self,
id: InitOnceId,
callback: impl UnblockCallback<'mir, 'tcx> + 'tcx,
callback: impl UnblockCallback<'tcx> + 'tcx,
) {
let this = self.eval_context_mut();
let thread = this.active_thread();

View file

@ -35,6 +35,10 @@ macro_rules! declare_id {
}
}
impl $crate::VisitProvenance for $name {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
}
impl Idx for $name {
fn new(idx: usize) -> Self {
// We use 0 as a sentinel value (see the comment above) and,
@ -258,6 +262,25 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>:
Ok(new_index)
}
}
fn condvar_reacquire_mutex(
&mut self,
mutex: MutexId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
if this.mutex_is_locked(mutex) {
assert_ne!(this.mutex_get_owner(mutex), this.active_thread());
this.mutex_enqueue_and_block(mutex, retval, dest);
} else {
// We can have it right now!
this.mutex_lock(mutex);
// Don't forget to write the return value.
this.write_scalar(retval, &dest)?;
}
Ok(())
}
}
// Public interface to synchronization primitives. Please note that in most
@ -384,29 +407,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
assert!(this.mutex_is_locked(id), "queing on unlocked mutex");
let thread = this.active_thread();
this.machine.sync.mutexes[id].queue.push_back(thread);
this.block_thread(BlockReason::Mutex(id), None, Callback { id, retval, dest });
struct Callback<'tcx> {
id: MutexId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
impl<'tcx> VisitProvenance for Callback<'tcx> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let Callback { id: _, retval, dest } = self;
retval.visit_provenance(visit);
dest.visit_provenance(visit);
}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
assert!(!this.mutex_is_locked(self.id));
this.mutex_lock(self.id);
this.write_scalar(self.retval, &self.dest)?;
Ok(())
}
}
this.block_thread(
BlockReason::Mutex(id),
None,
callback!(
@capture<'tcx> {
id: MutexId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
@unblock = |this| {
assert!(!this.mutex_is_locked(id));
this.mutex_lock(id);
this.write_scalar(retval, &dest)?;
Ok(())
}
),
);
}
#[inline]
@ -500,27 +517,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let thread = this.active_thread();
assert!(this.rwlock_is_write_locked(id), "read-queueing on not write locked rwlock");
this.machine.sync.rwlocks[id].reader_queue.push_back(thread);
this.block_thread(BlockReason::RwLock(id), None, Callback { id, retval, dest });
struct Callback<'tcx> {
id: RwLockId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
impl<'tcx> VisitProvenance for Callback<'tcx> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let Callback { id: _, retval, dest } = self;
retval.visit_provenance(visit);
dest.visit_provenance(visit);
}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
this.rwlock_reader_lock(self.id);
this.write_scalar(self.retval, &self.dest)?;
Ok(())
}
}
this.block_thread(
BlockReason::RwLock(id),
None,
callback!(
@capture<'tcx> {
id: RwLockId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
@unblock = |this| {
this.rwlock_reader_lock(id);
this.write_scalar(retval, &dest)?;
Ok(())
}
),
);
}
/// Lock by setting the writer that owns the lock.
@ -588,27 +600,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
assert!(this.rwlock_is_locked(id), "write-queueing on unlocked rwlock");
let thread = this.active_thread();
this.machine.sync.rwlocks[id].writer_queue.push_back(thread);
this.block_thread(BlockReason::RwLock(id), None, Callback { id, retval, dest });
struct Callback<'tcx> {
id: RwLockId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
impl<'tcx> VisitProvenance for Callback<'tcx> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let Callback { id: _, retval, dest } = self;
retval.visit_provenance(visit);
dest.visit_provenance(visit);
}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
this.rwlock_writer_lock(self.id);
this.write_scalar(self.retval, &self.dest)?;
Ok(())
}
}
this.block_thread(
BlockReason::RwLock(id),
None,
callback!(
@capture<'tcx> {
id: RwLockId,
retval: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
@unblock = |this| {
this.rwlock_writer_lock(id);
this.write_scalar(retval, &dest)?;
Ok(())
}
),
);
}
/// Is the conditional variable awaited?
@ -648,71 +655,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.block_thread(
BlockReason::Condvar(condvar),
timeout,
Callback { condvar, mutex, retval_succ, retval_timeout, dest },
callback!(
@capture<'tcx> {
condvar: CondvarId,
mutex: MutexId,
retval_succ: Scalar<Provenance>,
retval_timeout: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
@unblock = |this| {
// The condvar was signaled. Make sure we get the clock for that.
if let Some(data_race) = &this.machine.data_race {
data_race.acquire_clock(
&this.machine.sync.condvars[condvar].clock,
&this.machine.threads,
);
}
// Try to acquire the mutex.
// The timeout only applies to the first wait (until the signal), not for mutex acquisition.
this.condvar_reacquire_mutex(mutex, retval_succ, dest)
}
@timeout = |this| {
// We have to remove the waiter from the queue again.
let thread = this.active_thread();
let waiters = &mut this.machine.sync.condvars[condvar].waiters;
waiters.retain(|waiter| *waiter != thread);
// Now get back the lock.
this.condvar_reacquire_mutex(mutex, retval_timeout, dest)
}
),
);
return Ok(());
struct Callback<'tcx> {
condvar: CondvarId,
mutex: MutexId,
retval_succ: Scalar<Provenance>,
retval_timeout: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
impl<'tcx> VisitProvenance for Callback<'tcx> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let Callback { condvar: _, mutex: _, retval_succ, retval_timeout, dest } = self;
retval_succ.visit_provenance(visit);
retval_timeout.visit_provenance(visit);
dest.visit_provenance(visit);
}
}
impl<'tcx, 'mir> Callback<'tcx> {
#[allow(clippy::boxed_local)]
fn reacquire_mutex(
self: Box<Self>,
this: &mut MiriInterpCx<'mir, 'tcx>,
retval: Scalar<Provenance>,
) -> InterpResult<'tcx> {
if this.mutex_is_locked(self.mutex) {
assert_ne!(this.mutex_get_owner(self.mutex), this.active_thread());
this.mutex_enqueue_and_block(self.mutex, retval, self.dest);
} else {
// We can have it right now!
this.mutex_lock(self.mutex);
// Don't forget to write the return value.
this.write_scalar(retval, &self.dest)?;
}
Ok(())
}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
// The condvar was signaled. Make sure we get the clock for that.
if let Some(data_race) = &this.machine.data_race {
data_race.acquire_clock(
&this.machine.sync.condvars[self.condvar].clock,
&this.machine.threads,
);
}
// Try to acquire the mutex.
// The timeout only applies to the first wait (until the signal), not for mutex acquisition.
let retval = self.retval_succ;
self.reacquire_mutex(this, retval)
}
fn timeout(
self: Box<Self>,
this: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
) -> InterpResult<'tcx> {
// We have to remove the waiter from the queue again.
let thread = this.active_thread();
let waiters = &mut this.machine.sync.condvars[self.condvar].waiters;
waiters.retain(|waiter| *waiter != thread);
// Now get back the lock.
let retval = self.retval_timeout;
self.reacquire_mutex(this, retval)
}
}
}
/// Wake up some thread (if there is any) sleeping on the conditional
@ -755,50 +728,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.block_thread(
BlockReason::Futex { addr },
timeout,
Callback { addr, retval_succ, retval_timeout, dest, errno_timeout },
);
struct Callback<'tcx> {
addr: u64,
retval_succ: Scalar<Provenance>,
retval_timeout: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
errno_timeout: Scalar<Provenance>,
}
impl<'tcx> VisitProvenance for Callback<'tcx> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let Callback { addr: _, retval_succ, retval_timeout, dest, errno_timeout } = self;
retval_succ.visit_provenance(visit);
retval_timeout.visit_provenance(visit);
dest.visit_provenance(visit);
errno_timeout.visit_provenance(visit);
}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
let futex = this.machine.sync.futexes.get(&self.addr).unwrap();
// Acquire the clock of the futex.
if let Some(data_race) = &this.machine.data_race {
data_race.acquire_clock(&futex.clock, &this.machine.threads);
callback!(
@capture<'tcx> {
addr: u64,
retval_succ: Scalar<Provenance>,
retval_timeout: Scalar<Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
errno_timeout: Scalar<Provenance>,
}
// Write the return value.
this.write_scalar(self.retval_succ, &self.dest)?;
Ok(())
}
fn timeout(
self: Box<Self>,
this: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
) -> InterpResult<'tcx> {
// Remove the waiter from the futex.
let thread = this.active_thread();
let futex = this.machine.sync.futexes.get_mut(&self.addr).unwrap();
futex.waiters.retain(|waiter| waiter.thread != thread);
// Set errno and write return value.
this.set_last_error(self.errno_timeout)?;
this.write_scalar(self.retval_timeout, &self.dest)?;
Ok(())
}
}
@unblock = |this| {
let futex = this.machine.sync.futexes.get(&addr).unwrap();
// Acquire the clock of the futex.
if let Some(data_race) = &this.machine.data_race {
data_race.acquire_clock(&futex.clock, &this.machine.threads);
}
// Write the return value.
this.write_scalar(retval_succ, &dest)?;
Ok(())
}
@timeout = |this| {
// Remove the waiter from the futex.
let thread = this.active_thread();
let futex = this.machine.sync.futexes.get_mut(&addr).unwrap();
futex.waiters.retain(|waiter| waiter.thread != thread);
// Set errno and write return value.
this.set_last_error(errno_timeout)?;
this.write_scalar(retval_timeout, &dest)?;
Ok(())
}
),
);
}
/// Returns whether anything was woken.

View file

@ -41,22 +41,75 @@ pub enum TlsAllocAction {
}
/// Trait for callbacks that are executed when a thread gets unblocked.
pub trait UnblockCallback<'mir, 'tcx>: VisitProvenance {
fn unblock(
pub trait UnblockCallback<'tcx>: VisitProvenance {
/// Will be invoked when the thread was unblocked the "regular" way,
/// i.e. whatever event it was blocking on has happened.
fn unblock<'mir>(
self: Box<Self>,
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
) -> InterpResult<'tcx>;
fn timeout(
/// Will be invoked when the timeout ellapsed without the event the
/// thread was blocking on having occurred.
fn timeout<'mir>(
self: Box<Self>,
_ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
) -> InterpResult<'tcx> {
unreachable!(
"timeout on a thread that was blocked without a timeout (or someone forgot to overwrite this method)"
)
}
) -> InterpResult<'tcx>;
}
type DynUnblockCallback<'tcx> = Box<dyn UnblockCallback<'tcx> + 'tcx>;
#[macro_export]
macro_rules! callback {
(
@capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? }
@unblock = |$this:ident| $unblock:block
) => {
callback!(
@capture<$tcx, $($lft),*> { $($name: $type),+ }
@unblock = |$this| $unblock
@timeout = |_this| {
unreachable!(
"timeout on a thread that was blocked without a timeout (or someone forgot to overwrite this method)"
)
}
)
};
(
@capture<$tcx:lifetime $(,)? $($lft:lifetime),*> { $($name:ident: $type:ty),* $(,)? }
@unblock = |$this:ident| $unblock:block
@timeout = |$this_timeout:ident| $timeout:block
) => {{
struct Callback<$tcx, $($lft),*> {
$($name: $type,)*
_phantom: std::marker::PhantomData<&$tcx ()>,
}
impl<$tcx, $($lft),*> VisitProvenance for Callback<$tcx, $($lft),*> {
#[allow(unused_variables)]
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
$(
self.$name.visit_provenance(visit);
)*
}
}
impl<$tcx, $($lft),*> UnblockCallback<$tcx> for Callback<$tcx, $($lft),*> {
fn unblock<'mir>(self: Box<Self>, $this: &mut MiriInterpCx<'mir, $tcx>) -> InterpResult<$tcx> {
#[allow(unused_variables)]
let Callback { $($name,)* _phantom } = *self;
$unblock
}
fn timeout<'mir>(self: Box<Self>, $this_timeout: &mut MiriInterpCx<'mir, $tcx>) -> InterpResult<$tcx> {
#[allow(unused_variables)]
let Callback { $($name,)* _phantom } = *self;
$timeout
}
}
Callback { $($name,)* _phantom: std::marker::PhantomData }
}}
}
type DynUnblockCallback<'mir, 'tcx> = Box<dyn UnblockCallback<'mir, 'tcx> + 'tcx>;
/// A thread identifier.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
@ -127,21 +180,17 @@ pub enum BlockReason {
}
/// The state of a thread.
enum ThreadState<'mir, 'tcx> {
enum ThreadState<'tcx> {
/// The thread is enabled and can be executed.
Enabled,
/// The thread is blocked on something.
Blocked {
reason: BlockReason,
timeout: Option<Timeout>,
callback: DynUnblockCallback<'mir, 'tcx>,
},
Blocked { reason: BlockReason, timeout: Option<Timeout>, callback: DynUnblockCallback<'tcx> },
/// The thread has terminated its execution. We do not delete terminated
/// threads (FIXME: why?).
Terminated,
}
impl<'mir, 'tcx> std::fmt::Debug for ThreadState<'mir, 'tcx> {
impl<'tcx> std::fmt::Debug for ThreadState<'tcx> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Enabled => write!(f, "Enabled"),
@ -152,7 +201,7 @@ impl<'mir, 'tcx> std::fmt::Debug for ThreadState<'mir, 'tcx> {
}
}
impl<'mir, 'tcx> ThreadState<'mir, 'tcx> {
impl<'tcx> ThreadState<'tcx> {
fn is_enabled(&self) -> bool {
matches!(self, ThreadState::Enabled)
}
@ -180,7 +229,7 @@ enum ThreadJoinStatus {
/// A thread.
pub struct Thread<'mir, 'tcx> {
state: ThreadState<'mir, 'tcx>,
state: ThreadState<'tcx>,
/// Name of the thread.
thread_name: Option<Vec<u8>>,
@ -582,26 +631,18 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
self.block_thread(
BlockReason::Join(joined_thread_id),
None,
Callback { joined_thread_id },
);
struct Callback {
joined_thread_id: ThreadId,
}
impl VisitProvenance for Callback {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for Callback {
fn unblock(
self: Box<Self>,
this: &mut MiriInterpCx<'mir, 'tcx>,
) -> InterpResult<'tcx> {
if let Some(data_race) = &mut this.machine.data_race {
data_race.thread_joined(&this.machine.threads, self.joined_thread_id);
callback!(
@capture<'tcx> {
joined_thread_id: ThreadId,
}
Ok(())
}
}
@unblock = |this| {
if let Some(data_race) = &mut this.machine.data_race {
data_race.thread_joined(&this.machine.threads, joined_thread_id);
}
Ok(())
}
),
);
} else {
// The thread has already terminated - establish happens-before
if let Some(data_race) = data_race {
@ -656,7 +697,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
&mut self,
reason: BlockReason,
timeout: Option<Timeout>,
callback: impl UnblockCallback<'mir, 'tcx> + 'tcx,
callback: impl UnblockCallback<'tcx> + 'tcx,
) {
let state = &mut self.threads[self.active_thread].state;
assert!(state.is_enabled());
@ -963,7 +1004,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
&mut self,
reason: BlockReason,
timeout: Option<Timeout>,
callback: impl UnblockCallback<'mir, 'tcx> + 'tcx,
callback: impl UnblockCallback<'tcx> + 'tcx,
) {
let this = self.eval_context_mut();
if !this.machine.communicate() && matches!(timeout, Some(Timeout::RealTime(..))) {

View file

@ -10,6 +10,18 @@ pub trait VisitProvenance {
fn visit_provenance(&self, visit: &mut VisitWith<'_>);
}
// Trivial impls for types that do not contain any provenance
macro_rules! no_provenance {
($($ty:ident)+) => {
$(
impl VisitProvenance for $ty {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
}
)+
}
}
no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize ThreadId);
impl<T: VisitProvenance> VisitProvenance for Option<T> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
if let Some(x) = self {

View file

@ -337,7 +337,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
.unwrap_or_else(|| now.checked_add(Duration::from_secs(3600)).unwrap());
let timeout_time = Timeout::Monotonic(timeout_time);
this.block_thread(BlockReason::Sleep, Some(timeout_time), SleepCallback);
this.block_thread(
BlockReason::Sleep,
Some(timeout_time),
callback!(
@capture<'tcx> {}
@unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
@timeout = |_this| { Ok(()) }
),
);
Ok(0)
}
@ -353,23 +361,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let timeout_time = this.machine.clock.now().checked_add(duration).unwrap();
let timeout_time = Timeout::Monotonic(timeout_time);
this.block_thread(BlockReason::Sleep, Some(timeout_time), SleepCallback);
this.block_thread(
BlockReason::Sleep,
Some(timeout_time),
callback!(
@capture<'tcx> {}
@unblock = |_this| { panic!("sleeping thread unblocked before time is up") }
@timeout = |_this| { Ok(()) }
),
);
Ok(())
}
}
struct SleepCallback;
impl VisitProvenance for SleepCallback {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
}
impl<'mir, 'tcx: 'mir> UnblockCallback<'mir, 'tcx> for SleepCallback {
fn timeout(self: Box<Self>, _this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
Ok(())
}
fn unblock(
self: Box<Self>,
_this: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
) -> InterpResult<'tcx> {
panic!("a sleeping thread should only ever be woken up via the timeout")
}
}

View file

@ -76,28 +76,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}
// We have to block, and then try again when we are woken up.
this.init_once_enqueue_and_block(id, Callback { id, pending_place, dest: dest.clone() });
let dest = dest.clone();
this.init_once_enqueue_and_block(
id,
callback!(
@capture<'tcx> {
id: InitOnceId,
pending_place: MPlaceTy<'tcx, Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
@unblock = |this| {
let ret = this.init_once_try_begin(id, &pending_place, &dest)?;
assert!(ret, "we were woken up but init_once_try_begin still failed");
Ok(())
}
),
);
return Ok(());
struct Callback<'tcx> {
id: InitOnceId,
pending_place: MPlaceTy<'tcx, Provenance>,
dest: MPlaceTy<'tcx, Provenance>,
}
impl<'tcx> VisitProvenance for Callback<'tcx> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let Callback { id: _, dest, pending_place } = self;
pending_place.visit_provenance(visit);
dest.visit_provenance(visit);
}
}
impl<'mir, 'tcx> UnblockCallback<'mir, 'tcx> for Callback<'tcx> {
fn unblock(self: Box<Self>, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
let ret = this.init_once_try_begin(self.id, &self.pending_place, &self.dest)?;
assert!(ret, "we were woken up but init_once_try_begin still failed");
Ok(())
}
}
}
fn InitOnceComplete(