EnerOS v0.11.0
Release Date: November 3, 2024 Codename: DualExec Git Tag: v0.11.0 Support Status: Stable Total Crates: 22 (4 new) Test Cases: 3100+ (480 new)
Version Overview
EnerOS v0.11.0 “DualExec” is a key version marking EnerOS’s entry into the “runtime layering” phase. The core goal of this version is to introduce the Dual Execution Architecture, separating real-time control tasks and analytical computing tasks of the power system into two independent execution domains, fundamentally solving the long-standing pain point of “real-time tasks being blocked by analytical tasks”.
In traditional power operating systems, microsecond-level tasks such as real-time telemetry acquisition, protection logic, and AGC regulation share the same scheduler and thread pool with second-to-minute-level tasks such as power flow calculation, load forecasting, and report aggregation. This causes real-time task tail latency to degrade from microsecond level to millisecond or even tens of milliseconds under high analytical task load, creating safety risks such as protection refusal and control timeout. v0.11.0 physically isolates the two execution domains, allowing the real-time domain to always maintain microsecond-level response, while the analytical domain can fully utilize CPU resources for batch computation.
This version introduces three new crates: eneros-exec-rt (real-time domain runtime), eneros-exec-batch (analytical domain runtime), and eneros-exec-bridge (inter-domain data synchronization bridge), and extends the eneros-sched scheduler to support dual-domain priority preemption. The design philosophy is “physical isolation is better than logical priority” — only by scheduling the two types of tasks to different CPU core sets, different memory pools, and different I/O channels can the determinism of real-time tasks be guaranteed at the hardware level.
Key Data
| Metric | v0.10.0 | v0.11.0 | Improvement |
|---|---|---|---|
| Real-time task P99 latency | 2.4ms | 18μs | 133x |
| Real-time task P99.9 latency | 12ms | 42μs | 285x |
| Analytical task throughput | 80,000 ops/s | 230,000 ops/s | 2.9x |
| CPU utilization (full load) | 62% | 89% | +27pp |
| Tail latency jitter | High | Very low | Significant improvement |
New Features
1. Dual Execution Architecture
Introduces two independent runtimes, eneros-exec-rt and eneros-exec-batch, hosting the Realtime Domain and Batch Domain respectively. The two domains are bound to different CPU core sets at startup and isolated at the hardware level through cgroup / CPU affinity.
Execution Domain Configuration
use eneros_exec_rt::{RtRuntime, RtConfig};
use eneros_exec_batch::{BatchRuntime, BatchConfig};
use eneros_exec_bridge::Bridge;
// Configure real-time domain: bound to CPUs 0-3, using isolated memory pool
let rt = RtRuntime::new(RtConfig {
cpu_set: vec![0, 1, 2, 3],
memory_pool: MemoryPool::isolated(256 * 1024 * 1024),
pre_allocated_buffers: 1024,
polling_interval: Duration::microseconds(50),
max_jitter: Duration::microseconds(10),
})?;
// Configure analytical domain: bound to CPUs 4-15, dynamically expandable
let batch = BatchRuntime::new(BatchConfig {
cpu_set: vec![4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
memory_pool: MemoryPool::dynamic(2 * 1024 * 1024 * 1024),
worker_threads: 12,
queue_capacity: 65536,
})?;
// Start dual-domain bridge for inter-domain data synchronization
let bridge = Bridge::new(&rt, &batch)
.sync_interval(Duration::milliseconds(10))
.buffer_size(8192);
Inter-domain Data Flow
| Data Flow | Direction | Sync Method | Latency |
|---|---|---|---|
| Telemetry data | Real-time → Analytical | Lock-free ring buffer | < 5μs |
| Derived metrics | Analytical → Real-time | Shared atomic variables | < 1μs |
| Control commands | Analytical → Real-time | High-priority channel | < 20μs |
| Alert events | Real-time → Analytical | Async queue | < 100μs |
2. Inter-domain Data Synchronization Mechanism
Introduces the eneros-exec-bridge crate, providing safe data exchange channels between the real-time and analytical domains. All cross-domain data is passed through lock-free data structures, avoiding jitter from lock contention to the real-time domain.
Lock-free Ring Buffer
use eneros_exec_bridge::{RingBuffer, Producer, Consumer};
// Create a cross-domain shared lock-free ring buffer
let (producer, consumer) = RingBuffer::<TelemetrySnapshot>::new(8192)
.split()?;
// Real-time domain producer: write telemetry snapshots
producer.try_push(TelemetrySnapshot {
bus_id: 1,
voltage: 1.024,
current: 512.3,
timestamp: Instant::now(),
})?;
// Analytical domain consumer: batch read
let batch: Vec<TelemetrySnapshot> = consumer.drain_up_to(256)?;
Shared Derived Metrics
use eneros_exec_bridge::SharedMetric;
// Analytical domain calculates derived metrics, writes to shared atomic variables
let metric = SharedMetric::new();
metric.store_load_forecast(1250.6); // MW
// Real-time domain reads (lock-free, O(1))
let forecast = metric.load_load_forecast();
3. Isolation and Priority Scheduling
Extends the eneros-sched scheduler to support cross-domain priority preemption. Real-time domain tasks have absolute priority and can preempt analytical domain tasks; analytical domain tasks can “borrow” real-time domain CPU resources when the real-time domain is idle.
Priority Model
| Priority | Domain | Typical Tasks | Preemption Capability |
|---|---|---|---|
| P0 (Critical) | Real-time | Protection logic tripping | Preempts all |
| P1 (Realtime) | Real-time | AGC regulation | Preempts P2+ |
| P2 (High) | Real-time | Telemetry acquisition | Preempts P3+ |
| P3 (Normal) | Analytical | Power flow calculation | Cannot preempt real-time |
| P4 (Low) | Analytical | Report aggregation | Cannot preempt real-time |
Scheduler Configuration
use eneros_sched::{Scheduler, SchedConfig, Domain};
let sched = Scheduler::new(SchedConfig {
domains: vec![
Domain::realtime(rt_config),
Domain::batch(batch_config),
],
preemption: PreemptionPolicy::Strict,
borrowing: BorrowingPolicy::AllowWhenIdle,
starvation_timeout: Duration::seconds(30),
});
4. Real-time Domain Microsecond Response Guarantee
The real-time domain runtime uses pre-allocated memory + Polling Mode, avoiding jitter from dynamic memory allocation and interrupt response. On standard x86 servers, real-time task P99 latency is stable within 18μs.
Real-time Task Example
use eneros_exec_rt::{RtTask, RtContext};
// Register a microsecond-level telemetry task
RtTask::new("telemetry-poll")
.priority(Priority::P2)
.period(Duration::microseconds(200))
.stack_size(64 * 1024)
.spawn(move |ctx: &RtContext| {
// Read ADC samples from shared memory
let sample = ctx.shmem().read_adc(BUS_1)?;
// Validate physical constraints
ctx.constraint().check_voltage(sample.voltage)?;
// Write to ring buffer for analytical domain consumption
ctx.bridge().push(TelemetrySnapshot::from(sample))?;
Ok(())
})?;
Improvements
- Scheduler: Refactored
eneros-schedinternal data structures, replacingstd::sync::mpscwithcrossbeam-deque, improving work-stealing efficiency by 40% - Memory management: Real-time domain introduces
bumpaloblock allocator, avoiding allocation jitter - Observability: Added
eneros-exec-tracesubmodule, providing inter-domain data flow tracing - Configuration: Supports declarative dual-domain topology configuration via
eneros.toml
Bug Fixes
- Fixed
eneros-schedscheduler panicking when CPU core count changes (#1124) - Fixed
eneros-timeserieswrite path occasional deadlock under high concurrency (#1131) - Fixed
eneros-agentnot releasing CPU affinity binding when tasks are canceled (#1137) - Fixed
eneros-constraintlinear memory growth during batch validation (#1142)
Breaking Changes
Scheduler::runsignature change: Addeddomains: &[Domain]parameter; for single-domain configuration useDomain::single()Task::priority: Priority enum changed fromu8to strongly typedPriority, needs to be constructed viaPriority::P2etc.eneros-execcrate split: Originaleneros-execsplit intoeneros-exec-rtandeneros-exec-batch; see upgrade guide for old code migration
Performance Improvements
- Real-time task P99 latency reduced from 2.4ms to 18μs (133x improvement)
- Analytical task throughput increased from 80,000 ops/s to 230,000 ops/s (2.9x improvement)
- Full-load CPU utilization increased from 62% to 89%
- Inter-domain data synchronization latency stable within 5μs
Contributors
This version was completed by 18 contributors with 312 commits. Special thanks to:
- @rt-kernel: Real-time domain runtime core implementation
- @lockfree-master: Lock-free ring buffer design
- @sched-guru: Dual-domain priority preemption scheduling algorithm
- @memory-pool: Real-time domain memory pre-allocation scheme
Upgrade Guide
Upgrading from v0.10.0
1. Update Dependencies
# Cargo.toml
[dependencies]
eneros-exec-rt = { version = "0.11", features = ["polling"] }
eneros-exec-batch = { version = "0.11" }
eneros-exec-bridge = { version = "0.11" }
eneros-sched = { version = "0.11" }
2. Migrate Scheduler Configuration
// v0.10.0 old style
let sched = Scheduler::new(SingleDomainConfig::default());
// v0.11.0 new style
let sched = Scheduler::new(SchedConfig {
domains: vec![
Domain::realtime(rt_config),
Domain::batch(batch_config),
],
preemption: PreemptionPolicy::Strict,
borrowing: BorrowingPolicy::AllowWhenIdle,
starvation_timeout: Duration::seconds(30),
});
3. Annotate Task Domain Affiliation
All tasks must explicitly declare their execution domain:
// Old: domain not declared
Task::new("load-flow").priority(5).spawn(...);
// New: domain declared
Task::new("load-flow")
.domain(DomainKind::Batch)
.priority(Priority::P3)
.spawn(...);
4. CPU Pinning Recommendations
For production environments, it is recommended to use taskset or cgroup to pin the real-time domain to a dedicated CPU core set, and enable the isolcpus kernel parameter to avoid interference from other system processes. See the operations manual “Dual Execution Architecture Deployment” section for detailed configuration.