Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Can't catch all errors #1232

Closed
cinderblock opened this issue Sep 8, 2018 · 1 comment
Closed

BUG: Can't catch all errors #1232

cinderblock opened this issue Sep 8, 2018 · 1 comment
Milestone

Comments

@cinderblock
Copy link

There seems to be a bug that prevents catching connection errors that can occur when the server is down or unreachable.

Current behaviour

I am trying to catch all possible errors so that I can display something in the UI if these errors occur and keep my console output clean. Most errors are catchable with one of:

io.on('error', ...);
io.on('connect_error', ...);
io.on('reconnect_error', ...);

However when the host is down completely, there is a TransportError that is detected (as seen by debug mode of socket.io) which does not get passed to any io.on('error', ...) event and always prints to console.

Steps to reproduce

I tried forking the fiddle but to reproduce this error, you need the server not running so it is really not appropriate.

Instead, create this html file on your local machine, open it in a browser, and look at the console output.

test.html

<html>
    <head>
        <title>Socket.IO client error handling test</title>
        <script>
            var printSocketIOdebug = false;
            var printHandledErrors = false;
            var printNoticedEvents = false;

            var oldDebug = localStorage.debug;

            localStorage.debug = printSocketIOdebug ? '*' : undefined;
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.dev.js"></script>
    </head>
    <body></body>
    <script>
        var client = new io({
  transports: ['websocket']
});
        
        function handleEvent(e) {
            if (printHandledErrors) {
                client.on(e, console.log.bind(0, 'Event', e + ':'));
            }
            var name = document.createElement('h3');

            name.innerText = e + ' #';

            var count = document.createElement('span');
            var event = document.createElement('pre');

            count.innerText = 0;

            name.appendChild(count);
            document.body.appendChild(name);
            document.body.appendChild(event);

            client.on(e, function(data) {
                count.innerText++;
                event.innerText = data;
            });
        }

        // Handle events that we know happen
        handleEvent('error');
        handleEvent('connect_error');
        handleEvent('reconnect_error');

        // Print a log of all seen events
        var pre = document.createElement('pre');
        document.body.appendChild(document.createElement('h3')).innerText = 'Noticed events:';
        document.body.appendChild(pre);

        var oldEmit = client.emit;

        client.emit = function() {
            var emitArgs = arguments;
            
            if (printNoticedEvents) {
                console.log('Event noticed:', arguments[0]);
            }

            pre.innerText = arguments[0] + '\n' + pre.innerText;

            oldEmit.apply(client, arguments);
        }

        // Cleanup
        localStorage.debug = oldDebug;
    </script>
</html>

I got a little fancy with my "simple" test case...

Expected behaviour

I expect that socket.on('error', ...) will catch all errors that would otherwise go to the console and the console to be empty.

Setup

  • OS: Windows 10 (should be independent)
  • browser: Chrome 68 (should be independent)
  • socket.io version: 2.1.1

Other information

Output from browser console with Socket.IO debug enabled

socket.io-client:url protocol-less url file:// +0ms
socket.io-client:url parse file://file:// +3ms
socket.io-client new io instance for file://file:// +0ms
socket.io-client:manager readyState closed +0ms
socket.io-client:manager opening file://file:// +0ms
engine.io-client:socket creating transport "polling" +0ms
engine.io-client:polling polling +0ms
engine.io-client:polling-xhr xhr poll +0ms
engine.io-client:polling-xhr xhr open GET: http://file/socket.io/?EIO=3&transport=polling&t=MMxE_QG +1ms
engine.io-client:polling-xhr xhr data null +1ms
engine.io-client:socket setting transport polling +4ms
socket.io-client:manager connect attempt will timeout after 20000 +5ms
socket.io-client:manager readyState opening +1ms
  polling-xhr.js:268 GET http://file/socket.io/?EIO=3&transport=polling&t=MMxE_QG 0 ()
  Request.create @ polling-xhr.js:268
  Request @ polling-xhr.js:170
  XHR.request @ polling-xhr.js:97
  XHR.doPoll @ polling-xhr.js:127
  Polling.poll @ polling.js:123
  Polling.doOpen @ polling.js:68
  Transport.open @ transport.js:84
  Socket.open @ socket.js:250
  Socket @ socket.js:124
  Socket @ socket.js:33
  Manager.open.Manager.connect @ manager.js:230
  Manager @ manager.js:72
  Manager @ manager.js:42
  lookup @ index.js:65
  (anonymous) @ test-socket.io-client.html:16
engine.io-client:socket socket error {"type":"TransportError","description":0} +2s
...

Indented section is the stack trace that I can't get rid of and is the error I'd like to capture

List of events during startup (in reverse order)

...
reconnect_attempt
reconnect_error
connect_error
reconnecting
reconnect_attempt
reconnect_error
connect_error
reconnecting
reconnect_attempt
reconnect_error
connect_error
reconnecting
reconnect_attempt
reconnect_error
connect_error
reconnecting
reconnect_attempt
connect_error

Likely cause

Stack trace points to:

  } catch (e) {
    // Need to defer since .create() is called directly fhrom the constructor
    // and thus the 'error' event can only be only bound *after* this exception
    // occurs.  Therefore, also, we cannot throw here at all.
    setTimeout(function () {       // ### STACK TRACE POINTS HERE
      self.onError(e);
    }, 0);
    return;
  }

However I presume it is actually the self.onError(e); that is bubbling up in some way that I'm not able to catch.

Disabling polling transport

If I disable the polling transport, the error becomes a websocket error: failed: Error in connection establishment: net::ERR_NAME_NOT_RESOLVED or failed: Error during WebSocket handshake: Unexpected response code: 502. These are the expected errors for different test cases. The problem is still that I can't seem to catch the errors.

Other thoughts

The distinction between a connect_error and a reconnect_error is rather subtle. I feel this could be simplified to just one of the two and a parameter that specifies that attempt number. 0 would be the first attempt.

@darrachequesne
Copy link
Member

For future readers:

This should be fixed in Socket.IO v3. You can now use connect_error to catch any error during the connection:

socket.on("connect_error", (err) => {
  // ...
});

Reference:

Next release will also include additional details for the error: socketio/engine.io-client@b9252e2

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

No branches or pull requests

2 participants