Template Worker Internals
Deep dive into the template-worker implementation details, code patterns, and internal architecture.
Template Worker Internals
This document provides an in-depth look at the template-worker's internal implementation, code patterns, and architectural decisions.
Core Data Structures
LuaVmAction
The LuaVmAction enum represents all possible actions that can be performed on a Lua VM:
pub enum LuaVmAction {
/// Execute a template with given event and context
Execute {
event: Event,
context: TemplateContext,
callback: Option<oneshot::Sender<Result<Value>>>,
},
/// Destroy the VM
Destroy,
/// Get VM state/info
GetState {
callback: oneshot::Sender<VMState>,
},
// ... other actions
}Each action can optionally include a callback channel for receiving results asynchronously.
ThreadEntry
The ThreadEntry structure provides a generic abstraction for VM distribution strategies:
pub struct ThreadEntry {
pub guild_id: GuildId,
pub vm_handle: Arc<Mutex<Option<KhronosRuntime>>>,
pub action_rx: mpsc::Receiver<LuaVmAction>,
pub shutdown: Arc<AtomicBool>,
}| Prop | Type | Description |
|---|---|---|
guild_id | GuildId | The Discord guild this thread handles |
vm_handle | Arc<Mutex<Option<KhronosRuntime>>> | Shared reference to the Lua VM runtime (wrapped in Mutex for thread safety) |
action_rx | mpsc::Receiver<LuaVmAction> | Channel receiver for incoming VM actions |
shutdown | Arc<AtomicBool> | Atomic flag for graceful shutdown |
KhronosRuntime
The KhronosRuntime is the core Lua VM manager:
pub struct KhronosRuntime {
vm: Lua,
// Internal state
memory_usage: AtomicUsize,
execution_count: AtomicU64,
// ...
}Important Rules
- Each Lua VM is owned by a single
KhronosRuntime - Never clone the Lua VM handle directly (logic bug)
- Use
WeakLua(weak reference) for temporary access - Hold Lua VM handles for as short a time as possible
VM Distribution Implementation
Thread Pool Strategy
The thread pool strategy distributes multiple guilds across a pool of worker threads:
pub struct ThreadPoolStrategy {
pool: ThreadPool,
vm_registry: Arc<Mutex<HashMap<GuildId, Weak<ThreadEntry>>>>,
action_tx: HashMap<GuildId, mpsc::Sender<LuaVmAction>>,
}
impl ThreadPoolStrategy {
pub fn new(pool_size: usize) -> Self {
let pool = ThreadPool::new(pool_size);
// Initialize registry and channels
}
pub async fn dispatch(&self, guild_id: GuildId, action: LuaVmAction) {
// Get or create thread entry for guild
let entry = self.get_or_create_entry(guild_id).await;
// Send action to thread's channel
entry.action_tx.send(action).await?;
}
}Flow:
- Request arrives →
dispatch()is called - Get or create thread entry for the guild
- Send action to the thread's action channel
- Thread processes action in its main loop
- Result sent via callback channel (if provided)
Thread Entry Main Loop
Each thread runs a main loop that processes actions:
async fn thread_main(entry: ThreadEntry) {
// Setup phase
let mut vm = initialize_vm(entry.guild_id).await?;
// Main loop
while !entry.shutdown.load(Ordering::Relaxed) {
tokio::select! {
// Receive action from channel
Some(action) = entry.action_rx.recv() => {
match action {
LuaVmAction::Execute { event, context, callback } => {
let result = vm.execute_template(event, context).await;
if let Some(cb) = callback {
let _ = cb.send(result);
}
}
LuaVmAction::Destroy => {
vm.cleanup().await;
break;
}
// ... other actions
}
}
// Handle shutdown
_ = shutdown_signal => {
break;
}
}
}
// Cleanup
vm.cleanup().await;
}Template Execution Flow
Execution Pipeline
When a template execution request arrives:
pub async fn execute_template(
&mut self,
event: Event,
context: TemplateContext,
) -> Result<Value> {
// 1. Validate event and context
self.validate_execution(&event, &context)?;
// 2. Get or compile template
let template = self.get_template(&context.template_name).await?;
// 3. Check resource limits
self.check_limits()?;
// 4. Create Luau thread for async execution
let thread = self.vm.create_thread()?;
// 5. Set up sandbox environment
let env = self.create_sandbox_env(&context)?;
// 6. Execute template
let result = thread.execute_async(template, (event, context), env).await?;
// 7. Monitor resource usage
self.update_resource_usage()?;
Ok(result)
}Resource Management
Memory Tracking
Memory usage is tracked per-VM:
impl KhronosRuntime {
fn track_memory(&self, delta: isize) -> Result<()> {
let current = self.memory_usage.fetch_add(delta as usize, Ordering::Relaxed);
let new_total = current + delta as usize;
if new_total > MAX_TEMPLATE_MEMORY_USAGE {
// Kill VM and mark as broken
self.mark_broken(MemoryLimitExceeded)?;
return Err(Error::MemoryLimitExceeded);
}
Ok(())
}
}Execution Time Limits
Execution time is tracked with a timeout:
async fn execute_with_timeout(
&self,
template: &Template,
args: (Event, TemplateContext),
) -> Result<Value> {
tokio::time::timeout(
MAX_TEMPLATES_EXECUTION_TIME,
self.vm.execute(template, args)
)
.await
.map_err(|_| Error::ExecutionTimeout)?
}Event Dispatching
Event Queue
Events are queued and dispatched asynchronously:
pub struct EventDispatcher {
queues: HashMap<GuildId, mpsc::UnboundedSender<Event>>,
workers: Vec<JoinHandle<()>>,
}
impl EventDispatcher {
pub fn dispatch(&self, guild_id: GuildId, event: Event) {
if let Some(queue) = self.queues.get(&guild_id) {
let _ = queue.send(event);
}
}
async fn worker_loop(&self, guild_id: GuildId, mut rx: mpsc::UnboundedReceiver<Event>) {
while let Some(event) = rx.recv().await {
// Get VM for guild
let vm = self.get_vm(guild_id).await?;
// Create Luau thread for async execution
let thread = vm.create_thread()?;
// Dispatch event to template
thread.spawn(async move {
// Execute template with event
template.execute((event, context)).await
});
}
}
}Async Event Handling
Events are handled asynchronously using Luau threads. This means that the task library works as expected in event handlers, allowing for non-blocking operations.
Error Handling & Recovery
Panic Recovery
Per-thread panic hooks clean up on panic:
std::panic::set_hook(Box::new(move |panic_info| {
// Clean up all guild VMs on this thread
let mut registry = VM_REGISTRY.lock().unwrap();
for (guild_id, entry) in registry.iter() {
if entry.thread_id == current_thread_id() {
// Mark VM as broken
entry.mark_broken(PanicRecovery);
// Clean up resources
entry.cleanup();
}
}
// Log panic
error!("VM thread panicked: {:?}", panic_info);
}));VM State Management
VMs can be in various states:
| Prop | Type | Description |
|---|---|---|
Healthy? | VMState | VM is healthy and ready |
Broken? | VMState | VM is broken and needs recreation |
Initializing? | VMState | VM is being initialized |
Destroying? | VMState | VM is being destroyed |
When a VM is marked as broken, future dispatches will create a new VM.
Plugin System
Plugin Loading
Plugins are loaded via the require system:
fn load_plugin(&self, plugin_name: &str) -> Result<Value> {
// Parse plugin name (e.g., "@antiraid/discord")
let (namespace, module) = parse_plugin_name(plugin_name)?;
match namespace {
"antiraid" => {
// Load from builtins
self.builtins.get_module(module)?
}
_ => {
// Unknown plugin
Err(Error::UnknownPlugin)
}
}
}Plugin Sandboxing
Plugins are read-only and cannot be modified:
fn create_plugin_table(&self, plugin: &Plugin) -> Table {
let table = self.vm.create_table()?;
// Set read-only metatable
let metatable = self.vm.create_table()?;
metatable.set("__newindex", |_, _, _| {
Err("Cannot modify plugin")
})?;
metatable.set("__metatable", None)?; // Hide metatable
table.set_metatable(Some(metatable));
table
}Performance Optimizations
VM Reuse
VMs are reused across multiple template executions:
// Get or create VM
let vm = match self.vm_registry.get(&guild_id) {
Some(weak) => {
// Try to upgrade weak reference
weak.upgrade().ok_or(Error::VMBroken)?
}
None => {
// Create new VM
let vm = self.create_vm(guild_id).await?;
let weak = Arc::downgrade(&vm);
self.vm_registry.insert(guild_id, weak);
vm
}
};Lazy Template Compilation
Templates are compiled on first use and cached:
struct TemplateCache {
compiled: HashMap<String, CompiledTemplate>,
}
impl TemplateCache {
fn get_or_compile(&mut self, name: &str, source: &str) -> Result<&CompiledTemplate> {
if let Some(compiled) = self.compiled.get(name) {
return Ok(compiled);
}
// Compile template
let compiled = self.compile(source)?;
self.compiled.insert(name.to_string(), compiled);
Ok(self.compiled.get(name).unwrap())
}
}Related Documentation
- Template Worker Overview- Service overview
Template Worker Service
Developer documentation for the template-worker service - the template execution engine that runs user-defined Lua/Luau scripts in a sandboxed environment.
- Architecture- High-level architecture
Template Worker Architecture
Architecture details for the template-worker service including VM distribution, lifecycle, and runtime management.
- HTTP API- API documentation
Template Worker HTTP API
HTTP API documentation for the template-worker service including endpoints, types, and OpenAPI specification.
- Development- Development guide
Template Worker Development
Development guide for the template-worker service including building, testing, and code structure.
Last updated on