scipilot
scipilot

Reputation: 7467

How to disconnect a websocket before the next page is loaded in Chrome

While implementing a Socket.io based notification system in an Express JS app, I have found when the user navigates around the site, the next page load via the controllers, comes in before the previous page's websocket disconnect event.

This is frustrating, as I am mapping users-to-sockets and during the page load cycle any new messages get sent to the old socket.

I have a check which spools up the messages if there's no active connection, and then flushes them to the client when it connects, or on demand later. During the second/third page controller processing (before the page has even been sent of course) this process is fooled into thinking the previous page's connection is still active. Ideally I want the socket to be torn down, so I can remove the map entry before the controller runs.

While my app too complex to post here, I have reproduced the problem easily in the Socket.io sample app.

This produces the following trace when clicking between two pages and back again:

Page 1 controller
Page 1 done
connection  VlPV3nIQHFuzGUXKAAAU


Page 2 controller
Page 2 done
disconnecting VlPV3nIQHFuzGUXKAAAU transport close
disconnect VlPV3nIQHFuzGUXKAAAU transport close
connection  Xr7l-6TSv4VpWibXAAAV


Page 1 controller
Page 1 done
disconnecting Xr7l-6TSv4VpWibXAAAV transport close
disconnect Xr7l-6TSv4VpWibXAAAV transport close
connection  WcmYeaOnpmzYG3_qAAAW

As you can see the disconnect for the previous socket is done after the page is done rendering.

The server is a slightly modified version of this https://github.com/socketio/chat-example. I have just added event logging and a second page to alternate betwen.

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var port = process.env.PORT || 3000;

app.get('/', function(req, res){
    console.log('Page 1 controller');
    res.sendFile(__dirname + '/index.html');
    console.log('Page 1 done');
});
app.get('/2', function(req, res){
    console.log('Page 2 controller');
    res.sendFile(__dirname + '/index.html');
    console.log('Page 2 done');
});

io.on('connection', function(socket){
    console.log('connection ', socket.id);
    socket.on('chat message', function(msg){
        console.log('received ', msg);
        io.emit('chat message', msg);
    });
    socket.on('disconnecting', function(reason){
        console.log('disconnecting', socket.id, reason);
    });
    socket.on('disconnect', function(reason){
        console.log('disconnect', socket.id, reason);
    });
});

http.listen(port, function(){
    console.log('listening on *:' + port);
});

I have tried wrapping the controller in setImmediate() but that didn't delay it enough.

I'm guessing Chrome is doing some "clever" pre-fetching before tearing down the old page, or the socket is lingering slightly.

How can I get the disconnect before the controller is hit?


EDIT: Trying it in Safari gives the same results, but it's complicated by the fact it pre-fetches the site in the address bar, so you get duplicate connections.

Page 1 controller
Page 1 done
connection  77rWBC3fx_VrVGSsAAAj
disconnecting 77rWBC3fx_VrVGSsAAAj transport close
disconnect 77rWBC3fx_VrVGSsAAAj transport close

Page 2 controller
Page 2 done
connection  ExZeKTiG1wRix7pxAAAk
disconnecting ExZeKTiG1wRix7pxAAAk transport close
disconnect ExZeKTiG1wRix7pxAAAk transport close

Page 1 controller
Page 1 done
connection  0Xtkd5lYCyHDumHTAAAl
disconnecting 0Xtkd5lYCyHDumHTAAAl transport error
disconnect 0Xtkd5lYCyHDumHTAAAl transport error

Page 2 controller
Page 2 done
connection  0x7aV1ttnG6NT9_pAAAm

Page 2 controller
Page 2 done
disconnecting bxzFSDWGNDfEpw5MAAAi transport close
disconnect bxzFSDWGNDfEpw5MAAAi transport close
connection  l09oRFBX0-XZp658AAAn
disconnecting 0x7aV1ttnG6NT9_pAAAm transport close
disconnect 0x7aV1ttnG6NT9_pAAAm transport close

The last one seemed to be prefetched.

Upvotes: 0

Views: 1282

Answers (1)

scipilot
scipilot

Reputation: 7467

I tried using the window.onunload event to see if that went earlier, but it doesn't. Then I noticed the window.onbeforeload event and that one does come before the next page load:

Page 1 controller
Page 1 done
connection  RZJ0RpDdIRM9JwcHAAAI

window_beforeunload RZJ0RpDdIRM9JwcHAAAI
Page 2 controller
Page 2 done
window_unload RZJ0RpDdIRM9JwcHAAAI
disconnecting RZJ0RpDdIRM9JwcHAAAI transport close
disconnect RZJ0RpDdIRM9JwcHAAAI transport close
connection  5wU3WklUptDVm7HHAAAJ

window_beforeunload 5wU3WklUptDVm7HHAAAJ
Page 1 controller
Page 1 done
window_unload 5wU3WklUptDVm7HHAAAJ
disconnecting 5wU3WklUptDVm7HHAAAJ transport close
disconnect 5wU3WklUptDVm7HHAAAJ transport close
connection  VOywtaf_4nAipPpvAAAK

With this amended client:

      window.onbeforeunload = function(){
          socket.emit('window_beforeunload');
      };
      window.onunload = function(){
          socket.emit('window_unload');
      };

And this amended server:

io.on('connection', function(socket){
    ...

    socket.on('window_beforeunload', function(){
        console.log('window_beforeunload', socket.id);
    });
    socket.on('window_unload', function(){
        console.log('window_unload', socket.id );
    });
});

Upvotes: 1

Related Questions