Skip to main content

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}