Lifecycle Hooks API Reference¶
Category: Reference - Information-oriented
Audience: Developers implementing ComponentActor lifecycle customization
Purpose: Technical specification of lifecycle hook methods and execution order
Overview¶
Lifecycle hooks provide extension points for customizing ComponentActor behavior at key lifecycle events. Hooks are invoked automatically by the runtime during state transitions, enabling initialization logic, cleanup, monitoring, and error handling without modifying core ComponentActor code.
Hook Execution Order¶
ComponentActor::new()
↓
pre_start() ────────> [hooks called before Child::start()]
↓
Child::start() ─────> [WASM instantiation]
↓
post_start() ───────> [hooks called after successful start]
↓
ActorState::Ready ───> [component processes messages]
↓
pre_stop() ─────────> [hooks called before Child::stop()]
↓
Child::stop() ──────> [WASM cleanup]
↓
post_stop() ────────> [hooks called after successful stop]
↓
ActorState::Terminated
Hook Timing¶
Measured in Task 6.2 (task-004-phase-6-task-6.2-completion-report.md):
- Lifecycle overhead (pre_start + post_start + pre_stop + post_stop): 5-8µs
- Individual hook call: < 1µs (when using NoOpHooks)
- Full lifecycle (construction + start + stop): 1.49µs
Trait: LifecycleHooks¶
Base trait for implementing custom lifecycle hooks.
Definition:
pub trait LifecycleHooks: Send + Sync {
fn pre_start(&mut self, ctx: &LifecycleContext) -> HookResult;
fn post_start(&mut self, ctx: &LifecycleContext) -> HookResult;
fn pre_stop(&mut self, ctx: &LifecycleContext) -> HookResult;
fn post_stop(&mut self, ctx: &LifecycleContext) -> HookResult;
fn on_message_received(&mut self, ctx: &LifecycleContext, msg: &ComponentMessage) -> HookResult;
fn on_error(&mut self, ctx: &LifecycleContext, error: &WasmError) -> HookResult;
fn on_restart(&mut self, ctx: &LifecycleContext, reason: &str) -> HookResult;
}
Hook Methods¶
pre_start()¶
Called before WASM instantiation begins.
Invocation: Before Child::start() creates the WASM runtime
Purpose:
- Validate component metadata
- Initialize external resources (databases, connections)
- Pre-allocate resources
- Log component startup
Parameters:
ctx- Lifecycle context with component ID and metadata
Returns: HookResult (Ok to continue, Err to abort startup)
Performance: < 1µs overhead when using NoOpHooks
Example:
use airssys_wasm::actor::lifecycle::{LifecycleHooks, LifecycleContext, HookResult};
struct MyHooks;
impl LifecycleHooks for MyHooks {
fn pre_start(&mut self, ctx: &LifecycleContext) -> HookResult {
println!("Component starting: {}", ctx.component_id.as_str());
// Validate resource limits
if ctx.metadata.resource_limits.max_memory_bytes < 1_000_000 {
return HookResult::Err("Insufficient memory allocation".to_string());
}
HookResult::Ok
}
// ... other hooks
}
post_start()¶
Called after successful WASM instantiation.
Invocation: After Child::start() successfully creates WASM runtime
Purpose:
- Initialize component state
- Subscribe to topics
- Register with external services
- Log successful startup
Parameters:
ctx- Lifecycle context with component ID and metadata
Returns: HookResult (Ok to continue, Err to fail startup)
Example:
fn post_start(&mut self, ctx: &LifecycleContext) -> HookResult {
println!("Component started successfully: {}", ctx.component_id.as_str());
// Log uptime start
tracing::info!(
component_id = %ctx.component_id.as_str(),
"Component ready for messages"
);
HookResult::Ok
}
pre_stop()¶
Called before component shutdown begins.
Invocation: Before Child::stop() initiates cleanup
Purpose:
- Flush pending operations
- Save state to persistent storage
- Unsubscribe from topics
- Notify dependent components
Parameters:
ctx- Lifecycle context with component ID and metadata
Returns: HookResult (Ok to continue, Err logged but cleanup proceeds)
Example:
fn pre_stop(&mut self, ctx: &LifecycleContext) -> HookResult {
println!("Component stopping: {}", ctx.component_id.as_str());
// Flush pending operations
// (Note: Errors in pre_stop don't prevent shutdown)
HookResult::Ok
}
post_stop()¶
Called after successful component shutdown.
Invocation: After Child::stop() completes cleanup
Purpose:
- Release external resources
- Log final statistics
- Send termination notifications
- Close connections
Parameters:
ctx- Lifecycle context with component ID and metadata
Returns: HookResult (errors logged, cleanup always completes)
Example:
fn post_stop(&mut self, ctx: &LifecycleContext) -> HookResult {
println!("Component stopped: {}", ctx.component_id.as_str());
// Log final statistics
tracing::info!(
component_id = %ctx.component_id.as_str(),
"Component terminated successfully"
);
HookResult::Ok
}
on_message_received()¶
Called before routing each incoming message.
Invocation: Before Actor::handle_message() processes the message
Purpose:
- Message-level logging
- Rate limiting
- Message filtering
- Metrics collection
Parameters:
ctx- Lifecycle contextmsg- Incoming ComponentMessage
Returns: HookResult (Ok to process, Err to drop message)
Performance: < 1µs overhead per message (NoOpHooks)
Example:
fn on_message_received(
&mut self,
ctx: &LifecycleContext,
msg: &ComponentMessage
) -> HookResult {
// Log message arrival
tracing::debug!(
component_id = %ctx.component_id.as_str(),
message_type = ?msg,
"Message received"
);
// Rate limiting example
if self.message_count > 1000 {
return HookResult::Err("Rate limit exceeded".to_string());
}
self.message_count += 1;
HookResult::Ok
}
on_error()¶
Called when an error occurs during component execution.
Invocation: After any error in lifecycle or message handling
Purpose:
- Error logging
- Error metrics
- Alert notifications
- Recovery attempts
Parameters:
ctx- Lifecycle contexterror- WasmError that occurred
Returns: HookResult (return value currently ignored, error propagated)
Example:
fn on_error(
&mut self,
ctx: &LifecycleContext,
error: &WasmError
) -> HookResult {
tracing::error!(
component_id = %ctx.component_id.as_str(),
error = %error,
"Component error occurred"
);
// Increment error counter
self.error_count += 1;
HookResult::Ok
}
on_restart()¶
Called when supervisor triggers component restart.
Invocation: Before restart attempt after failure
Purpose:
- Log restart reason
- Restart metrics
- Reset internal state
- Notify monitoring systems
Parameters:
ctx- Lifecycle contextreason- Restart reason description
Returns: HookResult (Ok to continue, Err logged but restart proceeds)
Example:
fn on_restart(
&mut self,
ctx: &LifecycleContext,
reason: &str
) -> HookResult {
tracing::warn!(
component_id = %ctx.component_id.as_str(),
reason = reason,
restart_count = self.restart_count,
"Component restarting"
);
self.restart_count += 1;
HookResult::Ok
}
Struct: LifecycleContext¶
Context information passed to hook methods.
Fields:
component_id: ComponentId- Component identifiermetadata: ComponentMetadata- Component metadata (name, version, limits)
Example:
use airssys_wasm::actor::lifecycle::LifecycleContext;
fn pre_start(&mut self, ctx: &LifecycleContext) -> HookResult {
println!("Starting: {} v{}",
ctx.metadata.name,
ctx.metadata.version
);
println!("Memory limit: {} bytes",
ctx.metadata.resource_limits.max_memory_bytes
);
HookResult::Ok
}
Enum: HookResult¶
Result type returned by hook methods.
Variants:
Ok- Hook succeeded, continue executionErr(String)- Hook failed with error message
Example:
use airssys_wasm::actor::lifecycle::HookResult;
// Success
return HookResult::Ok;
// Error with context
return HookResult::Err(format!(
"Validation failed: insufficient memory (need {}MB, have {}MB)",
required_mb, available_mb
));
Struct: NoOpHooks¶
Default zero-overhead hook implementation.
All hook methods return HookResult::Ok immediately with no side effects.
Performance: < 1µs per hook call
Example:
use airssys_wasm::actor::lifecycle::NoOpHooks;
// Default hooks (no-op)
let hooks = Box::new(NoOpHooks);
actor.set_lifecycle_hooks(hooks);
Error Handling in Hooks¶
Startup Hooks¶
- pre_start() error: Startup aborted, component transitions to
ActorState::Failed - post_start() error: Startup fails, cleanup initiated
Shutdown Hooks¶
- pre_stop() error: Error logged, shutdown continues
- post_stop() error: Error logged, cleanup completes
Message Hooks¶
- on_message_received() error: Message dropped, error logged
Other Hooks¶
- on_error(): Return value ignored, error propagated
- on_restart(): Error logged, restart proceeds
Best Practices¶
Keep Hooks Fast¶
Hooks are called in the critical path. Minimize execution time:
// ✅ GOOD - Fast validation
fn pre_start(&mut self, ctx: &LifecycleContext) -> HookResult {
if ctx.metadata.name.is_empty() {
return HookResult::Err("Component name required".to_string());
}
HookResult::Ok
}
// ❌ AVOID - Slow I/O in critical path
fn pre_start(&mut self, ctx: &LifecycleContext) -> HookResult {
// Blocking database query in startup path!
let result = database.query("SELECT * FROM components").unwrap();
HookResult::Ok
}
Idempotent Hooks¶
Design hooks to be safely called multiple times:
fn post_start(&mut self, ctx: &LifecycleContext) -> HookResult {
// ✅ GOOD - Idempotent subscription
if !self.subscribed {
broker.subscribe("events").await?;
self.subscribed = true;
}
HookResult::Ok
}
Error Recovery¶
Hooks should not panic. Return HookResult::Err for errors:
fn on_error(&mut self, ctx: &LifecycleContext, error: &WasmError) -> HookResult {
// ✅ GOOD - Graceful error handling
if let Err(e) = self.send_alert(error) {
tracing::warn!("Failed to send alert: {}", e);
// Continue anyway, don't propagate alert failure
}
HookResult::Ok
}
Complete Example¶
use airssys_wasm::actor::lifecycle::{LifecycleHooks, LifecycleContext, HookResult};
use airssys_wasm::actor::ComponentMessage;
use airssys_wasm::core::WasmError;
use std::sync::atomic::{AtomicU64, Ordering};
struct MetricsHooks {
message_count: AtomicU64,
error_count: AtomicU64,
restart_count: AtomicU64,
}
impl MetricsHooks {
fn new() -> Self {
Self {
message_count: AtomicU64::new(0),
error_count: AtomicU64::new(0),
restart_count: AtomicU64::new(0),
}
}
}
impl LifecycleHooks for MetricsHooks {
fn pre_start(&mut self, ctx: &LifecycleContext) -> HookResult {
tracing::info!("Component starting: {}", ctx.component_id.as_str());
HookResult::Ok
}
fn post_start(&mut self, ctx: &LifecycleContext) -> HookResult {
tracing::info!("Component ready: {}", ctx.component_id.as_str());
HookResult::Ok
}
fn pre_stop(&mut self, ctx: &LifecycleContext) -> HookResult {
tracing::info!(
"Component stopping: {} (processed {} messages)",
ctx.component_id.as_str(),
self.message_count.load(Ordering::Relaxed)
);
HookResult::Ok
}
fn post_stop(&mut self, ctx: &LifecycleContext) -> HookResult {
tracing::info!("Component stopped: {}", ctx.component_id.as_str());
HookResult::Ok
}
fn on_message_received(
&mut self,
_ctx: &LifecycleContext,
_msg: &ComponentMessage
) -> HookResult {
self.message_count.fetch_add(1, Ordering::Relaxed);
HookResult::Ok
}
fn on_error(
&mut self,
ctx: &LifecycleContext,
error: &WasmError
) -> HookResult {
self.error_count.fetch_add(1, Ordering::Relaxed);
tracing::error!(
component_id = %ctx.component_id.as_str(),
error = %error,
"Component error"
);
HookResult::Ok
}
fn on_restart(
&mut self,
ctx: &LifecycleContext,
reason: &str
) -> HookResult {
self.restart_count.fetch_add(1, Ordering::Relaxed);
tracing::warn!(
component_id = %ctx.component_id.as_str(),
reason = reason,
"Component restarting"
);
HookResult::Ok
}
}
// Usage
let mut actor = ComponentActor::new(/* ... */);
actor.set_lifecycle_hooks(Box::new(MetricsHooks::new()));
Related Documentation¶
- ComponentActor API - Core API reference
- Tutorial: Stateful Component - Using hooks with state
- State Management Patterns - State design rationale
References¶
- ADR-WASM-006: Component Isolation and Sandboxing
- Phase 5 Task 5.2: Lifecycle Hooks Implementation
- Task 6.2 Performance Report: Hook overhead measurements