I believe that what's happening here, is similar to this issue:
http://stackoverflow.com/questions/24017770/event-stream-request-not-firing-close-event-under-passenger-apache/24067051#24067051
Phusion Passenger acts as a TCP proxy between the actual client and
your app. The thing about sockets is that there are two ways to find
out whether a socket has been closed: either 1) by reading end-of-file
from it, or 2) by writing to it and getting an error.
In case of WebSockets, when Phusion Passenger has noticed that the
client has sent EOF, it stops forwarding any further client data to
the application (because there is no further client data). However, it
does not close the connection with the application yet, because the
client may have only *half-closed* the socket (stopped writing to the
server). It does not half-close the connection with the application
either, because Node.js's HttpServer library does not support
half-closing by default. Thus, Phusion Passenger takes the
conservative approach, and keeps the connection alive.
To check whether the client has also stopped *reading* from the server
(also a half-close), a write is necessary. This is why your app does
not notice that the browser tab has closed, until it has attempted to
write something.
This issue is not limited to Phusion Passenger. If you put your
Node.js app behind a load balancer or any other kind of reverse proxy,
then you could also run into the same issue. It depends on the
implementation choices of the proxy.
The standard solution is to regularly send "ping" messages, with the
purpose of checking whether the connection is alive. This is the
reason why the WebSocket supports ping frames.