I am trying to implement some application based on websockets which will communicate with JS clients quite intensively.
The code to send the message is quite primitive:
synchronized (session) {
if (session.isOpen()) {
session.getBasicRemote().sendText(message);
}
}
For rare sending it works just fine, but when few threads are trying to send some messages by the same session (socket), next exception is thrown (please note that it is not multithreading issue because code block is synchronized by session):
java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1015)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textStart(WsRemoteEndpointImplBase.java:978)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendString(WsRemoteEndpointImplBase.java:161)
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendText(WsRemoteEndpointBasic.java:37)
Google is not rich of such type of exceptions at the moment and after beating few hours on this issue, still no solution.
Java 7.0.21, tested on Tomcat 7.0.52 and Tomcat 8.0.3.
Any answer is highly appreciated! Thanks in advance.
UPDATE 3/11/2014: I tested my application with Jetty 9.1 and this exception didn't occur. I assume that this is Tomcat implementation bug.
I just ran into this issue today, the accepted answer wasn't the resolution for me. I tried synchronizing every call to the remote endpoint that was in my code, which was only 4 instances. That did not fix it either. I also tried updating to the latest tomcat version which at this time was 9.0.24 which did not fix it.
It seams the source of my issue is that in a single websocket message request that came in, I happen to send two DIFFERENT messages (on purpose) during the request. I verified both sendText calls were properly synchronized, they were getting called about 0.001 milliseconds a part or less in different blocks.
My solution that I worked up real quick was to use the Async version of the remote endpoint, and just make sure the last msg's future was done by the time the next msg is requested to be sent. I wasn't thrilled about this but it did fix the issue... here is the class I wrote and I just now reference this object anytime I want to send something over the websocket without requiring the code in a sync block, since the send* methods on this class are already synchronized. Hope this helps someone down the line.
NOTE: I did not synchronize anything other than the send* so not sure if Ping/Pong will have the same issue or not, I've never used those.
public class WebSocketEndpointAsync implements RemoteEndpoint.Async {
private final Session _session;
private final Async _ep;
private Future<Void> _lastFuture = null;
public WebSocketEndpointAsync(Session session, Async ep)
{
_session = session;
_ep = ep;
}
@Override public long getSendTimeout() { return _ep.getSendTimeout(); }
@Override public void setSendTimeout(long timeout) { _ep.setSendTimeout(timeout); }
@Override public void setBatchingAllowed(boolean allowed) throws IOException { _ep.setBatchingAllowed(allowed); }
@Override public boolean getBatchingAllowed() { return _ep.getBatchingAllowed(); }
@Override public void flushBatch() throws IOException { _ep.flushBatch(); }
@Override public void sendPing(ByteBuffer byteBuffer) throws IOException, IllegalArgumentException { _ep.sendPing(byteBuffer); }
@Override public void sendPong(ByteBuffer byteBuffer) throws IOException, IllegalArgumentException { _ep.sendPong(byteBuffer); }
@Override public void sendText(String s, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
@Override public void sendBinary(ByteBuffer byteBuffer, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
@Override public void sendObject(Object o, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
protected synchronized void checkLastSendComplete() {
if (_lastFuture != null) {
try {
if (!_lastFuture.isDone()) {
// Only one write to the websocket can happen at a time, so we need to make sure the last one completed
// else we get ...
// java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
do { Thread.sleep(1); }
while (!_lastFuture.isDone());
}
// Get the result to ensure
var ignore = _lastFuture.get();
}
catch (InterruptedException ie) { }
catch (ExecutionException ee) { }
}
}
@Override
public synchronized Future<Void> sendText(String text) {
checkLastSendComplete();
return (_lastFuture = _ep.sendText(text));
}
@Override
public synchronized Future<Void> sendBinary(ByteBuffer byteBuffer) {
checkLastSendComplete();
return (_lastFuture = _ep.sendBinary(byteBuffer));
}
@Override
public synchronized Future<Void> sendObject(Object obj) {
checkLastSendComplete();
return (_lastFuture = _ep.sendObject(obj));
}
}
I found this: https://bz.apache.org/bugzilla/show_bug.cgi?id=56026
seems tomcat has done something unexpected, as a workaround you must synchronize all session.sendxxx calls no matter whether it's async.
OK, this is not a Tomcat issue but my fault.
My onMessage function returned a string, what means that I echoed the message back. As result, those part of code was not synced.
Bad:
@OnMessage
public String onMessage(String message, Session session) {
...
return message;
}
Good:
@OnMessage
public void onMessage(String message, Session session) {
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With