EnerOS v0.46.0 Release Notes
- Release Date: 2026-07-03
- Codename: Stable
- Support Status: Stable, Long-Term Support candidate
- Total Crates: 55
- Test Cases: 7200+
- Contributors: 30
Version Overview
EnerOS v0.46.0 is the sixth release of the “Mature Optimization” phase, focusing on stability enhancement. After the first five releases completed performance benchmarking, security auditing, developer experience, documentation systems, and ecosystem expansion, this release returns to engineering fundamentals, concentrating on solving stability problems during long-term operation, laying a solid foundation for the final convergence of v0.47.0.
Power systems have extremely stringent availability requirements. Provincial dispatching centers require system annual availability to reach 99.999% (i.e., annual downtime not exceeding 5 minutes). This means EnerOS must maintain memory stability, no performance degradation, and recoverable errors during long-term continuous operation (months or even years). v0.46.0 discovered 23 memory leaks, 12 error handling defects, and 8 unreleased resource issues through systematic stress testing, and fixed all of them.
This release introduces an automated stress testing framework eneros-stress, which can simulate 7 days of real load within 24 hours, verifying long-term system stability through accelerated clocks and fault injection. All stress tests run on a weekly cycle in CI, ensuring each commit does not introduce new stability issues.
Key Metrics
| Metric | Value | Description |
|---|---|---|
| Crate count | 55 | Added eneros-stress |
| Test cases | 7200+ | Including 180+ stress tests |
| Memory leak fixes | 23 | All fixed |
| Error handling defect fixes | 12 | All fixed |
| Continuous run test | 168 hours | No degradation |
| Memory growth (24h) | < 0.1% | Full load operation |
| Availability target | 99.999% | Annual downtime < 5 minutes |
New Features
1. Stress Testing Framework
Added the eneros-stress crate, providing systematic stress testing capabilities. The framework supports load generation, fault injection, accelerated clocks, and resource monitoring, capable of simulating long-term operation scenarios in a short time.
Framework Architecture
eneros-stress/
├── src/
│ ├── generator/ # Load generator
│ │ ├── topology.rs # Topology operation load
│ │ ├── powerflow.rs # Power flow computation load
│ │ ├── timeseries.rs # Time-series write load
│ │ └── agent.rs # Agent message load
│ ├── injector/ # Fault injector
│ │ ├── network.rs # Network faults
│ │ ├── disk.rs # Disk faults
│ │ └── process.rs # Process faults
│ ├── monitor/ # Resource monitoring
│ │ ├── memory.rs # Memory growth detection
│ │ ├── fd.rs # File descriptor leaks
│ │ └── thread.rs # Thread leaks
│ └── clock/ # Accelerated clock
Usage Example
use eneros_stress::{StressTest, StressConfig, LoadProfile, FaultInjector};
use std::time::Duration;
let stress = StressTest::new(StressConfig {
duration: Duration::from_secs(86400 * 7), // Simulate 7 days
accelerated_clock: 24.0, // 24x acceleration, actual runtime 7 hours
load_profile: LoadProfile::Realistic {
peak_times: vec![8, 12, 18], // Daily peak hours
base_load: 0.3, // Base load 30%
peak_load: 0.95, // Peak load 95%
},
fault_injection: FaultInjector::Chaos {
network_partition_prob: 0.001, // 0.1% network partition
disk_full_prob: 0.0005, // 0.05% disk full
process_kill_prob: 0.002, // 0.2% process kill
},
memory_leak_threshold: Duration::from_secs(3600), // Detect after 1 hour
});
let report = stress.run().await?;
println!("Test results:");
println!(" Total operations: {}", report.total_ops);
println!(" Error count: {}", report.errors.len());
println!(" Memory growth: {:.2}%", report.memory_growth_pct);
println!(" Availability: {:.4}%", report.availability * 100.0);
for leak in &report.memory_leaks {
println!(" Memory leak: {} (growth {:.2} MB/h)", leak.location, leak.rate_mb_per_hour);
}
Stress Test Scenarios
| Scenario | Load Pattern | Fault Injection | Duration | Validation Target |
|---|---|---|---|---|
| Full load operation | 100% continuous | None | 168 hours | Memory stability |
| Peak-valley fluctuation | Real load curve | None | 7 days (accelerated) | No performance degradation |
| Chaos engineering | Real load | Network partition | 24 hours | Fault recovery |
| Resource exhaustion | Increasing load | Disk full | 12 hours | Graceful degradation |
| Long connection | Continuous connection | Process kill | 48 hours | Reconnection recovery |
2. Memory Leak Fixes
Discovered and fixed 23 memory leaks through stress testing, covering five modules: topology snapshots, Agent context, time-series cache, constraint engine, and audit chain.
Main Leak Points
| Module | Leak Cause | Impact Rate | Fix Method |
|---|---|---|---|
| eneros-topology | Snapshots not released | 2.3 MB/h | Reference counting + Drop |
| eneros-agent | Context cache accumulation | 1.8 MB/h | LRU eviction |
| eneros-timeseries | Compression buffer not recycled | 5.1 MB/h | Tiered flushing |
| eneros-constraint | Constraint snapshot history | 0.7 MB/h | Limit history depth |
| eneros-trust | Audit log buffer | 3.2 MB/h | Periodic flush |
| eneros-memory | Agent memory vectors | 4.5 MB/h | Hierarchical compression |
| eneros-orchestrator | DAG node cache | 1.2 MB/h | Cleanup after execution |
Memory Monitoring Tool
use eneros_stress::monitor::{MemoryMonitor, LeakDetector};
let monitor = MemoryMonitor::new()
.sample_interval(Duration::from_secs(60))
.leak_detector(LeakDetector::LinearRegression {
min_samples: 60, // At least 60 samples (1 hour)
growth_threshold: 0.1, // Alert if growth > 0.1% per hour
});
monitor.start().await?;
// Get memory report
let snapshot = monitor.snapshot().await?;
println!("Current RSS: {:.1} MB", snapshot.rss_mb);
println!("24-hour growth: {:.2}%", snapshot.growth_24h_pct);
// Detect leaks
if let Some(leaks) = monitor.detect_leaks().await? {
for leak in leaks {
eprintln!("Suspected leak: {} growth {:.2} MB/h", leak.location, leak.rate);
}
}
3. Long-Term Operation Optimization
Multiple optimizations for long-term operation scenarios, ensuring system performance does not degrade after months of continuous operation.
Snapshot History Depth Limit
use eneros_topology::{TopologyConfig, SnapshotPolicy};
let config = TopologyConfig {
snapshot_policy: SnapshotPolicy::Bounded {
max_history: 1000, // Keep at most 1000 snapshots
max_age: Duration::from_secs(3600), // Keep for at most 1 hour
eviction: EvictionPolicy::OldestFirst,
},
..Default::default()
};
Agent Context LRU Eviction
use eneros_agent::{AgentRuntime, CacheConfig};
let runtime = AgentRuntime::new(CacheConfig {
max_contexts: 500, // Cache at most 500 contexts
context_ttl: Duration::from_secs(300), // Evict if unused for 5 minutes
eviction: EvictionPolicy::LRU,
});
Time-Series Data Tiered Flushing
use eneros_timeseries::{TsdbConfig, FlushPolicy};
let config = TsdbConfig {
flush_policy: FlushPolicy::Tiered {
hot_tier_size: 8 * 1024 * 1024, // Hot tier 8MB
warm_tier_size: 64 * 1024 * 1024, // Warm tier 64MB
cold_tier_path: "data/cold/",
flush_interval: Duration::from_millis(100),
backpressure_threshold: 0.8, // Trigger backpressure at 80%
},
..Default::default()
};
4. Error Handling Enhancement
Systematically reviewed error handling paths across all crates, fixing 12 error handling defects, including unhandled panics, silently ignored errors, and improper error propagation.
Error Handling Improvements
| Defect Type | Count | Impact | Fix Method |
|---|---|---|---|
| Unhandled panic | 3 | Process crash | Replace with Result |
| Silently ignored error | 5 | Data loss | Explicit handling or propagation |
| Error information loss | 4 | Debugging difficulty | Preserve error chain |
| Unreleased resource | 8 | Resource leak | RAII + Drop |
Graceful Degradation Mechanism
use eneros_core::resilience::{CircuitBreaker, FallbackPolicy};
let breaker = CircuitBreaker::new()
.failure_threshold(5) // Open circuit after 5 failures
.reset_timeout(Duration::from_secs(30)) // Try recovery after 30 seconds
.fallback(FallbackPolicy::UseLastKnownGood); // Degrade to last known good value
match breaker.call(|| async {
ctx.syscall(SolvePowerFlow { /* ... */ }).await
}).await {
Ok(result) => { /* Normal result */ }
Err(CircuitBreakerError::Open) => {
// Circuit open, use fallback value
let fallback = ctx.cache.get_last_powerflow().await?;
log::warn!("Power flow computation circuit open, using cached value");
}
}
Backpressure Mechanism
use eneros_core::resilience::{Backpressure, BackpressureConfig};
let bp = Backpressure::new(BackpressureConfig {
high_watermark: 0.8, // Start backpressure at 80%
low_watermark: 0.5, // Stop backpressure at 50%
queue_capacity: 10000,
});
// Check backpressure on write
if bp.is_pressure_high() {
// Return backpressure signal, upstream should slow down
return Err(BackpressureError::SlowDown);
}
bp.increment();
let result = do_work().await;
bp.decrement();
Improvements
Panic Capture and Recovery
All Agent execution entry points add panic capture, preventing a single Agent’s panic from crashing the entire runtime. After panic is captured, the complete call stack is recorded, and Agent restart is triggered.
use eneros_agent::runtime::PanicGuard;
let guard = PanicGuard::new("dispatch-agent");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
agent.run(ctx)
}));
match result {
Ok(Ok(())) => { /* Normal completion */ }
Ok(Err(e)) => { /* Agent returned error */ }
Err(panic) => {
let backtrace = guard.capture_backtrace(panic);
log::error!("Agent panic: {}", backtrace);
runtime::restart_agent("dispatch-agent").await?;
}
}
Resource Limits
Each Agent and plugin adds resource limits, preventing resource exhaustion from affecting system stability.
| Resource | Default Limit | Over-limit Behavior |
|---|---|---|
| Memory | 512 MB | OOM alert + restart |
| CPU | 4 cores | Throttle |
| File descriptors | 1024 | Reject new FDs |
| Network connections | 256 | Reject new connections |
| Disk writes | 1 GB/h | Throttle |
Graceful Shutdown
When the system shuts down, it exits gracefully in dependency order, ensuring all in-flight requests complete, buffers flush, and state persists.
use eneros_os::shutdown::{ShutdownManager, ShutdownOrder};
let manager = ShutdownManager::new()
.timeout(Duration::from_secs(30))
.order(ShutdownOrder::Dependency)
.step("agents", |ctx| async { /* Stop all Agents */ })
.step("api", |ctx| async { /* Close API gateway */ })
.step("timeseries", |ctx| async { /* Flush time-series data */ })
.step("topology", |ctx| async { /* Persist topology */ })
.step("kernel", |ctx| async { /* Kernel cleanup */ });
manager.shutdown().await?;
Bug Fixes
- BG-701: Topology snapshot reference counting error during concurrent writes, fixed with atomic operations
- BG-705: Agent context cache unlimited causing infinite memory growth, introduced LRU
- BG-709: Time-series compression buffer infinite growth when write speed exceeds flush speed, introduced backpressure
- BG-713: Constraint engine history snapshots not cleaned, limited max depth
- BG-717: Audit log buffer not periodically flushed, changed to scheduled flushing
- BG-721: Agent memory vectors infinite accumulation, introduced hierarchical compression
- BG-725: DAG orchestration node cache not cleaned, cleanup after execution
- BG-729: Panic not caught in Agent, added PanicGuard
- BG-733: File descriptor leak on reconnection, fixed RAII encapsulation
- BG-737: In-flight requests discarded during graceful shutdown, added drain period
Breaking Changes
BC-251: AgentRuntime Adds Resource Limits Configuration
The configuration parameter of AgentRuntime::new adds a resource_limits field, which needs to be added to existing code.
// v0.45.0 (old)
let runtime = AgentRuntime::new(RuntimeConfig::default());
// v0.46.0 (new)
let runtime = AgentRuntime::new(RuntimeConfig {
resource_limits: ResourceLimits::default(),
..Default::default()
});
BC-252: TsdbConfig flush_policy Field Change
flush_policy changed from simple FlushInterval to FlushPolicy enum, supporting tiered flushing.
Dependency Upgrades
| Dependency | Old Version | New Version | Description |
|---|---|---|---|
| rustc | 1.75 | 1.76 | Panic hook enhancement |
| jemallocator | 0.5 | 0.5.4 | Memory allocator |
| mimalloc | 0.1 | 0.1.39 | Alternative allocator |
| tracing | 0.1.40 | 0.1.40 | No change |
Upgrade Guide
Upgrade from v0.45.0
rustup default 1.76.0
cargo update
cargo test --workspace
Handle breaking changes:
// Add resource limits configuration
let runtime = AgentRuntime::new(RuntimeConfig {
resource_limits: ResourceLimits {
max_memory_mb: 512,
max_cpu_cores: 4,
max_file_descriptors: 1024,
},
..Default::default()
});
Run stress tests to verify stability:
eneros-cli stress run --scenario full-load --duration 7h
eneros-cli stress report
Related Documentation
- Stress Testing Guide - Stress testing framework usage
- Memory Analysis - Memory leak troubleshooting
- Error Handling - Error handling specifications
- Graceful Shutdown - Graceful exit mechanism