nemo_flow_ffi/api/scope_stack.rs
1// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::{
5 FfiScopeStack, FfiThreadScopeStackBinding, NemoFlowStatus, capture_thread_scope_stack,
6 clear_last_error, create_scope_stack, restore_thread_scope_stack, scope_stack_active,
7 set_last_error, set_thread_scope_stack,
8};
9
10// ---------------------------------------------------------------------------
11// Scope stack isolation
12// ---------------------------------------------------------------------------
13
14/// Create a new isolated scope stack with its own root scope.
15///
16/// Each scope stack is independent: scopes pushed on one do not appear on another.
17/// Use `nemo_flow_scope_stack_set_thread` to bind a stack to the current thread
18/// before making other NeMo Flow API calls.
19///
20/// # Parameters
21/// - `out`: On success, receives a heap-allocated `FfiScopeStack` that must be
22/// freed with `nemo_flow_scope_stack_free`.
23///
24/// # Returns
25/// - Returns [`NemoFlowStatus::Ok`] on success and writes the new scope stack
26/// to `out`.
27/// - Returns [`NemoFlowStatus::NullPointer`] when `out` is null.
28///
29/// # Safety
30/// `out` must be a valid, non-null pointer.
31#[unsafe(no_mangle)]
32pub unsafe extern "C" fn nemo_flow_scope_stack_create(
33 out: *mut *mut FfiScopeStack,
34) -> NemoFlowStatus {
35 clear_last_error();
36 if out.is_null() {
37 set_last_error("out pointer is null");
38 return NemoFlowStatus::NullPointer;
39 }
40 let handle = create_scope_stack();
41 unsafe { *out = Box::into_raw(Box::new(FfiScopeStack(handle))) };
42 NemoFlowStatus::Ok
43}
44
45/// Bind an isolated scope stack to the current OS thread.
46///
47/// After this call, all NeMo Flow scope operations on the current thread
48/// (e.g. `nemo_flow_push_scope`, `nemo_flow_get_handle`) will use the
49/// given scope stack. This is typically used from Go goroutines that have
50/// called `runtime.LockOSThread()`.
51///
52/// The `FfiScopeStack` is **not** consumed — the caller retains ownership
53/// and must still free it when done.
54///
55/// # Parameters
56/// - `stack`: Scope stack to bind to the current OS thread.
57///
58/// # Returns
59/// - Returns [`NemoFlowStatus::Ok`] when the thread-local scope stack was
60/// updated successfully.
61/// - Returns [`NemoFlowStatus::NullPointer`] when `stack` is null.
62///
63/// # Safety
64/// `stack` must be a valid, non-null `FfiScopeStack` pointer.
65#[unsafe(no_mangle)]
66pub unsafe extern "C" fn nemo_flow_scope_stack_set_thread(
67 stack: *const FfiScopeStack,
68) -> NemoFlowStatus {
69 clear_last_error();
70 if stack.is_null() {
71 set_last_error("stack pointer is null");
72 return NemoFlowStatus::NullPointer;
73 }
74 let handle = unsafe { &*stack }.0.clone();
75 set_thread_scope_stack(handle);
76 NemoFlowStatus::Ok
77}
78
79/// Capture the current thread-local scope stack binding.
80///
81/// The returned binding must be restored with
82/// `nemo_flow_scope_stack_restore_thread`.
83///
84/// # Parameters
85/// - `out`: On success, receives a heap-allocated binding handle.
86///
87/// # Safety
88/// `out` must be a valid, non-null pointer.
89#[unsafe(no_mangle)]
90pub unsafe extern "C" fn nemo_flow_scope_stack_capture_thread(
91 out: *mut *mut FfiThreadScopeStackBinding,
92) -> NemoFlowStatus {
93 clear_last_error();
94 if out.is_null() {
95 set_last_error("out pointer is null");
96 return NemoFlowStatus::NullPointer;
97 }
98 let binding = capture_thread_scope_stack();
99 unsafe { *out = Box::into_raw(Box::new(FfiThreadScopeStackBinding(binding))) };
100 NemoFlowStatus::Ok
101}
102
103/// Restore and free a captured thread-local scope stack binding.
104///
105/// # Safety
106/// `binding` must be a valid pointer returned by
107/// `nemo_flow_scope_stack_capture_thread`.
108#[unsafe(no_mangle)]
109pub unsafe extern "C" fn nemo_flow_scope_stack_restore_thread(
110 binding: *mut FfiThreadScopeStackBinding,
111) -> NemoFlowStatus {
112 clear_last_error();
113 if binding.is_null() {
114 set_last_error("binding pointer is null");
115 return NemoFlowStatus::NullPointer;
116 }
117 let binding = unsafe { Box::from_raw(binding) };
118 restore_thread_scope_stack(binding.0);
119 NemoFlowStatus::Ok
120}
121
122/// Returns whether the current execution context has an explicitly-initialized
123/// scope stack.
124///
125/// Returns `true` if `nemo_flow_scope_stack_set_thread` has been called on the
126/// current OS thread (or the caller is inside a tokio task-local scope).
127/// Returns `false` when only the auto-created default is present.
128///
129/// # Notes
130/// This helper does not allocate or install a scope stack. It only reports
131/// whether one is already explicit in the current execution context.
132#[unsafe(no_mangle)]
133pub extern "C" fn nemo_flow_scope_stack_active() -> bool {
134 scope_stack_active()
135}