How Subsecond Hot-Patches Rust Code: Jump Table Indirection Explained
Subsecond hot-patches Rust code by routing every function call through a lock-free global jump table that atomically swaps pointers to freshly compiled functions at runtime, avoiding direct process memory modification.
The Subsecond package in the DioxusLabs/dioxus repository enables true hot-reloading for Rust applications without restarting the process. Unlike traditional dynamic linking or runtime code injection, Subsecond implements hot-patching through an indirection layer that safely detours execution to updated code versions. This mechanism relies on a global jump table updated atomically whenever the Dioxus CLI detects source changes.
The Global Jump Table Architecture
At the core of Subsecond’s hot-patching mechanism lies a lock-free AtomicPtr<JumpTable> called APP_JUMP_TABLE. This global pointer holds the current mapping from original function IDs to their latest memory addresses.
The jump table is lazily allocated during the first hot-reload operation and swapped atomically whenever a patch arrives. This design ensures that no active function calls encounter a partially updated table, as the atomic pointer swap guarantees thread-safe transitions between code versions. According to the source code in packages/subsecond/subsecond/src/lib.rs (lines 73-80), the table declaration uses lock-free primitives to minimize runtime overhead during the detour checks.
Detouring Calls with subsecond::call
User code enters the hot-patching system through the subsecond::call wrapper or the HotFn type. When a closure is executed via subsecond::call, the runtime builds a HotFn that performs a lookup in the global jump table before invoking the actual implementation.
As implemented in packages/subsecond/subsecond/src/lib.rs (lines 41-71), the subsecond::call function checks whether a newer version of the function exists in the jump table. If a patched address is present, the call detours to that location; otherwise, the original implementation runs. This indirection happens transparently on every function call, ensuring that code updates take effect immediately after the jump table swap completes.
The HotFn Wrapper and Retry Logic
HotFn serves as a thin wrapper around any FnMut closure, recording the original function in its inner field. On each invocation, HotFn queries the jump table for the current address via ptr_address and transmutes the resulting pointer into a callable function.
If a call would be stale—for instance, when a change occurs above the current call site in the stack—HotFn throws a HotFnPanic. This panic bubbles up to the next outer subsecond::call, which catches the error and retries the invocation against the updated jump table. The implementation in packages/subsecond/subsecond/src/lib.rs (lines 50-88) demonstrates this retry mechanism, ensuring that in-flight calls gracefully transition to new code versions without corrupting the program state.
Runtime Patch Application and ASLR Handling
When the Dioxus CLI detects a code change, it builds a patch library containing only the modified object files. The apply_patch function loads this library using platform-specific mechanisms—dlopen on native platforms or WebAssembly fetch on WASM—and extracts the exported symbols.
Because modern operating systems randomize memory layouts via Address Space Layout Randomization (ASLR), Subsecond first computes the offset between the original executable’s main symbol and the newly loaded library. This aslr_reference calculation allows the runtime to re-base all jump table entries correctly before installation. As shown in packages/subsecond/subsecond/src/lib.rs (lines 107-149), the rebased pointers are written to a new jump table structure, and the global APP_JUMP_TABLE is atomically replaced. After the swap, any pending HotFn calls automatically resolve to the new code addresses.
Build Pipeline: ThinLink and CLI Integration
The Dioxus CLI (dx serve --hotpatch) orchestrates the patching process through ThinLink, a lightweight linker that produces the patch library and generates jump-table metadata. The CLI streams this metadata to the running process via the Dioxus Devtools WebSocket protocol, triggering the runtime to call apply_patch and install the updates. This architecture separates the compilation and linking concerns from the runtime indirection mechanism, allowing Subsecond to maintain minimal overhead in the hot path while supporting complex rebuild scenarios.
Practical Implementation Examples
The following examples demonstrate Subsecond’s API for hot-reloading Rust applications:
// Simple hot-reloaded loop
fn main() {
// Every iteration goes through the jump table
subsecond::call(|| {
for i in 0..5 {
// The inner call can be patched independently
subsecond::call(|| {
println!("iteration {i}");
});
}
});
}
Each subsecond::call creates a HotFn that checks the jump table before invoking the closure. When the source file is edited and the CLI rebuilds the patch, the next iteration automatically runs the updated code.
// Manual use of HotFn and apply_patch
use subsecond::{HotFn, apply_patch, JumpTable};
fn add(a: i32, b: i32) -> i32 { a + b }
fn main() {
let mut hot_add = HotFn::current(add);
println!("2 + 3 = {}", hot_add.call((2, 3)));
// Imagine we received a JumpTable from an external tool:
let patch: JumpTable = /* ...deserialize... */;
unsafe { apply_patch(patch).unwrap() };
// After the patch, the same HotFn now calls the new implementation
println!("2 + 3 = {}", hot_add.call((2, 3)));
}
Key implementation details:
HotFn::currentcaptures the original function and its identity in the jump table.apply_patchperforms an atomic swap of the global jump table (markedunsafebecause malformed tables can crash the process).- Subsequent calls to
hot_addresolve to the patched address without modifying thehot_addvariable itself.
Summary
- Jump table indirection: Subsecond routes all calls through
APP_JUMP_TABLE, a lock-free atomic pointer to a function address mapping. - Transparent detours: The
subsecond::callwrapper andHotFntype check for updated implementations on every invocation with minimal overhead. - Safe atomic updates: Patches are applied by computing ASLR offsets, rebasing pointers, and atomically swapping the global table, ensuring no thread observes a partial update.
- Stale-call recovery: The
HotFnPanicmechanism allows outer wrappers to retry calls that would otherwise execute stale code. - Tooling integration: ThinLink and the Dioxus CLI handle patch generation and delivery, while the runtime manages the address-space adjustments.
Frequently Asked Questions
How does Subsecond handle Address Space Layout Randomization (ASLR)?
Subsecond calculates an aslr_reference offset by comparing the address of the main symbol in the original executable against its location in the freshly loaded patch library. This offset is applied to all function pointers in the patch before they enter the jump table, ensuring that rebased addresses correctly point to the new code regardless of where the operating system loaded the library in memory.
Is Subsecond’s hot-patching mechanism thread-safe?
Yes. The global jump table is protected by an AtomicPtr, allowing lock-free reads during the common case of function calls. When a patch arrives, Subsecond constructs the new table off-thread and performs a single atomic pointer swap. The HotFn retry logic handles the rare race condition where a call begins with the old table but completes after a swap, ensuring that no execution occurs at invalid addresses.
What platforms does Subsecond support?
Subsecond supports native platforms using dlopen for dynamic library loading and WebAssembly targets using the fetch API. The ASLR rebasing logic is primarily relevant for native platforms where address space randomization is active; WASM environments use a different addressing model that does not require offset calculations.
How does Subsecond differ from traditional DLL injection or dynamic linking?
Traditional dynamic linking resolves symbols at load time, while DLL injection typically overwrites process memory directly. Subsecond instead maintains a persistent indirection layer (the jump table) that can be updated atomically without modifying executable code segments. This approach avoids the safety hazards of self-modifying code and allows granular, function-level hot-patching rather than requiring entire library replacements.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →