ITADN

[Bug] EssPower circular dependency fix from #3113 needs to be applied to all ESS implementations

#3500Opencvabc 创建于 2026-01-06
C
cvabccommented
# [Bug] EssPower circular dependency causes "Coefficient was not found" error in multiple ESS implementations ## Summary The fix for the circular dependency issue in PR #3113 was only applied to `Edge2EdgeWebsocketEssImpl`. However, **many other ESS implementations** have the same problematic `@Reference` pattern for the `Power` service, causing the same "Coefficient was not found" error. ## Error Message ``` WARN [ems.edge.ess.core.power.Solver] Power-Solver: Solve failed: Coefficient for [ess0,ALL,ACTIVE] was not found. Ess-Power is not (yet) fully initialized. ``` ## Root Cause Analysis ### 1. Circular Dependency (Fixed in #3113 for Edge2Edge only) The dependency chain `ess -> _power -> ess` causes issues during OSGi component activation: 1. ESS component tries to activate 2. ESS has `@Reference(cardinality = MANDATORY)` to `Power` 3. `Power` (EssPowerImpl) needs ESS to register via `addEss()` to create coefficients 4. Race condition: ESS may be referenced before its coefficients are initialized **The fix in #3113:** ```java // Before (problematic): @Reference private Power power; // After (fixed): @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY, policy = ReferencePolicy.DYNAMIC) private volatile Power power; ``` ### 2. Thread Safety Issue in Data.java (Unfixed) There's also an underlying race condition in `Data.java`: ```java // EssPowerImpl.java - addEss() is synchronized protected synchronized void addEss(ManagedSymmetricEss ess) { this.data.addEss(ess); // adds to esss list, then updates coefficients } // Data.java - getConstraintsForAllInverters() is NOT synchronized public List<Constraint> getConstraintsForAllInverters() throws OpenemsException { return this.getConstraintsWithoutDisabledInverters(Collections.emptyList()); } ``` **Race condition:** ``` Thread A (OSGi): Thread B (Cycle Scheduler): ───────────────── ─────────────────────────── addEss(ess0) - acquires lock esss.add(ess0) ✓ updateInverters() starts... Solver.solve() - NO LOCK! getConstraintsForAllInverters() iterates esss → sees ess0 coefficients.of("ess0"...) → FAILS! (not initialized yet) coefficients.initialize() ✓ releases lock ``` ## Affected Components ### Affected ESS Implementations | Module | File | |--------|------| | io.openems.edge.ess.generic | EssGenericManagedSymmetricImpl.java | | io.openems.edge.ess.generic | EssGenericOffGridImpl.java | | io.openems.edge.ess.cluster | EssClusterImpl.java | | io.openems.edge.goodwe | GoodWeEssImpl.java | | io.openems.edge.goodwe | GoodWeBatteryInverterImpl.java | | io.openems.edge.edge2edge | Edge2EdgeEssImpl.java | | io.openems.edge.edge2edge | Edge2EdgeGenericReadComponentImpl.java | | io.openems.edge.fenecon.mini | FeneconMiniEssImpl.java | | io.openems.edge.fenecon.pro | FeneconProEssImpl.java | | io.openems.edge.ess.fenecon.commercial40 | EssFeneconCommercial40Impl.java | | io.openems.edge.sma | EssSmaSunnyIslandImpl.java | | io.openems.edge.kaco.blueplanet.hybrid10 | KacoBlueplanetHybrid10EssImpl.java | | io.openems.edge.kostal | KostalManagedEssImpl.java | | io.openems.edge.simulator | SimulatorEssSinglePhaseReactingImpl.java | | io.openems.edge.simulator | SimulatorEssSymmetricReactingImpl.java | | io.openems.edge.simulator | SimulatorEssAsymmetricReactingImpl.java | *Note: Other ESS implementations in third-party modules may also be affected.* ## Steps to Reproduce 1. Configure multiple ESS components (e.g., ess0, ess1, ess2, ess3) 2. Start OpenEMS Edge 3. Observe logs during startup 4. Error appears: `Coefficient for [essX,ALL,ACTIVE] was not found` The error may: - Appear briefly during startup and then stop - Persist for a long time if component activation order is unfavorable - Reappear when ESS components reconnect (e.g., Modbus disconnect/reconnect) ## Proposed Fix ### Fix 1: Apply OPTIONAL/DYNAMIC pattern to all ESS implementations For each affected ESS implementation: ```java // Change from: @Reference private Power power; // or @Reference(cardinality = ReferenceCardinality.MANDATORY, policyOption = ReferencePolicyOption.GREEDY) private Power power; // To: @Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY, policy = ReferencePolicy.DYNAMIC) private volatile Power power; ``` And update `getPower()`: ```java @Override public Power getPower() { final var power = this.power; if (power == null) { throw new OpenemsRuntimeException("Ess.Power is not yet available"); } return power; } ``` ### Fix 2: Add synchronization to Data.java (additional fix) ```java // Data.java - make constraint retrieval synchronized public synchronized List<Constraint> getConstraintsWithoutDisabledInverters( Collection<Inverter> disabledInverters) throws OpenemsException { // existing code } ``` ## Environment - OpenEMS Version: 2025.6.0+ (post PR #3113) - Java Version: 21 - OS: Linux (ARM64) ## Related - PR #3113 - FEMS Backports 2025-05-01 (partial fix for Edge2EdgeWebsocketEss only) - Community discussion: https://community.openems.io/t/how-can-i-prevent-the-error-coefficient-ess0-all-active-was-not-found-ess-power-is-not-yet-fully-initialized-from-occurring/10352 ## Checklist - [ ] Apply fix to all ESS implementations listed above - [ ] Consider adding synchronization to `Data.getConstraintsWithoutDisabledInverters()` - [ ] Add unit tests for race condition scenarios
3 条评论