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

> Discover how LiteBox implements interruptible threads for cross-platform thread control. Learn about its WaitState state machine and ThreadHandle abstraction for seamless guest execution.

- Repository: [Microsoft/litebox](https://github.com/microsoft/litebox)
- Tags: internals
- Published: 2026-02-16

---

**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`](https://github.com/microsoft/litebox/blob/main/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:

```rust
// 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:

```rust
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`](https://github.com/microsoft/litebox/blob/main/litebox/src/event/wait.rs), it contains a clone of the waker and the platform-specific thread handle:

```rust
// 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:

```rust
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`](https://github.com/microsoft/litebox/blob/main/litebox_platform_linux_userland/src/lib.rs), the `ThreadProvider::interrupt_thread` implementation sends a dedicated signal to the target thread:

```rust
// 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`](https://github.com/microsoft/litebox/blob/main/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:

```rust
// 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:

```rust
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:

```rust
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`](https://github.com/microsoft/litebox/blob/main/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`](https://github.com/microsoft/litebox/blob/main/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`](https://github.com/microsoft/litebox/blob/main/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`](https://github.com/microsoft/litebox/blob/main/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.