Conversation
balloob
reviewed
Feb 7, 2026
Member
Author
|
server side leaks as well def _proc_on_exit(self, returncode: int) -> None: no close |
Merged
17 tasks
balloob
approved these changes
Feb 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WebSocket leak in
streamLogsstreamLogsnever closed the WebSocket after receiving theexitevent from the server. The promise resolved/rejected, but the socket stayed open as a zombie connection. This affected all callers that didn't pass anAbortController— notablycompileConfigurationMetadata, which is called by theMetadataRefresherfor every config with missing metadata (loaded_integrationsis empty).The
MetadataRefresherqueues configs for compilation each time the device list subscriber fires. On dashboards with configs that fail to compile (e.g. missing secrets), each failed compile left a zombie WebSocket open. Scrolling down the device list renders more cards, which triggers the subscriber callback and queues more broken configs — each one leaking another WebSocket. Eventually the server's WebSocket capacity is exhausted and all WebSocket-based features (logs, compiles) stop working.This may also contribute to user reports of "update all" stopping after a specific number of devices, if the dashboard has enough configs with missing metadata to exhaust the server's WebSocket capacity.
This bug was hard to find because it required a combination of conditions: a dashboard with many configs that fail to compile, and scrolling far enough through the device list to leak enough sockets to exhaust the server. Refreshing the page appeared to fix it because the zombie sockets were cleared, and the user hadn't yet scrolled far enough to re-trigger enough leaks.
Fixed by calling
socket.close()after the exit event and guarding the close handler to only reject on unexpected closures.removeChilderror in logs-target-dialogWhen no serial ports are available and WebSerial isn't supported,
_handleClose()is called directly to auto-select the OTA option. The mwc-dialog's@closedevent then fires and calls_handleClose()again, but by this point the element has already been removed from the DOM.Using optional chaining makes the method idempotent.