Skip to content

Conversation

Joy-chakraborty-23
Copy link

@Joy-chakraborty-23 Joy-chakraborty-23 commented Aug 8, 2025

PR Description:

Issue: Firefox crashes the Visdom server due to unhandled WebSocket disconnections (#942).

Root Cause:

  • The SocketHandler and VisSocketHandler in socket_handlers.py lack error handling for abrupt WebSocket closures (common in Firefox).

Changes:

  1. Added try-catch blocks in socket_handlers.py to:
    • Gracefully log WebSocket errors.
    • Prevent server crashes on disconnections.
  2. Verified fixes with Firefox (abrupt refresh/network drops).

Impact:

  • Server stability improved for Firefox users.
  • No changes to existing functionality; only error handling added.

Steps to Test (Offline Workflow):

  1. Download Visdom and comment out main() in run_server.py:
    def download_scripts_and_run():
        download_scripts()
        # main()  # Commented for manual execution
  2. Install in editable mode:
    pip install -e visdom.tar.gz
  3. Manually run python run_server.py after patching.

Files Modified:

modified: visdom/server/handlers/socket_handlers.py

Key Technical Fixes :

  1. Where: Patched SocketHandler and VisSocketHandler in socket_handlers.py.
  2. What:
    • Wrapped on_message(), open(), and on_close() with error logging.
    • Added self.close() on exceptions to prevent orphaned sockets.
  3. Why: Firefox aggressively closes WebSockets, triggering uncaught exceptions in Tornado.

Summary by Sourcery

Add error handling to WebSocket handlers to prevent server crashes on abrupt disconnects and improve the line plotting API by sanitizing NaN values and refining the data payload.

Bug Fixes:

  • Prevent server crashes on abrupt Firefox WebSocket disconnections by wrapping open, message, and close methods with error logging and graceful socket closure.

Enhancements:

  • Mask invalid (NaN) values in the line plot API to ensure clean numeric data.
  • Generate unique window IDs and adjust the line plot payload structure when sending data.

Uses numpy.ma.masked_invalid to ignore NaN values.

Alternatively, np.nan_to_num replaces NaN with a default (e.g., 0).
Copy link

sourcery-ai bot commented Aug 8, 2025

Reviewer's Guide

This PR enhances server robustness by adding comprehensive error handling to WebSocket handlers—wrapping open, message, and close events in try/except blocks with logging and graceful socket closure—and extends the line plotting API to sanitize input data via NumPy conversion, NaN masking, and updated send logic with dynamic window naming.

Sequence diagram for improved WebSocket error handling in SocketHandler

sequenceDiagram
participant Client
participant SocketHandler
participant Server
Client->>SocketHandler: Open WebSocket
SocketHandler->>Server: Register client (try/catch)
Note over SocketHandler: Logs error if exception
Client->>SocketHandler: Send message
SocketHandler->>Server: Handle message (try/catch)
Note over SocketHandler: Logs error, closes socket on exception
Client-->>SocketHandler: Abrupt disconnect
SocketHandler->>Server: Cleanup (try/catch)
Note over SocketHandler: Logs closure event
Loading

Class diagram for updated SocketHandler error handling

classDiagram
class SocketHandler {
  +check_origin(origin)
  +open()
  +on_message(message)
  +on_close()
}
SocketHandler : +try/catch in open()
SocketHandler : +try/catch in on_message()
SocketHandler : +try/catch in on_close()
SocketHandler : +logging
SocketHandler : +self.close() on error
Loading

File-Level Changes

Change Details Files
Introduce robust error handling in WebSocket handlers
  • Override check_origin to allow all connections
  • Wrap open() logic in try/except and log errors
  • Wrap on_message() in try/except, log exceptions, and close socket on failure
  • Wrap on_close() with logging for orderly shutdown
py/visdom/server/app.py
Sanitize and reshape data in line plotting function
  • Import numpy and convert Y input to np.array
  • Mask invalid (NaN) entries using np.ma.masked_invalid
  • Generate dynamic window name based on env and index
  • Use updated _send call to transmit cleaned data and metadata
py/visdom/__init__.py

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Joy-chakraborty-23 - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Allowing all origins in check_origin may introduce security risks. (link)
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `py/visdom/__init__.py:1815` </location>
<code_context>
             labels = np.tile(labels, (Y.shape[0], 1)).ravel(order="F")

+        win = 'line_' + env + str(idx)
+        return _send({
+        'data': Y.tolist(),  # Send cleaned data
+        'win': win,
+        'eid': env,
+        'layout': layout,
+        'opts': opts})
+
         return self.scatter(
</code_context>

<issue_to_address>
Early return from the function bypasses the original scatter logic.

If the early return is intentional, remove the unreachable scatter logic below. Otherwise, clarify when each path should be used.
</issue_to_address>

### Comment 2
<location> `py/visdom/server/app.py:221` </location>
<code_context>

         return settings
+class SocketHandler(tornado.websocket.WebSocketHandler):
+    def check_origin(self, origin):
+        return True  # Or customize for security
+
+    def open(self):
</code_context>

<issue_to_address>
Allowing all origins in check_origin may introduce security risks.

Disabling origin checks exposes the WebSocket to CSRF and similar attacks. Restrict allowed origins or make this configurable for production.
</issue_to_address>

### Comment 3
<location> `py/visdom/server/app.py:231` </location>
<code_context>
+        except Exception as e:
+            logging.error(f"WebSocket open error: {e}")
+
+    def on_message(self, message):
+        try:
+            # Existing message handling logic
+            pass
+        except Exception as e:
+            logging.error(f"WebSocket message error: {e}")
+            self.close()  # Gracefully close on error
+
+    def on_close(self):
</code_context>

<issue_to_address>
Closing the WebSocket on any message error may be overly aggressive.

Consider handling exceptions more selectively, logging non-critical errors, and only closing the connection for serious issues.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    def on_message(self, message):
        try:
            # Existing message handling logic
            pass
        except Exception as e:
            logging.error(f"WebSocket message error: {e}")
            self.close()  # Gracefully close on error
=======
    class CriticalWebSocketError(Exception):
        """Exception for critical WebSocket errors that require closing the connection."""
        pass

    def on_message(self, message):
        try:
            # Existing message handling logic
            pass
        except CriticalWebSocketError as e:
            logging.error(f"Critical WebSocket message error: {e}")
            self.close()  # Gracefully close on critical error
        except Exception as e:
            logging.warning(f"Non-critical WebSocket message error: {e}")
            # Optionally, send an error message to the client or take other action
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1815 to +1820
return _send({
'data': Y.tolist(), # Send cleaned data
'win': win,
'eid': env,
'layout': layout,
'opts': opts})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Early return from the function bypasses the original scatter logic.

If the early return is intentional, remove the unreachable scatter logic below. Otherwise, clarify when each path should be used.

Comment on lines +221 to +222
def check_origin(self, origin):
return True # Or customize for security
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Allowing all origins in check_origin may introduce security risks.

Disabling origin checks exposes the WebSocket to CSRF and similar attacks. Restrict allowed origins or make this configurable for production.

Comment on lines +231 to +237
def on_message(self, message):
try:
# Existing message handling logic
pass
except Exception as e:
logging.error(f"WebSocket message error: {e}")
self.close() # Gracefully close on error
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Closing the WebSocket on any message error may be overly aggressive.

Consider handling exceptions more selectively, logging non-critical errors, and only closing the connection for serious issues.

Suggested change
def on_message(self, message):
try:
# Existing message handling logic
pass
except Exception as e:
logging.error(f"WebSocket message error: {e}")
self.close() # Gracefully close on error
class CriticalWebSocketError(Exception):
"""Exception for critical WebSocket errors that require closing the connection."""
pass
def on_message(self, message):
try:
# Existing message handling logic
pass
except CriticalWebSocketError as e:
logging.error(f"Critical WebSocket message error: {e}")
self.close() # Gracefully close on critical error
except Exception as e:
logging.warning(f"Non-critical WebSocket message error: {e}")
# Optionally, send an error message to the client or take other action

@mariobehling
Copy link
Member

@Joy-chakraborty-23 Thanks for looking into this. Please respond to sourcery-ai.

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