Skip to content

Commit c17bd6c

Browse files
committed
Fix to ensure all event listeners are stopped when disconnected from SD websocket server
1 parent 363c6e5 commit c17bd6c

File tree

3 files changed

+29
-10
lines changed

3 files changed

+29
-10
lines changed

streamdeck/event_listener.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ def is_sentinal(cls, event: str | bytes | type[_SENTINAL]) -> TypeIs[type[_SENTI
3131
return event is cls
3232

3333

34+
class StopStreaming(Exception): # noqa: N818
35+
"""Raised by an EventListener implementation to signal that the entire EventManagerListener should stop streaming events."""
36+
37+
3438
class EventListenerManager:
3539
"""Manages event listeners and provides a shared event queue for them to push events into.
3640
@@ -65,8 +69,12 @@ def _listener_wrapper(self, listener: EventListener) -> None:
6569
if not self.running:
6670
break
6771

72+
except StopStreaming:
73+
logger.debug("Event listener requested to stop streaming.")
74+
self.event_queue.put(_SENTINAL)
75+
6876
except Exception:
69-
logger.exception("Error in wrapped listener.")
77+
logger.exception("Unexpected error in wrapped listener %s. Stopping just this listener.", listener)
7078

7179
def stop(self) -> None:
7280
"""Stops the event generation loop and waits for all threads to finish.
@@ -79,10 +87,12 @@ def stop(self) -> None:
7987
self.event_queue.put(_SENTINAL)
8088

8189
for thread in self.listeners_lookup_by_thread:
82-
self.listeners_lookup_by_thread[thread].stop()
83-
thread.join()
90+
logger.debug("Stopping listener %s.")
91+
if thread.is_alive():
92+
self.listeners_lookup_by_thread[thread].stop()
93+
thread.join()
8494

85-
logger.info("All listeners have been stopped.")
95+
logger.info("All listeners have been stopped.")
8696

8797
def event_stream(self) -> Generator[str | bytes, None, None]:
8898
"""Starts all registered listeners, sets the running flag to True, and yields events from the shared queue."""
@@ -96,6 +106,7 @@ def event_stream(self) -> Generator[str | bytes, None, None]:
96106
while True:
97107
event = self.event_queue.get()
98108
if _SENTINAL.is_sentinal(event):
109+
logger.debug("Sentinal received, stopping event stream.")
99110
break # Exit loop immediately if the sentinal is received
100111
yield event
101112
finally:

streamdeck/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,4 @@ def run(self) -> None:
166166

167167
processed_handler(data)
168168

169-
169+
logger.info("PluginManager has stopped processing events.")

streamdeck/websocket.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
from logging import getLogger
99
from typing import TYPE_CHECKING
1010

11+
from websockets import ConnectionClosedError, WebSocketException
1112
from websockets.exceptions import ConnectionClosed, ConnectionClosedOK
1213
from websockets.sync.client import ClientConnection, connect
1314

14-
from streamdeck.event_listener import EventListener
15+
from streamdeck.event_listener import EventListener, StopStreaming
1516
from streamdeck.models import events
1617

1718

@@ -72,14 +73,21 @@ def listen(self) -> Generator[str | bytes, Any, None]:
7273
message: str | bytes = self._client.recv()
7374
yield message
7475

75-
except ConnectionClosedOK:
76-
logger.debug("Connection was closed normally, stopping the client.")
76+
except WebSocketException as exc:
77+
if isinstance(exc, ConnectionClosedOK):
78+
logger.debug("Connection was closed normally, stopping the client.")
79+
elif isinstance(exc, ConnectionClosedError):
80+
logger.exception("Connection was terminated with an error.")
81+
elif isinstance(exc, ConnectionClosed):
82+
logger.exception("Connection was already closed.")
83+
else:
84+
logger.exception("Connection is closed due to an unexpected WebSocket error.")
7785

78-
except ConnectionClosed:
79-
logger.exception("Connection was closed with an error.")
86+
raise StopStreaming from None
8087

8188
except Exception:
8289
logger.exception("Failed to receive messages from websocket server due to unexpected error.")
90+
raise
8391

8492
def start(self) -> None:
8593
"""Start the connection to the websocket server."""

0 commit comments

Comments
 (0)