mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 00:59:33 +00:00
perf: speed up startup from ~15s to ~2s (#280)
Three high-impact changes eliminate most startup latency: 1. Enable wasmtime persistent compilation cache — call cache_config_load_default() so compiled native code is serialized to disk (~/.cache/wasmtime). Subsequent startups deserialize instead of recompiling, dropping the WASM phase from ~13s to <1s. 2. Cache compiled Component in PreparedModule — store the compiled wasmtime::component::Component directly instead of raw bytes. Eliminates ~2.6s recompilation on every first tool/channel execution. 3. Move blocking housekeeping to background tasks — embedding backfill (~1.3s of failing HTTP calls) and stale job cleanup are fire-and-forget work that no longer blocks the critical startup path. Also: deduplicate Workspace creation in main.rs (two identical instances reduced to one), and replace leftover println! in session validation with tracing calls. Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
2cdd1acb1e
commit
98ee648fcb
@@ -488,7 +488,7 @@ mod tests {
|
||||
let prepared = Arc::new(PreparedChannelModule {
|
||||
name: name.to_string(),
|
||||
description: format!("Test channel: {}", name),
|
||||
component_bytes: Vec::new(),
|
||||
component: None,
|
||||
limits: ResourceLimits::default(),
|
||||
});
|
||||
|
||||
|
||||
@@ -68,38 +68,51 @@ impl WasmChannelRuntimeConfig {
|
||||
}
|
||||
|
||||
/// A compiled WASM channel component ready for instantiation.
|
||||
#[derive(Debug)]
|
||||
///
|
||||
/// Stores the pre-compiled `Component` directly so instantiation
|
||||
/// doesn't require recompilation.
|
||||
pub struct PreparedChannelModule {
|
||||
/// Channel name.
|
||||
pub name: String,
|
||||
/// Channel description.
|
||||
pub description: String,
|
||||
/// Compiled component bytes (public for testing, otherwise use component_bytes()).
|
||||
pub(crate) component_bytes: Vec<u8>,
|
||||
/// Pre-compiled component (cheaply cloneable via internal Arc).
|
||||
pub(crate) component: Option<wasmtime::component::Component>,
|
||||
/// Resource limits for this channel.
|
||||
pub limits: ResourceLimits,
|
||||
}
|
||||
|
||||
impl PreparedChannelModule {
|
||||
/// Get the compiled component bytes.
|
||||
pub fn component_bytes(&self) -> &[u8] {
|
||||
&self.component_bytes
|
||||
/// Get the pre-compiled component for instantiation.
|
||||
pub fn component(&self) -> Option<&wasmtime::component::Component> {
|
||||
self.component.as_ref()
|
||||
}
|
||||
|
||||
/// Create a PreparedChannelModule for testing purposes.
|
||||
///
|
||||
/// Creates a module with no actual WASM bytes, suitable for testing
|
||||
/// Creates a module with no actual WASM component, suitable for testing
|
||||
/// channel infrastructure without requiring a real WASM component.
|
||||
pub fn for_testing(name: impl Into<String>, description: impl Into<String>) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
description: description.into(),
|
||||
component_bytes: Vec::new(),
|
||||
component: None,
|
||||
limits: ResourceLimits::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PreparedChannelModule {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PreparedChannelModule")
|
||||
.field("name", &self.name)
|
||||
.field("description", &self.description)
|
||||
.field("has_component", &self.component.is_some())
|
||||
.field("limits", &self.limits)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM channel runtime.
|
||||
///
|
||||
/// Manages the Wasmtime engine and a cache of prepared channel modules.
|
||||
@@ -137,6 +150,13 @@ impl WasmChannelRuntime {
|
||||
// Disable debug info in production
|
||||
wasmtime_config.debug_info(false);
|
||||
|
||||
// Enable persistent compilation cache. Wasmtime serializes compiled native
|
||||
// code to disk (~/.cache/wasmtime by default), so subsequent startups
|
||||
// deserialize instead of recompiling — typically 10-50x faster.
|
||||
if let Err(e) = wasmtime_config.cache_config_load_default() {
|
||||
tracing::warn!("Failed to enable wasmtime compilation cache: {}", e);
|
||||
}
|
||||
|
||||
let engine = Engine::new(&wasmtime_config).map_err(|e| {
|
||||
WasmChannelError::Config(format!("Failed to create Wasmtime engine: {}", e))
|
||||
})?;
|
||||
@@ -183,13 +203,13 @@ impl WasmChannelRuntime {
|
||||
// Compile in blocking task (Wasmtime compilation is synchronous)
|
||||
let prepared = tokio::task::spawn_blocking(move || {
|
||||
// Validate and compile the component
|
||||
let _component = wasmtime::component::Component::new(&engine, &wasm_bytes)
|
||||
let component = wasmtime::component::Component::new(&engine, &wasm_bytes)
|
||||
.map_err(|e| WasmChannelError::Compilation(e.to_string()))?;
|
||||
|
||||
Ok::<_, WasmChannelError>(PreparedChannelModule {
|
||||
name: name.clone(),
|
||||
description: desc,
|
||||
component_bytes: wasm_bytes,
|
||||
component: Some(component),
|
||||
limits: limits.unwrap_or(default_limits),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ use tokio::sync::{RwLock, mpsc, oneshot};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use uuid::Uuid;
|
||||
use wasmtime::Store;
|
||||
use wasmtime::component::{Component, Linker};
|
||||
use wasmtime::component::Linker;
|
||||
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
|
||||
|
||||
use crate::channels::wasm::capabilities::ChannelCapabilities;
|
||||
@@ -725,9 +725,13 @@ impl WasmChannel {
|
||||
) -> Result<SandboxedChannel, WasmChannelError> {
|
||||
let engine = runtime.engine();
|
||||
|
||||
// Compile the component (uses cached bytes)
|
||||
let component = Component::new(engine, prepared.component_bytes())
|
||||
.map_err(|e| WasmChannelError::Compilation(e.to_string()))?;
|
||||
// Use the pre-compiled component (no recompilation needed)
|
||||
let component = prepared
|
||||
.component()
|
||||
.ok_or_else(|| {
|
||||
WasmChannelError::Compilation("No compiled component available".to_string())
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// Create linker and add host functions
|
||||
let mut linker = Linker::new(engine);
|
||||
@@ -778,7 +782,7 @@ impl WasmChannel {
|
||||
/// Returns the channel configuration for HTTP endpoint registration.
|
||||
async fn call_on_start(&self) -> Result<ChannelConfig, WasmChannelError> {
|
||||
// If no WASM bytes, return default config (for testing)
|
||||
if self.prepared.component_bytes.is_empty() {
|
||||
if self.prepared.component().is_none() {
|
||||
tracing::info!(
|
||||
channel = %self.name,
|
||||
"WASM channel on_start called (no WASM module, returning defaults)"
|
||||
@@ -918,7 +922,7 @@ impl WasmChannel {
|
||||
);
|
||||
|
||||
// If no WASM bytes, return 200 OK (for testing)
|
||||
if self.prepared.component_bytes.is_empty() {
|
||||
if self.prepared.component().is_none() {
|
||||
tracing::debug!(
|
||||
channel = %self.name,
|
||||
method = method,
|
||||
@@ -1018,7 +1022,7 @@ impl WasmChannel {
|
||||
/// Called periodically if polling is configured.
|
||||
pub async fn call_on_poll(&self) -> Result<(), WasmChannelError> {
|
||||
// If no WASM bytes, do nothing (for testing)
|
||||
if self.prepared.component_bytes.is_empty() {
|
||||
if self.prepared.component().is_none() {
|
||||
tracing::debug!(
|
||||
channel = %self.name,
|
||||
"WASM channel on_poll called (no WASM module)"
|
||||
@@ -1118,7 +1122,7 @@ impl WasmChannel {
|
||||
);
|
||||
|
||||
// If no WASM bytes, do nothing (for testing)
|
||||
if self.prepared.component_bytes.is_empty() {
|
||||
if self.prepared.component().is_none() {
|
||||
tracing::debug!(
|
||||
channel = %self.name,
|
||||
message_id = %message_id,
|
||||
@@ -1236,7 +1240,7 @@ impl WasmChannel {
|
||||
metadata: &serde_json::Value,
|
||||
) -> Result<(), WasmChannelError> {
|
||||
// If no WASM bytes, do nothing (for testing)
|
||||
if self.prepared.component_bytes.is_empty() {
|
||||
if self.prepared.component().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1307,7 +1311,7 @@ impl WasmChannel {
|
||||
timeout: Duration,
|
||||
wit_update: wit_channel::StatusUpdate,
|
||||
) -> Result<(), WasmChannelError> {
|
||||
if prepared.component_bytes.is_empty() {
|
||||
if prepared.component().is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1627,7 +1631,7 @@ impl WasmChannel {
|
||||
workspace_store: &Arc<ChannelWorkspaceStore>,
|
||||
) -> Result<Vec<EmittedMessage>, WasmChannelError> {
|
||||
// Skip if no WASM bytes (testing mode)
|
||||
if prepared.component_bytes.is_empty() {
|
||||
if prepared.component().is_none() {
|
||||
tracing::debug!(
|
||||
channel = %channel_name,
|
||||
"WASM channel on_poll called (no WASM module)"
|
||||
@@ -2206,7 +2210,7 @@ mod tests {
|
||||
let prepared = Arc::new(PreparedChannelModule {
|
||||
name: "test".to_string(),
|
||||
description: "Test channel".to_string(),
|
||||
component_bytes: Vec::new(),
|
||||
component: None,
|
||||
limits: ResourceLimits::default(),
|
||||
});
|
||||
|
||||
@@ -2271,7 +2275,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_poll_no_wasm_returns_empty() {
|
||||
// When there's no WASM module (empty component_bytes), execute_poll
|
||||
// When there's no WASM module (None component), execute_poll
|
||||
// should return an empty vector of messages
|
||||
let config = WasmChannelRuntimeConfig::for_testing();
|
||||
let runtime = Arc::new(WasmChannelRuntime::new(config).unwrap());
|
||||
@@ -2279,7 +2283,7 @@ mod tests {
|
||||
let prepared = Arc::new(PreparedChannelModule {
|
||||
name: "poll-test".to_string(),
|
||||
description: "Test channel".to_string(),
|
||||
component_bytes: Vec::new(), // No WASM bytes
|
||||
component: None, // No WASM module
|
||||
limits: ResourceLimits::default(),
|
||||
});
|
||||
|
||||
@@ -2381,7 +2385,7 @@ mod tests {
|
||||
let prepared = Arc::new(PreparedChannelModule {
|
||||
name: "poll-channel".to_string(),
|
||||
description: "Polling test channel".to_string(),
|
||||
component_bytes: Vec::new(),
|
||||
component: None,
|
||||
limits: ResourceLimits::default(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user