Skip to content

Fix WebSocket leak in streamLogs and logs target dialog removeChild error#869

Merged
balloob merged 2 commits intomainfrom
logs_fix
Feb 8, 2026
Merged

Fix WebSocket leak in streamLogs and logs target dialog removeChild error#869
balloob merged 2 commits intomainfrom
logs_fix

Conversation

@bdraco
Copy link
Copy Markdown
Member

@bdraco bdraco commented Feb 4, 2026

Summary

WebSocket leak in streamLogs

streamLogs never closed the WebSocket after receiving the exit event from the server. The promise resolved/rejected, but the socket stayed open as a zombie connection. This affected all callers that didn't pass an AbortController — notably compileConfigurationMetadata, which is called by the MetadataRefresher for every config with missing metadata (loaded_integrations is empty).

The MetadataRefresher queues 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.

removeChild error in logs-target-dialog

When no serial ports are available and WebSerial isn't supported, _handleClose() is called directly to auto-select the OTA option. The mwc-dialog's @closed event 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.

Comment thread src/logs-target/logs-target-dialog.ts
@bdraco bdraco changed the title Fix logs target dialog removeChild error Fix WebSocket leak in streamLogs and logs target dialog removeChild error Feb 7, 2026
@bdraco
Copy link
Copy Markdown
Member Author

bdraco commented Feb 7, 2026

server side leaks as well

def _proc_on_exit(self, returncode: int) -> None:
if not self._is_closed:
_LOGGER.info("Process exited with return code %s", returncode)
self.write_message({"event": "exit", "code": returncode})

no close

@balloob balloob merged commit 36bd3fe into main Feb 8, 2026
3 checks passed
@balloob balloob deleted the logs_fix branch February 8, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants