I have a standard websocket endpoint based on Tyrus implementation which times to times triggers the java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request
. We are running on Payara 4.1.
My standard implementation
@ServerEndpoint(value = "...", decoders=MessageDecoder.class, encoders=MessageEncoder.class)
public class EndpointImpl extends AbstractEndpoint{
// onOpen, onClose, onMessage, onError methods
}
Where the abstract class is
public abstract class AbstractEndpoint{
// irrelevant onOpen, onOpen handling method
117 protected void sendMessage(Session session, Message message){
118 if(message == null){
119 LOGGER.error("null message");
120 } else if(!session.isOpen()){
121 LOGGER.error("session is not opened");
122 } else{
>>>123 session.getAsyncRemote().sendObject(message, (result) -> {
124 if (result.isOK()) {
125 LOGGER.info("success! yeah!");
126 } else {
127 LOGGER.error("error when sending message", result.getException());
128 }
129 });
130 }
}
}
IllegalStateException
So far, nothing special. I can perfectly communicate and respond to the request I received and, websocket FTW, I can push information and get back the feedback. However, I times to times receive an exception:
java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request
at org.apache.catalina.connector.OutputBuffer.setWriteListener(OutputBuffer.java:536)
at org.apache.catalina.connector.CoyoteOutputStream.setWriteListener(CoyoteOutputStream.java:223)
at org.glassfish.tyrus.servlet.TyrusServletWriter.write(TyrusServletWriter.java:140)
at org.glassfish.tyrus.core.ProtocolHandler.write(ProtocolHandler.java:486)
at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:274)
at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:332)
at org.glassfish.tyrus.core.TyrusWebSocket.sendText(TyrusWebSocket.java:317)
at org.glassfish.tyrus.core.TyrusRemoteEndpoint.sendSyncObject(TyrusRemoteEndpoint.java:429)
at org.glassfish.tyrus.core.TyrusRemoteEndpoint$Async.sendAsync(TyrusRemoteEndpoint.java:352)
at org.glassfish.tyrus.core.TyrusRemoteEndpoint$Async.sendObject(TyrusRemoteEndpoint.java:249)
at com.mycompany.websocket.AbstEndpoint.sendMessage(AbstEndpoint.java:123)
Second sendMessage method attempt
At first, I thought that my asynchronous endpoint was wrongly configured so I tried the Future<> way instead of the callback way:
RemoteEndpoint.Async async = session.getAsyncRemote();
async.setSendTimeout(5000); // 5 seconds
Future<Void> future = async.sendObject(message);
try{
future.get();
}
catch(InterruptedException | ExecutionException ex){
LOGGER.error("error when sending message", ex);
}
I also got the exception.
So far and symptoms
Surprisingly, I only found one link talking about this issue.
- The github link highlights a buffer size issue. I don't use partial messages, only whole messages. Moreover, regardless if I'm using the default buffer size or I set a new one, the exception comes
- I could not find a global rule about how to reproduce the error
- After the exception was raised, the client could keep sending messages and the server would process it but the server never replied to the client. It appears that the outgoing communication channel is blocked
- As the server keeps processing incoming messages, the websocket channel is not closed after the exception
Digging in Tyrus implementation
I browsed the tyrus-core implementation to found out that the sending method is depending on some Grizzly component. I don't know anything about Grizzly but it appears that the sending must be synchronous anyway due to some Grizzly restriction
Questions
- Did someone already meet such a situation? If yes, does the exception really means that there is a bottleneck somewhere or it means something else?
- Is tyrus asynchronous endpoint really asynchronous, ie like "process-and-forget"?
- I haven't found any way to have and outgoing messages queued: if a message A is long, wait for message A sending to finish before sending message B. Is there a way to handle large messages in websocket or the asynchronous endpoint is the only way?
- I want to make sure that the sending did not encounter any issue, hence my choice of a asynchronous solution. Should I go back to a synchronous way?
I haven't detailed my Tyrus investigation. If you feel it relevant, feel free to ask and I'll gladly develop.
IllegalStateException
again. So that's must be the solution! As I'm not sure about all the ins and outs, I'll wait for a month and then post a self-answer if it could help the others. Anyway, many thanks @BalusC! – Messer