kaptan
kaptan

Reputation: 3149

Tomcat throws "The remote endpoint was in state [BINARY_FULL_WRITING] ..." when trying to write to websocket session concurrently

I am using tomcat 8.0.23 to terminate my websocket connections. I have the following code to take care of the incoming messages:

@OnMessage
public void onMsg(Session session, byte[] request) {
        executorService.execute(() ->
                session.getAsyncRemote().sendBinary(
                        ByteBuffer.wrap(getResponse(session, request)), result -> {
                            if (!result.isOK()) {
                                LOGGER.catching(result.getException());
                            }
                        }
                ));
}

But I get the following exception:

Exception in thread "pool-6-thread-10160" java.lang.IllegalStateException: The remote endpoint was in state [BINARY_FULL_WRITING] which is an invalid state for called method
    at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1148)
    at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.binaryStart(WsRemoteEndpointImplBase.java:1101)
    at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendBytesByCompletion(WsRemoteEndpointImplBase.java:152)
    at org.apache.tomcat.websocket.WsRemoteEndpointAsync.sendBinary(WsRemoteEndpointAsync.java:65)

It looks like that when I am trying to write to the same session concurrently tomcat is throwing that exception.

The error is coming from this method :

    private void checkState(State... required) {
        for (State state : required) {
            if (this.state == state) {
                return;
            }
        }
        throw new IllegalStateException(
                sm.getString("wsRemoteEndpoint.wrongState", this.state));
    }

I did not expect sendBinary throw that exception since based on java doc:

sendBinary void sendBinary(ByteBuffer data, SendHandler handler)

Throws: IllegalArgumentException - if either the data or the handler are null.

So it looks like that tomcat implementation checks to see if the state is open or not in this code:

    public synchronized void binaryStart() {
        checkState(State.OPEN);
        state = State.BINARY_FULL_WRITING;
    }

and if it is not open then it will throw that exception.

It is interesting to note that under java doc for RemoteEndpoint.Basic (not RemoteEndpoint.Async) we read:

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 IllegalStateException.

There is no such paragraph for RemoteEndpoint.Async!

NOW THE QUESTION

Is it not acceptable to call RemoteEndpoint.Async.sendBinary on a session while something else is writing to the same session?

If it is not acceptable how do I check the state of the remote endpoint before trying to write to it!


Update 1:

Looks like that there has been a discussion around the same issue on java.net.

Update 2:

Link to a similar bug report on apache bugzilla.

Upvotes: 3

Views: 4350

Answers (1)

archeryue
archeryue

Reputation: 41

I got the same situation. The doc is shit. Here is how I handle it.

In fact, I write a actor to wrap the socketSession. It will produce an event when the send-method is called. Each actor will be registered in an Looper which contains a work thread and an event queue. Meanwhile the work thread keeps sending message.

So, I will use the sync-send method inside, the actor model will make sure about the concurrency.

The key problem now is about the number of Looper. You know, you can't make neither too much or too few threads. But you can still estimate a number by your business cases, and keep adjusting it.

Upvotes: 1

Related Questions