How LiteBox Implements Interruptible Threads: Cross-Platform Thread Control in a Sandbox

LiteBox implements interruptible threads using a per-thread WaitState state machine and ThreadHandle abstraction that atomically transitions thread states to wake waiting threads or force guest execution interruptions via platform-specific mechanisms.

LiteBox, Microsoft's lightweight sandboxing runtime, requires robust thread control to safely pause or terminate guest execution. The implementation of LiteBox interruptible threads centers on a unified abstraction that works across Linux and Windows, allowing host code to interrupt threads regardless of whether they are waiting on synchronization primitives or actively executing guest instructions.

The Core Architecture of LiteBox Interruptible Threads

Per-Thread WaitState and State Machine

Every thread in LiteBox owns a WaitState object defined in litebox/src/event/wait.rs. This structure tracks the thread's current execution context through an atomic state machine that distinguishes between host execution, waiting states, and guest execution:

// litebox/src/event/wait.rs
pub struct WaitState<Platform: RawSyncPrimitivesProvider> {
    waker: Waker<Platform>,
    _phantom: PhantomData<core::cell::Cell<()>>,
}

The WaitState can produce a ThreadHandle via the thread_handle() method, which captures the current thread's identity and synchronization primitives:

pub fn thread_handle(&self) -> ThreadHandle<Platform>
where
    Platform: ThreadProvider,
{
    let current_thread = self.waker.0.platform.current_thread();
    ThreadHandle {
        waker: self.waker.clone(),
        thread: current_thread,
    }
}

ThreadHandle for External Control

The ThreadHandle struct serves as the external interface for interrupting threads. Stored in litebox/src/event/wait.rs, it contains a clone of the waker and the platform-specific thread handle:

// litebox/src/event/wait.rs
pub struct ThreadHandle<Platform: RawSyncPrimitivesProvider + ThreadProvider> {
    waker: Waker<Platform>,
    thread: Platform::ThreadHandle,
}

How Thread Interruption Works in Practice

Atomic State Transitions in ThreadHandle::interrupt()

The ThreadHandle::interrupt() method implements the core logic for LiteBox interruptible threads. It performs an atomic compare-and-swap on the thread's state to determine whether to wake a waiting thread or request a platform-specific guest interruption:

impl<Platform: RawSyncPrimitivesProvider + ThreadProvider> ThreadHandle<Platform> {
    /// Interrupts the thread, whether it is waiting or running guest code.
    pub fn interrupt(&self) {
        let condvar = &self.waker.0.condvar;
        let v = condvar.underlying_atomic().fetch_update(
            Ordering::Release,
            Ordering::Relaxed,
            |state| match ThreadState(state) {
                ThreadState::RUNNING_IN_HOST
                | ThreadState::WOKEN
                | ThreadState::INTERRUPTED_GUEST => None,
                ThreadState::WAITING => Some(ThreadState::WOKEN.0),
                ThreadState::RUNNING_IN_GUEST => Some(ThreadState::INTERRUPTED_GUEST.0),
                state => unreachable!("{state:?}"),
            },
        );
        match v.map(ThreadState) {
            Ok(ThreadState::WAITING) => {
                condvar.wake_one();                     // wake a waiting thread
            }
            Ok(ThreadState::RUNNING_IN_GUEST) => {
                self.waker.0.platform.interrupt_thread(&self.thread); // platform‑specific interrupt
            }
            Ok(state) => unreachable!("{state:?}"),
            Err(_) => core::sync::atomic::fence(Ordering::Release), // no state change – still a safe fence
        }
    }
}

This implementation ensures that LiteBox interruptible threads are never left in inconsistent states, and the interrupt call is safe to invoke from any other thread.

Platform-Specific Interrupt Mechanisms

While the ThreadHandle provides the abstract interface, the actual mechanism for forcing a thread to stop executing guest code depends on the host operating system.

Linux Implementation via Signals

In litebox_platform_linux_userland/src/lib.rs, the ThreadProvider::interrupt_thread implementation sends a dedicated signal to the target thread:

// litebox_platform_linux_userland/src/lib.rs
fn interrupt(&self) {
    let thread = self.0.lock().unwrap();
    if let Some(&thread) = thread.as_ref() {
        unsafe {
            // Send a dedicated signal that LiteBox registers as "interrupt".
            libc::pthread_kill(thread, INTERRUPT_SIGNAL_NUMBER.load(Ordering::Relaxed));
        }
    }
}

The signal is caught by LiteBox's signal handler, which sets the per-thread interrupt flag; the next shim entry point observes the flag and returns control to the host.

Windows Implementation via Context Rewriting

Unlike Linux, which uses pthread_kill to send an interrupt signal, the Windows implementation in litebox_platform_windows_userland/src/lib.rs uses SuspendThread to pause the target, sets an interrupt flag in thread-local storage (TLS), and checks if the thread is currently executing guest code. If it is, the implementation rewrites the thread's CPU context to redirect execution to a dedicated interrupt callback before resuming:

// litebox_platform_windows_userland/src/lib.rs
fn interrupt(&self, current: Option<&ThreadHandle>) {
    // 1️⃣ Suspend the target thread
    unsafe {
        windows_sys::Win32::System::Threading::SuspendThread(inner.handle.as_raw_handle());
    }
    // 2️⃣ Set its interrupt flag in TLS
    target_tls.interrupt.set(true);

    // 3️⃣ If the thread isn't in the guest, just resume.
    if !target_tls.is_in_guest.get() {
        return;
    }

    // 4️⃣ If it is in the guest, rewrite the return-to-guest context so that
    //    execution continues at the interrupt callback (see `set_context_to_interrupt_callback`).
    // 5️⃣ Resume the thread.
}

This method effectively hijacks the guest's execution flow without using signals, ensuring cross-platform consistency in the LiteBox interruptible threads architecture.

Using the Interruptible API in LiteBox Applications

Developers working with LiteBox interruptible threads typically follow a pattern: create a WaitState, obtain a ThreadHandle, and use the handle to interrupt the thread from external contexts such as watchdog timers or signal handlers.

Watchdog Pattern for Guest Thread Cancellation

The following example demonstrates a watchdog thread that cancels a guest thread after a timeout:

use litebox::event::wait::{WaitState, WaitError};
use std::time::Duration;

// Thread that runs guest code
let wait_state = WaitState::new(platform);
let handle = wait_state.thread_handle();   // keep a copy for the watchdog

std::thread::spawn(move || {
    // Simulate long-running guest work
    while !guest_finished() {
        // Periodically check for interrupt
        if wait_state.context()
            .with_check_for_interrupt(&NeverInterrupt)   // we rely on the handle
            .wait_until(|| false)                       // dummy wait
            .is_err()
        {
            // Interrupted – clean up and exit
            break;
        }
    }
});

// Watchdog thread – abort after 2 s
std::thread::sleep(Duration::from_secs(2));
handle.interrupt();   // forces the guest thread to return to the shim

Custom Interrupt Checkers

For scenarios requiring custom interruption logic, such as responding to external signals, implement the CheckForInterrupt trait:

struct SignalChecker;
impl litebox::event::wait::CheckForInterrupt for SignalChecker {
    fn check_for_interrupt(&self) -> bool {
        // read a global atomic set by a signal handler
        SIGNAL_RECEIVED.load(Ordering::Relaxed)
    }
}

let ctx = wait_state.context()
    .with_check_for_interrupt(&SignalChecker)
    .with_timeout(Duration::from_secs(10));

match ctx.wait_until(|| shared_queue.not_empty()) {
    Ok(()) => println!("got work"),
    Err(WaitError::Interrupted) => println!("signal arrived, aborting"),
    Err(WaitError::TimedOut) => println!("no work within deadline"),
}

Summary

  • LiteBox interruptible threads rely on a per-thread WaitState that tracks execution context through an atomic state machine (RUNNING_IN_HOST, WAITING, RUNNING_IN_GUEST, etc.).
  • The ThreadHandle abstraction, obtained via WaitState::thread_handle(), provides a thread-safe mechanism to request interruptions from any external thread.
  • ThreadHandle::interrupt() in litebox/src/event/wait.rs atomically transitions thread states to either wake waiting threads or trigger platform-specific guest interruption mechanisms.
  • Linux implementations use pthread_kill with a dedicated signal (litebox_platform_linux_userland/src/lib.rs), while Windows suspends threads and rewrites CPU context to redirect execution (litebox_platform_windows_userland/src/lib.rs).
  • The API supports custom interruption logic through the CheckForInterrupt trait and integrates with timeout mechanisms via WaitContext.

Frequently Asked Questions

What is the difference between interrupting a waiting thread versus a thread running guest code?

When a thread is in the WAITING state, ThreadHandle::interrupt() atomically transitions the state to WOKEN and calls condvar.wake_one() to unblock the thread from its condition variable. For threads in RUNNING_IN_GUEST, the method transitions the state to INTERRUPTED_GUEST and delegates to Platform::interrupt_thread, which uses OS-specific mechanisms—signals on Linux or context rewriting on Windows—to force the guest to return to host control.

Is it safe to call ThreadHandle::interrupt() from any thread?

Yes. The interrupt() method is designed to be thread-safe and can be invoked from any thread that holds a ThreadHandle. It uses atomic operations (fetch_update with Ordering::Release) to ensure that state transitions are race-free, and it safely handles cases where the target thread is already interrupted, running in host code, or in the process of shutting down.

How does LiteBox handle interrupting threads on Windows without using signals?

Unlike Linux, which uses pthread_kill to send an interrupt signal, the Windows implementation in litebox_platform_windows_userland/src/lib.rs uses SuspendThread to pause the target, sets an interrupt flag in thread-local storage (TLS), and checks if the thread is currently executing guest code. If it is, the implementation rewrites the thread's CPU context to redirect execution to a dedicated interrupt callback before resuming, effectively hijacking the guest's execution flow without signals.

Can I implement custom interruption logic for specific application needs?

Yes. LiteBox provides the CheckForInterrupt trait that you can implement to define custom interruption conditions. By creating a type that implements check_for_interrupt() and passing it to WaitContext::with_check_for_interrupt(), you can integrate external signals, atomic flags, or complex business logic into the interruption mechanism while still leveraging the standard WaitState and timeout handling provided by the framework.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →