Skip to main content

v0.11.0 Release Notes

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

Metricv0.10.0v0.11.0Improvement
Real-time task P99 latency2.4ms18μs133x
Real-time task P99.9 latency12ms42μs285x
Analytical task throughput80,000 ops/s230,000 ops/s2.9x
CPU utilization (full load)62%89%+27pp
Tail latency jitterHighVery lowSignificant 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 FlowDirectionSync MethodLatency
Telemetry dataReal-time → AnalyticalLock-free ring buffer< 5μs
Derived metricsAnalytical → Real-timeShared atomic variables< 1μs
Control commandsAnalytical → Real-timeHigh-priority channel< 20μs
Alert eventsReal-time → AnalyticalAsync 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

PriorityDomainTypical TasksPreemption Capability
P0 (Critical)Real-timeProtection logic trippingPreempts all
P1 (Realtime)Real-timeAGC regulationPreempts P2+
P2 (High)Real-timeTelemetry acquisitionPreempts P3+
P3 (Normal)AnalyticalPower flow calculationCannot preempt real-time
P4 (Low)AnalyticalReport aggregationCannot 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-sched internal data structures, replacing std::sync::mpsc with crossbeam-deque, improving work-stealing efficiency by 40%
  • Memory management: Real-time domain introduces bumpalo block allocator, avoiding allocation jitter
  • Observability: Added eneros-exec-trace submodule, providing inter-domain data flow tracing
  • Configuration: Supports declarative dual-domain topology configuration via eneros.toml

Bug Fixes

  • Fixed eneros-sched scheduler panicking when CPU core count changes (#1124)
  • Fixed eneros-timeseries write path occasional deadlock under high concurrency (#1131)
  • Fixed eneros-agent not releasing CPU affinity binding when tasks are canceled (#1137)
  • Fixed eneros-constraint linear memory growth during batch validation (#1142)

Breaking Changes

  • Scheduler::run signature change: Added domains: &[Domain] parameter; for single-domain configuration use Domain::single()
  • Task::priority: Priority enum changed from u8 to strongly typed Priority, needs to be constructed via Priority::P2 etc.
  • eneros-exec crate split: Original eneros-exec split into eneros-exec-rt and eneros-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.