Description
Scenario:
Client connected to a server via WebSocket.
Client is listening for event "bar".
Client emits a message "foo" waiting for ack callback.
Server processes "foo" message sent by client, it sends an ack, then emits a "bar" event.
Expected Behavior:
I expect that "foo" ack callback will be executed before "bar" handler.
Current Behavior:
Event handler is executed before ack callback
Context:
- Emit method invoked from main queue
- Ack Callback code is executed synchronously (no dispatch_async in the block provided to OnAckCallback instance)
- Event Handler code is executed synchronously (no dispatch_async in the block provided to SocketIOClient instance)
- Handle queue is the default one (main queue)
- Code is in objective-c
- Network has a very low latency, so packets are sent and received almost simultaneously
Detailed Description:
I noticed that anytime the SocketManager class receives any kind of packets, it submits a block onto its handleQueue asynchronously, asking a SocketIOClient to handle the parsed packet. If packet is an "event" kind, the client executes event handlers callbacks immediately; If the packet received is an "ack" kind, it delegates the packet handling operation to SocketAckManager, that it will submit a block asynchronously on the queue, provided as argument to "executeAck" method (in this case SocketManager's handleQueue), that will execute the ack's callback.
The chain of events I believe is happening is the following:
- "foo" ack packet is received.
- SocketManager dispatches async a block (A) on handle queue. Inside this block, the ack packet will be handled by a client instance
- "bar" event packet is received.
- SocketManager dispatches async a block (B) on handle queue. Inside this block, the event packet will be handled by a client instance.
- At this point, handle queue has two blocks waiting for execution. A - B
- Block A is executed. SocketAckManager's "executeAck" method is invoked. This will dispatch async a new block (C) on handle queue
- At this point, handle queue has two blocks waiting for execution. A (executing) - B - C
- Block B is executed. SocketIOClient handles "bar" event calling any event handler attached to "bar" event.
- Block C is executed. "foo" ack callback is executed.
Proposed Solution:
Promote SocketAckManager to a class. Change "executeAck" and "timeoutAck" methods to execute ack callback synchronously. The reason why SocketAckManager must be modified to be a class, is that in case of re-entrant invocations in one of the "mutating" methods you get a runtime exception (i.e Simultaneous accesses to [...], but modification requires exclusive access).
I did this editing, and I noticed that the problem doesn't occur anymore.
Side Notes:
I don't have any experience with Swift whatsoever, and I didn't understand entirely Socket.IO library internals either. So, I don't know which side effects these changes could carry.
I'm pretty sure this strange behaviour is not caused by the server implementation, because our Android and Web clients don't seem to show the same behaviour.
You can find below a log where you can see the differences between what happens in the original library implementation and my changed version (sensitive data has been removed):