onClose not always being called

883 views
Skip to first unread message

Hans Ahrens

unread,
Sep 6, 2012, 7:58:14 AM9/6/12
to ratch...@googlegroups.com
Hello,

I have implemented a part of the chat example code (WAMP Server) and created some extra calls for some specific functions. 
Everything is working great but i have the feeling that when a user drops his connection (reloads page,etc) the onClose command is not always called.
To connect to the server we use an flash client (https://github.com/gimite/web-socket-js) and the AutoBahn Javascript client.

The problem with this is that the server will keeps rooms running when they need to be closed.
What we do is the user that create's the room is the room master and if he leaves the room should be closed. But unfortunately this doesn't always happen.
I can track this problem using a basic monitor tool i created for this problem with the use of a Memcached server and i can also connect to a room when it should have been closed.
Also we track the user count with the same memcached server and sometime's it wont have a correct user count.

The code we use on onClose = 
    public function onClose(ConnectionInterface $conn) {
    //Update Number of active users
        $this->activeUserCount--;
        //Update Memcached number of users
        $this->sharedCached->replace('userCount', $this->activeUserCount);

        //Loop through all the rooms and unSubscribe them
      foreach ($conn->User->rooms as $topic => $one) {
            $this->onUnSubscribe($conn, $topic);
        }                
    }

Most of the code for unSubscribe accept a few additions come from the example so it should work. It also looks like the problem appears more when from the flash web socket library.
The code for the unSubscribe function = 
    function onUnSubscribe(ConnectionInterface $conn, $topic) {   
    if(!$conn->User->verified){  
    $this->closeConnection($conn);   
    return;
    }
   
    $topic = str_replace('"', '', $topic);  
    $topic = strtolower($topic);
   
    //If room already is gone then return nothing
    if(!isset($this->roomLookup[$topic])){
    return;
    }
   
    //get the Room id from the lookup array
        $roomID = $this->roomLookup[$topic];
        unset($conn->User->rooms[$topic]);
        
        //See if the user exists in the room, if so remove it 
        if($this->rooms[$roomID]->contains($conn)){
        $this->rooms[$roomID]->detach($conn);
        }
        
         //Update Admin Console Room User Count 
         $this->roomAdminConsole[$topic] = $this->rooms[$roomID]->count(); 
$this->sharedCached->replace("rooms", $this->roomAdminConsole);
        
//Check if the room is empty if so then delete it 
        if ($this->rooms[$roomID]->count() == 0) {
            $this->closeRoom($roomID,$topic);
        }else{
       //This is the Master user leaving the room. Destroy the entire room  - We set the user type when we create the room
       if($conn->User->type == 1){
        //Notify Everyone that the room has closed
        $this->broadcast($topic, array('type'=>self::TYPE_ROOMCLOSED, 'userID'=>$conn->WAMP->sessionId, 'userName'=>$conn->User->name), $conn);
      foreach ($this->rooms[$roomID] as $patron) {
      unset($patron->User->rooms[$roomID]);
     
      if($this->rooms[$roomID]->contains($patron)){
      $this->rooms[$roomID]->detach($patron);
      }
           }
                    //This unsets the room variable's
           $this->closeRoom($roomID,$topic);
           
           return;
       }      
            $this->broadcast($topic, array('type'=>self::TYPE_ROOMMEMBERLEFT, 'userID'=>$conn->WAMP->sessionId));
        }
    }


The closeRoom code is:

    private function closeRoom($roomID,$roomName){
   unset($this->rooms[$roomID]);
   unset($this->roomLookup[$roomName]);
   
    //Update Admin Console Room  Count 
             unset($this->roomAdminConsole[$roomName]);
             $this->sharedCached->replace("rooms", $this->roomAdminConsole);
    }



Am i doing something wrong or should this work ? 

Further more could you answer the following questions :
-Is there a call to get the number of connected active clients ? 
-If onClose is not called is there an efficient way to clean old rooms ? 
-Can/Should i put all the variable's that i use in Memcached so i can acces them from another script ? (Maybe i can clean the old rooms this way ?)
-Is it possible that onClose is not always called if so what can i do to fix this (Is there an idle timeout etc for a user, even if it isn't connected)


Thanks in advanced for your reaction.

Best Regards,
Hans

cboden

unread,
Sep 7, 2012, 4:32:08 PM9/7/12
to ratch...@googlegroups.com
Hi Hans,

Your code seems to look ok.  It looks similar to the code from the first version of the chat tutorial.  I have since updated it; it's much cleaner now, you may want to give it a try.

If the Flash client is the issue not calling onClose that would be a bug with the web-socket-js library.  I would recommend filing a bug or asking a question in their forums.  The underlying Flash socket interacts with React (React powers Ratchet) the same as all sockets; onClose is bubbled up when it does close.  I have noticed however, a similar issue occurring on timeouts however.  

"-Is there a call to get the number of connected active clients ?"
Nope, you're doing the right thing by incrementing/decrementing a counter in onOpen and onClose.

"-If onClose is not called is there an efficient way to clean old rooms ?"
You could try to call $conn->close() if you're sure you want to remove that connection; the events will then bubble up and your room should clear

"-Can/Should i put all the variable's that i use in Memcached so i can acces them from another script ? (Maybe i can clean the old rooms this way ?)"
You _could_ do this, but I would not recommend it.  Both scripts would have to poll shared memory for changes.  The best way to interact between two scripts would be with RabbitMQ or ZeroMQ.  Also note: You can not directly communicate with a connection from a different script, you would have to have some kind of lookup.  The script with the connection would have to do the raw I/O.

"-Is it possible that onClose is not always called if so what can i do to fix this (Is there an idle timeout etc for a user, even if it isn't connected)"
onClose will always eventually be called.  Having some connections timeout and linger for a few minutes may unfortunately happen, but if you're sure a user is no longer connected and hasn't been, say for half an hour, than your script would probably have some faulty logic, perhaps ignoring an onClose message and not removing it from a channel it is present in. 

Cheers,
Chris

Hans Ahrens

unread,
Sep 10, 2012, 8:39:03 AM9/10/12
to ratch...@googlegroups.com
Hi Chris,

Thank you for your response.
Ok i will look if i can find a solution for Flash dropping the connection.
Also thank you for the great documentation on how to interact with the server form another PHP script.

I just have 1 more question.  Today i have gotten a few calls from some new external testers that they cant connect to the web socket server.
I know that they are behind a proxy server. 

Now i have read that using wss instead of ws should solve the problem (i am going to test this tonight). 
Have you ever encountered this problem and if so how did you solve it ? 

Best Regards, 
Hans


Op vrijdag 7 september 2012 22:32:08 UTC+2 schreef cboden het volgende:

Hans Ahrens

unread,
Sep 10, 2012, 9:21:34 AM9/10/12
to ratch...@googlegroups.com
A little update i just tried it myself on our national 3G network and it did not work. I also tested your demo and that also didn't work.
Then i tried a socket.io demo : http://serv1.aelag.com:8084/ and that one did work. Could it be a server problem ?

Further more i tried wss but it seems that the server does not support this ? 
I am getting the following:

Ratchet\Server\IoServer::factory(Object(Ratchet\WebSocket\WsServer), 443) #2 {main} thrown in /websocket/vendor/react/socket/React/Socket/Server.php on line 23 Errorexception 'Ratchet\Wamp\JsonException' with message 'Syntax error, malformed JSON' in /websocket/vendor/cboden/Ratchet/src/Ratchet/Wamp/ServerProtocol.php:89 Stack trace: #0 /websocket/vendor/cboden/Ratchet/src/Ratchet/WebSocket/Version/RFC6455.php(201): Ratchet\Wamp\ServerProtocol->onMessage(Object(Ratchet\WebSocket\Version\RFC6455\Connection), 'Rock it with HT...') #1 /websocket/vendor/cboden/Ratchet/src/Ratchet/WebSocket/WsServer.php(130): Ratchet\WebSocket\Version\RFC6455->onMessage(Object(Ratchet\WebSocket\Version\RFC6455\Connection), '???j/???L???[??...') #2 /websocket/vendor/cboden/Ratchet/src/Ratchet/Server/IoServer.php(103): Ratchet\WebSocket\WsServer->onMessage(Object(Ratchet\Server\IoConnection), '???j/???L???[??...') #3 [internal function]: Ratchet\Server\IoServer->handleData('???j/???L???[??...', Object(React\Socket\Connection)) #4 /websocket/vendor/evenement/evenement/src/Evenement/EventEmitter.php(70): call_user_func_array(Array, Array) #5 /websocket/vendor/react/socket/React/Socket/Connection.php(20): Evenement\EventEmitter->emit('data', Array) #6 [internal function]: React\Socket\Connection->handleData(Resource id #2315, Object(React\EventLoop\StreamSelectLoop)) #7 /websocket/vendor/react/event-loop/React/EventLoop/StreamSelectLoop.php(128): call_user_func(Array, Resource id #2315, Object(React\EventLoop\StreamSelectLoop)) #8 /websocket/vendor/react/event-loop/React/EventLoop/StreamSelectLoop.php(152): React\EventLoop\StreamSelectLoop->runStreamSelect() #9 /websocket/vendor/react/event-loop/React/EventLoop/StreamSelectLoop.php(162): React\EventLoop\StreamSelectLoop->tick() #10 /websocket/vendor/cboden/Ratchet/src/Ratchet/Server/IoServer.php(76): React\EventLoop\StreamSelectLoop->run() #11 /websocket/proConnect.php(556): Ratchet\Server\



Op maandag 10 september 2012 14:39:03 UTC+2 schreef Hans Ahrens het volgende:

cboden

unread,
Sep 10, 2012, 9:35:50 AM9/10/12
to ratch...@googlegroups.com
Cellular is difficult...I've tested Ratchet against 3G with mixed results.  Apparently with 3G the carrier can choose to "hold" messages (including a close call) in an attempt to save bandwidth.  If small amounts of data are being sent the carrier can/will buffer them.  Read through this issue and the link inside to the cellular study for a bit more information.  Implementing an ad-hoc heartbeat may be a solution. 

Due to what seems to be a bug in PHP, Ratchet can't support SSL (wss) at the moment.  See this issue for more information.  While I haven't tested it myself, I'm told wrapping Ratchet in stunnel is a solution to this problem. 

Hans Ahrens

unread,
Sep 11, 2012, 3:00:22 AM9/11/12
to ratch...@googlegroups.com
Ah Thats, a shame because it's not only Cellular but also company's that use a proxy server.
It's an interesting article about the Heartbeat solution and i will look into that. I will also look into SSL, but i think that will be a pretty hard thing to get up and running reliably. 



Op maandag 10 september 2012 15:35:50 UTC+2 schreef cboden het volgende:

Hans Ahrens

unread,
Sep 11, 2012, 10:35:56 AM9/11/12
to ratch...@googlegroups.com
A little update i tried to get Stunnel working and atm it works. 
There is just one problem it works on Android and on all desktop browsers but on iOS i am getting the following error : Error during websocket handshake location mismatch
The problem is that it sends an wss requests and recieve's an ws request back.
 I Dont know if it is possible but could the server detect what request it gets and sends that one back instead of the default one ?


Op dinsdag 11 september 2012 09:00:23 UTC+2 schreef Hans Ahrens het volgende:

cboden

unread,
Sep 14, 2012, 10:48:42 AM9/14/12
to ratch...@googlegroups.com
iOS would be using the old Hixie version of the protocol. As far as I know the protocol shouldn't need to know if SSL is enabled or not.  I'll have a look over the spec in a day or two and get back to you.

Cheers.
Reply all
Reply to author
Forward
0 new messages