Repository navigation
Conversation
A socket error or close during a subscribe left the pending reply state set, so every later subscribe was rejected before anything was sent. Events only came back after a page reload. - Clear the pending subscribe reply on an unexpected disconnect - Stop reusing a rejected shared asset or attribute subscription, and remove the callback of the failed call - Detach and close the previous socket before opening a new one - Restore subscriptions after a reconnect - Let unsubscribe cancel a subscription that is still being restored - Add component tests and npmTest for the core package
wborn
left a comment
There was a problem hiding this comment.
This is an initial AI-assisted review.
The changes address the reported WebSocket subscription issues in a straightforward way:
- Clearing the pending subscription state on disconnect prevents subscriptions from becoming permanently stuck.
- Detaching handlers and closing replaced WebSockets prevents stale socket events from interfering with the active connection.
- Correcting the
Mapiteration restores existing subscriptions after reconnecting. - Handling cancellation during subscription restoration prevents obsolete subscriptions from remaining active.
The new regression tests cover the main failure scenarios and several important reconnect and cancellation edge cases. The relevant CI checks are passing.
One minor edge case remains around cancelling an initial queued subscription, but this does not appear to affect existing consumers and should not block merging.
No blocking issues were identified. Manual maintainer review is still needed before merge.
|
I also made a refactored implementation of The reduction is |
Unsubscribing a subscription the server hadn't confirmed yet left its subscribe promise pending. It now resolves with the ID, as it already did when the reply arrived after the unsubscribe.
|



Description
Events could stop until the page was reloaded after a connection drop. The event provider reconnected and reported
CONNECTED, but every subscribe failed withThere's already a pending subscriptionand nothing was sent to the server.Cause: a socket
errororclosewhile a subscribe was waiting for its reply left_subscribeDeferredset, because_beforeDisconnectdidn't clear it. Only a deliberate_doDisconnectdid. The interrupted subscribe was queued again, but_doSubscriberejected it and every later subscribe before sending a frame. The rejected_attributeEventPromiseor_assetEventPromisethen stayed cached, so later calls got the same rejection.Two related problems:
closechanged the state of the current connection, because itsonclosehandler works on the provider's shared state andnew WebSocketreplaced the socket without closing the old one. This could fail a reconnect in progress, trigger the stuck state above, and leave an extra socket open._onConnectusedObject.keys()andfor...inon aMap, which see no entries, so subscriptions were never restored after a reconnect. This is the cause of Bugreport: Map-view never receives AttributeEvents automatically anymore after being once offline #1200: the map page subscribes only once, so it stopped updating.Evidence: confirmed through Web Inspector on an iPhone, in the app using
core1.28.1, whoseevent.tsbehaves the same asmain. The stuck provider wasCONNECTEDwith_subscribeDeferredset and_pendingSubscriptionnull, and it had two open sockets, only one of them held by the provider. A fresh subscribe was rejected with no frames sent. Clearing_subscribeDeferredand_attributeEventPromiseby hand brought events back without a reload. In the manager, the reproduction below makes creating an asset hang, withUncaught (in promise) There's already a pending subscriptionin the console.Evidence for #1200: reproduced 3 times on a staging server without this PR, each time after the connection dropped. The provider reconnected and reported
CONNECTED, but the new connection showed no frames at all. After a forced reconnect, none of the 3 subscriptions the page held was sent again. Queuing them again by hand brought events back without a reload.Behavior change for consumers: subscriptions now survive a reconnect and keep their ID, so a consumer that holds on to its subscription keeps getting events. A consumer that resubscribes on reconnect still ends with one server subscription, but may send an extra
SUBSCRIBEandUNSUBSCRIBE.unsubscribenow also covers a subscription still being restored, otherwise that consumer would end up with two subscriptions and receive every event twice. Components using thesubscribemixin unsubscribe onDISCONNECTEDand subscribe again onCONNECTED. I traced that flow through the code, but no test covers it.Testing
The 11 new tests use a fake socket, so each test controls the event order. Removing any single change makes at least one test fail.
The issue is confirmed resolved on a test server running this branch, and in the app on 1.28.1 with the fix backported, in Chromium and Firefox.
How the fix was manually tested on the app
Get 1.28.1 with the fix
Build the package
Writes to
ui/component/core/openremote-core-1.28.1-event-provider-fix.tgz.Build the app with it
package.jsonresolutions, set:"@openremote/core": "file:./openremote-core-1.28.1-event-provider-fix.tgz"yarn install, then./gradlew clean installDist. The bundle ends up atdeployment/build/image/manager/app/<app>/bundle.*.js.Serve it over the deployed app
Chromium:
<app>/bundle.<deployed hash>.jsand choose Override content. DevTools creates<folder>/<host>/<app>/bundle.<deployed hash>.js.cp deployment/build/image/manager/app/<app>/bundle.*.js <folder>/<host>/<app>/bundle.<deployed hash>.jstypeof __p._discardWebSocketgives 'function'.Firefox: in Debugger, right-click the bundle, choose Add script override and save it. Copy your build over the saved file, then reload with the cache disabled.
Reproduction
These steps reproduce the failing subscribes. They work in any browser and in any app built on core, including the manager. The steps for #1200 are in this issue comment.
Found the event provider.to be logged).Shorter Safari only alternative
In Safari's Web Inspector, including iOS web views, this finds the event provider straight away, with no tab switch needed:
There's already a pending subscriptionChangelog
_subscribeDeferredon an unexpected disconnect, so the interrupted subscribe goes out again after the reconnectunsubscribecancel a subscription that is still being restored, once its reply arrives or when the connection drops againnpmTestfor the core packageChecklist