Skip to content

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

@cvabc

Description

@cvabc

[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:

// 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:

// 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:

// 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():

@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)

// Data.java - make constraint retrieval synchronized
public synchronized List<Constraint> getConstraintsWithoutDisabledInverters(
        Collection<Inverter> disabledInverters) throws OpenemsException {
    // existing code
}

Environment

Related

Checklist

  • Apply fix to all ESS implementations listed above
  • Consider adding synchronization to Data.getConstraintsWithoutDisabledInverters()
  • Add unit tests for race condition scenarios

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions