Bug 55715 - RemoteEndpoint.Async#sendText(String, SendHandler) can cause StackOverflowErrors and contradicts Oracle's JavaDoc
Summary: RemoteEndpoint.Async#sendText(String, SendHandler) can cause StackOverflowErr...
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 8
Classification: Unclassified
Component: WebSocket (show other bugs)
Version: 8.0.x-trunk
Hardware: PC All
: P2 normal (vote)
Target Milestone: ----
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2013-10-28 15:34 UTC by Konstantin Preißer
Modified: 2013-10-30 11:24 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Konstantin Preißer 2013-10-28 15:34:56 UTC
See thread on users list [1]:

Tomcat's current implementation of RemoteEndpoint.Async#sendText(String, SendHandler) can cause StackOverflowErrors (and seems to contradict Oracle's JavaDoc).

In Tomcat, Async#sendText(...) seems to implemented so that when it could send all of the data immediately, then it directly calls SendHandler#onResult(...); whereas when it couldn't send the data immediately, the SendHandler will be called from another thread.

Oracle's javadoc for RemoteEndpoint.Async says:

"The completion handlers for the asynchronous methods are always called with a different thread from that which initiated the send."

Now, imagine the case that you want to send 10000 very small text messages to a client (very unlikely, but possibly could happen). With synchronous I/O (RemoteEndpoint.Basic), you would do this in this way:

        RemoteEndpoint.Basic basic = session.getBasicRemote();
        for (int i = 0; i < 10000; i++) {
            basic.sendText("Hi, Count: " + i);
        }

In this case, there is no problem.

Now imagine you want to do this asynchronously (using a Callback to be informed when sending is completed), then you could do it this way (e.g. put the following code in onOpen() method of Tomcat's EchoEndpoint example):

        final AtomicInteger aint = new AtomicInteger();
        final RemoteEndpoint.Async async = session.getAsyncRemote();
        
        SendHandler handler = new SendHandler() {
            @Override
            public void onResult(SendResult result) {
                int nextVal = aint.incrementAndGet();
                if (nextVal < 10000) {
                    async.sendText("Hi, Count: " + nextVal, this);
                }
            }
        };
        async.sendText("Hi, Count: " + aint.get(), handler);

The problem here is that because the messages are very short, Tomcat will be able to send them immediately, calling the SendHandler#onResult() directly from Async.sendText(), which will eventually cause a StackOverflowError.

I think if SendHandler#onResult() was always called from a different thread than the one which calls Async.send... (like the Javadoc says), then although the performance would probably be worse, StackOverflowErrors shouldn't occur.


[1] http://markmail.org/message/gpxzdwtxtrpynvux
Comment 1 Mark Thomas 2013-10-30 11:24:40 UTC
Thanks for the report.

This has been fixed in trunk for 8.0.0-RC6 and 7.0.x for 7.0.48.