[Bug] EssPower circular dependency fix from #3113 needs to be applied to all ESS implementations
# [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 条评论