Skip to content

Commit

Permalink
Upgrading to our new websocket lib that have major perf benfits!
Browse files Browse the repository at this point in the history
  • Loading branch information
QuinnDamerell committed Sep 6, 2024
1 parent 4d4b053 commit de6cd72
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
"octosessionimpl",
"octostream",
"octostreammsgbuilder",
"octowebsocket",
"octowebstream",
"octowebstreamhttphelper",
"octowebstreamhttphelperimpl",
Expand Down Expand Up @@ -215,6 +216,7 @@
"rtsps",
"sdcard",
"serverauth",
"setdefaulttimeout",
"shotty",
"skipsudoactions",
"smartpause",
Expand Down
4 changes: 2 additions & 2 deletions moonraker_octoeverywhere/moonrakerclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import math
import configparser

import websocket
import octowebsocket

from octoeverywhere.compat import Compat
from octoeverywhere.sentry import Sentry
Expand Down Expand Up @@ -749,7 +749,7 @@ def _onWsError(self, ws, exception):
if Client.IsCommonConnectionException(exception):
# Don't bother logging, this just means there's no server to connect to.
pass
elif isinstance(exception, websocket.WebSocketBadStatusException) and "Handshake status" in str(exception):
elif isinstance(exception, octowebsocket.WebSocketBadStatusException) and "Handshake status" in str(exception):
# This is moonraker specific, we sometimes see stuff like "Handshake status 502 Bad Gateway"
self.Logger.info(f"Failed to connect to moonraker due to bad gateway stats. {exception}")
else:
Expand Down
12 changes: 6 additions & 6 deletions octoeverywhere/WebStream/octowebstreamwshelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time
import threading

import websocket
import octowebsocket

from ..mdns import MDns
from ..sentry import Sentry
Expand Down Expand Up @@ -283,11 +283,11 @@ def IncomingServerMessage(self, webStreamMsg:WebStreamMsg.WebStreamMsg):
sendType = 0
msgType = webStreamMsg.WebsocketDataType()
if msgType == WebSocketDataTypes.WebSocketDataTypes.Text:
sendType = websocket.ABNF.OPCODE_TEXT
sendType = octowebsocket.ABNF.OPCODE_TEXT
elif msgType == WebSocketDataTypes.WebSocketDataTypes.Binary:
sendType = websocket.ABNF.OPCODE_BINARY
sendType = octowebsocket.ABNF.OPCODE_BINARY
elif msgType == WebSocketDataTypes.WebSocketDataTypes.Close:
sendType = websocket.ABNF.OPCODE_CLOSE
sendType = octowebsocket.ABNF.OPCODE_CLOSE
else:
raise Exception("Web stream ws was sent a data type that's unknown. "+str(msgType))

Expand Down Expand Up @@ -320,9 +320,9 @@ def onWsData(self, ws, buffer:bytes, msgType):
# Figure out the data type
# TODO - we should support the OPCODE_CONT type at some point. But it's not needed right now.
sendType = WebSocketDataTypes.WebSocketDataTypes.None_
if msgType == websocket.ABNF.OPCODE_BINARY:
if msgType == octowebsocket.ABNF.OPCODE_BINARY:
sendType = WebSocketDataTypes.WebSocketDataTypes.Binary
elif msgType == websocket.ABNF.OPCODE_TEXT:
elif msgType == octowebsocket.ABNF.OPCODE_TEXT:
sendType = WebSocketDataTypes.WebSocketDataTypes.Text
# In PY3 using the modern websocket_client lib the text also comes as a byte buffer.
else:
Expand Down
25 changes: 17 additions & 8 deletions octoeverywhere/websocketimpl.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import queue
import threading
import certifi
import websocket
from websocket import WebSocketApp
import octowebsocket
from octowebsocket import WebSocketApp

from .sentry import Sentry

Expand All @@ -11,6 +11,12 @@ class Client:

def __init__(self, url, onWsOpen = None, onWsMsg = None, onWsData = None, onWsClose = None, onWsError = None, headers:dict = None, subProtocolList:list = None):

# Set the default timeout for the socket. There's no other way to do this than this global var, and it will be shared by all websockets.
# This is used when the system is writing or receiving, but not when it's waiting to receive, as that's a select()
# We set it to be something high because most all errors will be handled other ways, but this prevents the websocket from hanging forever.
# The value is in seconds, we currently set it to 10 minutes.
octowebsocket.setdefaulttimeout(10 * 60)

# Since we also fire onWsError if there is a send error, we need to capture
# the callback and have some vars to ensure it only gets fired once.
self.clientWsErrorCallback = onWsError
Expand Down Expand Up @@ -185,9 +191,9 @@ def fireWsErrorCallbackThread(self, exception):

def Send(self, msgBytes, isData):
if isData:
self.SendWithOptCode(msgBytes, websocket.ABNF.OPCODE_BINARY)
self.SendWithOptCode(msgBytes, octowebsocket.ABNF.OPCODE_BINARY)
else:
self.SendWithOptCode(msgBytes, websocket.ABNF.OPCODE_TEXT)
self.SendWithOptCode(msgBytes, octowebsocket.ABNF.OPCODE_TEXT)


def SendWithOptCode(self, msgBytes, opcode):
Expand All @@ -211,7 +217,10 @@ def _SendQueueThread(self):
if context is None or context.Buffer is None:
return
# Send it!
self.Ws.send(context.Buffer, context.OptCode)
# Important! We don't want to use the frame mask because it adds about 30% CPU usage on low end devices.
# The frame masking was only need back when websockets were used over the internet without SSL.
# Our server, OctoPrint, and Moonraker all accept unmasked frames, so its safe to do this for all WS.
self.Ws.send(context.Buffer, context.OptCode, False)
except Exception as e:
# If any exception happens during sending, we want to report the error
# and shutdown the entire websocket.
Expand Down Expand Up @@ -259,15 +268,15 @@ def IsCommonConnectionException(e:Exception):
# This means the other side never responded.
if isinstance(e, TimeoutError) and "Connection timed out" in str(e):
return True
if isinstance(e, websocket.WebSocketTimeoutException):
if isinstance(e, octowebsocket.WebSocketTimeoutException):
return True
# This just means the server closed the socket,
# or the socket connection was lost after a long delay
# or there was a DNS name resolve failure.
if isinstance(e, websocket.WebSocketConnectionClosedException) and ("Connection to remote host was lost." in str(e) or "ping/pong timed out" in str(e) or "Name or service not known" in str(e)):
if isinstance(e, octowebsocket.WebSocketConnectionClosedException) and ("Connection to remote host was lost." in str(e) or "ping/pong timed out" in str(e) or "Name or service not known" in str(e)):
return True
# Invalid host name.
if isinstance(e, websocket.WebSocketAddressException) and "Name or service not known" in str(e):
if isinstance(e, octowebsocket.WebSocketAddressException) and "Name or service not known" in str(e):
return True
# We don't care.
if isinstance(e. WebSocketConnectionClosedException):
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#
# For comments on package lock versions, see the comments in the setup.py file.
#
websocket_client>=1.6.0,<1.7.99
octowebsocket_client==1.8.2
requests>=2.31.0
octoflatbuffers==24.3.27
pillow
Expand Down
15 changes: 7 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
# Note that this single version string is used by all of the plugins in OctoEverywhere!
plugin_version = "3.6.0"
plugin_version = "3.6.1"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
Expand All @@ -52,12 +52,11 @@
#
# On 4/13/2023 we updated to only support PY3, which frees us up from a lot of package issues. A lot the packages we depend on only support PY3 now.
#
# websocket_client
# For the websocket_client, some older versions seem to have a thread issue that causes the 24 hour disconnect logic to fail, and eventually makes the thread limit get hit.
# Version 1.4.0 also has an SSL error in it. https://github.com/websocket-client/websocket-client/issues/857
# Update: We also found a bug where the ping timer doesn't get cleaned up: https://github.com/websocket-client/websocket-client/pull/918
# Thus we need version 1.6.0 or higher.
# The sonic pad runs python 3.7.8 as of 12/18/2023 and websocket_client>=1.7 doesn't support it. So we must keep our version at 1.6 at least for now.
# octowebsocket_client
# We forked this package so we could add a flag to disable websocket frame masking when sending messages, which got us a 30% CPU reduction.
# For a full list of changes, reasons, and version details, see the repo readme.md
# For the source lib, we must be on version 1.6 due to a bug before that version.
# We also must remain compatible with Python 3.7 for the Sonic pad. For now we are pulling the latest changes and fixing any 3.7 issues.
# dnspython
# We depend on a feature that was released with 2.3.0, so we need to require at least that.
# For the same reason as websocket_client for the sonic pad, we also need to include at least 2.3.0, since 2.3.0 is the last version to support python 3.7.8.
Expand All @@ -78,7 +77,7 @@
#
# Note! These also need to stay in sync with requirements.txt, for the most part they should be the exact same!
plugin_requires = [
"websocket_client>=1.6.0,<1.7.99",
"octowebsocket_client==1.8.2",
"requests>=2.31.0",
"octoflatbuffers==24.3.27",
"pillow",
Expand Down

0 comments on commit de6cd72

Please sign in to comment.