fix(ui-server): make UI broadcast-channel commands complete reliably (#2018) (#2020)
* fix(ui-server): time out broadcast-channel requests so UI commands cannot hang
A UI control command (start/stop/delete a station) is dispatched over the
worker broadcast channel and the UI service waits for a fixed number of
worker responses, sampled at send time. If a targeted worker never replies
-- e.g. the station is deleted while the command is in flight -- that count
is never reached, so the request is never completed or released and the
client waits forever. Read commands keep working, which masks the wedge.
Arm a per-request safety-net timeout when a broadcast-channel request is
sent. On expiry the request is completed with a failure (reporting the
charging stations that did reply successfully) and both the request and
response aggregation state are released, so the caller gets an answer
instead of hanging. The timeout is cleared on normal completion, on a
dispatch failure, and on service stop.
Closes #2018.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel <7558512+DerGenaue@users.noreply.github.com>
* fix(ui-server): complete broadcast requests by target set and reconcile on station deletion (#2018)
Broadcast-channel request completion was gated on a frozen integer count
sampled at send time, blind to which stations were targeted. When the set
of stations changed mid-flight the aggregator could not reconcile the
drift, producing failure modes on top of the hang the safety-net timeout
already backstops:
- Hang until timeout when a targeted station is deleted mid-flight (the
count is never reached).
- Late double-completion: after release the count reads 0, so a late reply
re-enters and 1 >= 0 re-completes the request.
- False success: an empty explicit hashIds array degraded to a
broadcast-to-all instead of failing.
Track the outstanding responders as a Set of target hashIds and make the
Set the single source of truth for completion:
1. Snapshot the resolved targets (validated explicit hashIds, or the live
station set for a broadcast) into the request context; complete when the
Set empties. AbstractUIServer.deleteChargingStationData fans out
AbstractUIService.reconcileDeletedStation, which drops the departed
hashId from every in-flight request and completes any that empty with
the truthful aggregated payload. A DELETE_CHARGING_STATIONS request is
left untouched by reconciliation: its targets self-delete yet still post
their command reply on a separate transport, so that reply (not the
racing `deleted` event) is the completion source; a worker that dies
mid-delete is covered by the timeout.
2. Drop untracked responses (unknown/released request, or a hashId that is
not an outstanding responder) in the response handler, closing the late
double-completion re-entry. A reply without a hashId for a still-tracked
request is dropped and logged distinctly so the resulting timeout is
diagnosable.
3. Reject unknown and empty explicit targets instead of degrading to a
broadcast-to-all.
The 60 s safety-net timeout stays as a pure backstop for genuine worker
crash/deadlock; normal and reconciled completion clear its timer. The
completion accessor is named getBroadcastChannelOutstandingResponseCount to
reflect that it returns the responses still outstanding, not a frozen total.
Duplicate-identity false success and orphan-worker termination are not
addressed here (a hashId-keyed set cannot disambiguate two workers sharing
an identity) and are deferred to follow-up changes. The deprecated HTTP
transport bypasses aggregation and is likewise out of scope.
Tests cover reconcile-on-delete, the untracked-response guard, unknown and
empty target rejection, the DELETE self-target ordering (reconcile-first
completes via the reply; no reply falls back to the timeout), reconcile
that does not empty the set, two concurrent requests reconciled by one
deletion, a hashId-less reply, and the previously untested timeout
behaviors (partial-success payload, dispatch-failure and stop() timer
clearing, and the completion-race guard).
Closes #2018.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
---------
Signed-off-by: Daniel <7558512+DerGenaue@users.noreply.github.com> Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com>