Todays article would be about one simple use-case of JSR-356: Java API for WebSocket. This was brought to my attention by Mark Thomas, who started relatively long thread on the jsr356-experts mailing list. The described problem can be simplified to following code:
You might be wondering what is the problem here.. first message is sent asynchronously and the second one synchronously. Session object itself is meant to be thread safe (last paragraph on class level javadoc), but there is a problem with container itself, more precisely with RemoteEndpoint.Basic interface, see following quote of its documentation:
If the websocket connection underlying this RemoteEndpoint is busy sending a message when a call is made to send another one, for example if two threads attempt to call a send method concurrently, or if a developer attempts to send a new message while in the middle of sending an existing one, the send method called while the connection is already busy may throw an
What I get from this paragraph is that the second line (from the sample above) can throw an IllegalStateException, when previous message is not sent. To be honest, reference implementation did not throw that exception (ever) and I don’t believe that any sample application or any WebSocket application I wrote checks for that state. Anyway, the javadoc does not mention that it MUST throw an exception, so for the mentioned sample, it is ok to block the second send until both messages are sent.
Unfortunately, that’s not all :). WebSocket API also allows sending partial messages and when you mix partial messages and whole messages in this context, you might start getting small headache… let’s consider following:
session.getBasicRemote().sendText(message, false); // partial msg session.getBasicRemote().sendBinary(data); session.getBasicRemote().sendText(message, true); // partial msg
In this sample, the second message should not be sent before the message from third line. Problem arises when this sequence is executed from a single thread – if second line blocks until text message is completed, we would have a problem. Implementation cannot really assume what will or won’t happen and generally it does not want to block the thread for a long time, so throwing IllegalStateException seem to be relatively OK.
(Sidenote: reference implementation, Tyrus, did not handle this scenario correctly. The issue related to this functionality was filed as TYRUS-393 and the fix will be present in next released version – 1.10)
Since this seem to be grey area in the specification (it says what “may” be done, not what MUST or MUST NOT be done), the implementation can choose whatever it want – actually I believe that blocking the thread and waiting until it can send a message is valid, specification compliant approach. For Tyrus, I implemented little more defensive solution: the implementation waits (blocks the thread) for short period of time and if the conditions still don’t allow sending the message, IllegalStateException will be thrown.
Conclusion is not as clear as it should be – WebSocket Session object is thread safe for sure, but there are some conditions and states, which might be surprising and confusing for common users of this API. All of them can be prevented by following some rules when using WebSocket Sessions – for example if you are sending only whole messages synchronously, you don’t need to care about anything. If you are sending messages only asynchronously, it should be similar, but you might encounter some discrepancies among the implementations. When you want or need to send partial message, the issue might be harder to deal with – since WebSocket Session does not offer any way how to check the current state, it will need to be stored somewhere else and checked before invoking send* methods. My current advice is that the applications you write should add handling of IllegalStateException from RemoteEndpoint.Basic.send* methods, especially if you expect that it can be run on different JSR 356 implementation.