Ws: Second connection does not always fire the open event.

Created on 9 Jul 2019  路  15Comments  路  Source: websockets/ws

  • [x] I've searched for any related issues and avoided creating a duplicate
    issue.

Description

I have two (almost identical) connections going to the same host/port but with different tokens in the URL. When using both together, the second connection does not always fire the "open" event. I use a time-out workaround to fix the issue. The second connection does actually open, as we can see it closes if no ping event is received after 1 min of inactivity.

Reproducible in:

  • version: ^6.1.4
  • Node.js version(s): v11.0.0
  • OS version(s): OSX 10.14 (Mojave)

Steps to reproduce:


init/connect sockets

let wsPrivate = new WebSocket(websocketPrivate, { 'force new connection': true } )
let wsPublic = new WebSocket(websocketPublic, { 'force new connection': true })

await setupWebsocket(wsPrivate, 'private')
await setupWebsocket(wsPublic, 'public')


setupWebsocket

function setupWebsocket(ws, type) {
  return new Promise((resolve, reject) => {
    console.log('Setting up websocket events for', type, 'channel')

    Sockets.ws[type] = ws
    let handledResolve = false

    // This timeout recovers the connection just fine, telling me that the "open" event doesn't always fire.
    setTimeout(() => {
      if (!handledResolve && ws.readyState === ws.OPEN) {
        console.log('Recovered ws connection')

        console.log('Opened websocket connection for', type, 'channel')

        resolve()
      }
    }, 5000)

    ws.on('open', () => {
      handledResolve = true
      console.log('Opened websocket connection for', type, 'channel')

      resolve()
    })

    ws.on('error', (error) => {
      Sockets.handleSocketError(error)
      console.log(error)
      reject()
    })

    ws.on('ping', () => {

    })

    ws.on('close', () => {
      console.log(type + ' websocket closed...')
    })
  })
}

Output:

Sometimes "open" fires:

Setting up websocket events for private channel
Opened websocket connection for private channel
Setting up websocket events for public channel
Opened websocket connection for public channel

Other times, it doesn't fire:

Setting up websocket events for private channel
Opened websocket connection for private channel
Setting up websocket events for public channel
Recovered ws connection
Opened websocket connection for public channel

Expected result:

For both "open" events to fire

Actual result:

Only the first "open" event fires, but is recoverable using a timeout.

All 15 comments

Remove all your code. Use only ws API. Does this work?

const ws1 = new WebSocket(urlWithToken1);

ws1.on('open', function() {
  console.log('ws1 open');
});

const ws2 = new WebSocket(urlWithToken2);

ws2.on('open', function() {
  console.log('ws2 open');
});

Also show how the server part.

There is no 'force new connection' option in ws.

Remove all your code. Use only ws API. Does this work?

Strange, this seems to be working. Could this be because of the delay of setting up the Promises? There is no other code between the await setupWebsocket and the above listed function.

My thought is that the connection is already established before the event hooks are set up. Could this be a problem?

Using the debugger I do see the ws.on('open') getting executed, but the callback is never fired.

There is no 'force new connection' option in ws.

I thought as much because I couldn't find any documentation. I was just looking through the Github issues on here and came across that as a suggestion back in 2016. Figured I would try it before creating a new issue.

Also show how the server part.

The server code is outside of my control. I do know that nothing is happening there as several other projects work just fine with it. As previously said, the connection opens fine and the timeout works as intended. This tells us that it is the client side code that is the problem.

I don't know but if my example above works it means that the issue is outside ws.

I don't know but if my example above works it means that the issue is outside ws.

I don't think that's necessarily true, if the event setup IS being called. This means it's possible ws expects the event listeners to be set up in the same process tick. As previously mentioned, I set a breakpoint in the debugger to ensure ws.on("open") was hit, however the callback never fired.

And here is my theory backed by some code. It does what I expected.

wsPrivate.on('open', () => {
  console.log('Opened websocket connection for', 'private', 'channel')
})

setTimeout(() => {
  wsPublic.on('open', () => {
    console.log('Opened websocket connection for', 'public', 'channel')
  })
}, 2000)

Output:

Opened websocket connection for private channel

Expected:

Opened websocket connection for private channel
Opened websocket connection for public channel
setTimeout(() => {
  wsPublic.on('open', () => {
    console.log('Opened websocket connection for', 'public', 'channel')
  })
}, 2000)

This is obviously wrong. The 'open' event could have already be emitted by the time setTimeout() callback is called.

This is precisely my point. In edge cases like these, where it's essential to set up listeners before the .connect is called. Is there a way to set up a WebSocket and not automatically connect but with a manual API?

Something like this pseudo-code:

let ws = new WebSocket(url, { dontConnect: true })

ws.on("open", ...)
ws.connect()

No, the interface is modeled around browser implementation (WHATWG) and there is no such API.

That's unfortunate. Due to my fast connection and slight delay of Promises, it makes it difficult to set up multiple connections with a delay and not have a ton of boilerplate.

Your .connect() is the WebSocket constructor.

Moved my code into the setupWebsocket function. It's an elegant enough solution.

function setupWebsocket(connectURL, type) {
  return new Promise((resolve, reject) => {
    console.log('Setting up websocket events for', type, 'channel')

    let ws = new WebSocket(connectURL)
    ws.on('open', () => {
      console.log('Opened websocket connection for', type, 'channel')

      resolve()
    })
  })
}

Appreciate your time.

Just as a PR recommendation, you could check if a user is setting up ws.on("open") event later in the lifecycle and if the connection/event has already fired, then fire it for the new event.

Just as a PR recommendation, you could check if a user is setting up ws.on("open") event later in the lifecycle and if the connection/event has already fired, then fire it for the new event.

I had this thought exactly but if this library is trying to stick strictly to the WHATWG spec then the spec would need to be updated first. I wish there was a "connect()" api like @bugs181 suggests.

What I think ws could do is warn people in their documentation that they should handle the "open" event immediately after making the connection or they might miss that event completely. This happened to me while I was setting up some unit tests. One of my tests would hang and it would not hit the error event and there is no on('timeout') event either.

I guess one of the things you could do if you're setting up an on('open') event later in the code is check the readyState

function doSomethingOnOpen(){   
  // ... code
}

if (client.readyState === 1) { 
 doSomethingOnOpen()
} else {
  client.on('open', doSomethingOnOpen)
}

What I've found to be the most reliable is to just use a timer and check the connection state. If it's connected, then fire the open event. If nested inside of a promise for a setupWebsocket function, then this connection event will only fire once.

The code looks like this:

const connectionCheckTimer = setInterval(() => {
  if (ws.readyState === ws.OPEN) {
    console.log('Recovered ws connection')
    ws.emit('open')
  }
}, 1000)

ws.on('open', () => {
  clearInterval(connectionCheckTimer)

  console.log('Opened websocket connection for', type, 'channel')
  resolve()
})
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dcflow picture dcflow  路  4Comments

pmcalabrese picture pmcalabrese  路  4Comments

bartosz-m picture bartosz-m  路  3Comments

HanHtoonAung picture HanHtoonAung  路  3Comments

jorenvandeweyer picture jorenvandeweyer  路  4Comments