Spring DeferredResult throws an IOException: the established connection was interrupted by the software of your host machine

I am trying to use Spring DeferredResult to do a lengthy poll. In this example, one user visits a page that uses a lengthy survey to wait for another user to click the link. The second user (you are in a different browser) then clicks on this link, and a long survey returns to the first user, notifying her of the second user click.

jsp looks like this:

 <!DOCTYPE html> <html lang="en"> <head> <title>Spring Example</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script> function pollContent() { $.ajax({url: "waitForClick", success: function(result){ console.log("Polled result: " + result); $("#polledContent").html(result); pollContent(); }}); } $(pollContent); </script> </head> <body> <p><a href="clickTheThing">Click this thing.</a></p> <p id="polledContent">Waiting for somebody to click the thing...</p> </body> </html> 

And the controller is as follows:

 package com.example.controller; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Component; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.async.DeferredResult; import com.example.controller.interfaces.ExampleControllerInterface; @Component public class ExampleController implements ExampleControllerInterface{ private int clickCount = 0; private List<DeferredResult<String>> waiting = new ArrayList<>(); @Override public String viewHomePage(HttpServletRequest request, ModelMap model, HttpSession session){ return "index"; } @RequestMapping(value = "/clickTheThing", method = RequestMethod.GET) public String clickTheThing(HttpServletRequest request, ModelMap model, HttpSession session){ new Thread(){ public void run(){ clickCount++; System.out.println("Somebody clicked the thing! Click count: " + clickCount); Iterator<DeferredResult<String>> iterator = waiting.iterator(); while(iterator.hasNext()){ DeferredResult<String> result = iterator.next(); System.out.println("Setting result."); result.setResult("Somebody clicked the thing! Click count: " + clickCount); iterator.remove(); } } }.start(); return "clicked"; } @ResponseBody @RequestMapping(value = "/waitForClick", method = RequestMethod.GET) public DeferredResult<String> waitForClick(HttpServletRequest request, ModelMap model, HttpSession session){ final DeferredResult<String> result = new DeferredResult<>(); waiting.add(result); return result; } @ResponseBody @RequestMapping(value = "/getClickCount", method = RequestMethod.GET) public String getClickCount(HttpServletRequest request, ModelMap model, HttpSession session){ return String.valueOf(clickCount); } } 

And, for completeness, here is my class ErrorConfig :

 package com.example.config; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice public class ErrorConfig{ @ExceptionHandler(Exception.class) public String handleException (HttpServletRequest request, HttpServletResponse response, HttpSession session, Exception e) { e.printStackTrace(); return "index"; } } 

Everything seems to be working fine. The first user is really notified when another user clicks on the link.

However, if this first user refreshes the page before the second user clicks on the link, I also get a stack trace for each "old" DeferredResult:

 org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:396) at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426) at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:342) at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:316) at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:110) at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297) at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229) at org.springframework.util.StreamUtils.copy(StreamUtils.java:106) at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106) at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:40) at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:208) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:143) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:89) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:193) at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852) at javax.servlet.http.HttpServlet.service(HttpServlet.java:618) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837) at javax.servlet.http.HttpServlet.service(HttpServlet.java:725) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721) at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639) at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605) at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208) at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405) at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646) at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Caused by: java.io.IOException: An established connection was aborted by the software in your host machine at sun.nio.ch.SocketDispatcher.write0(Native Method) at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51) at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93) at sun.nio.ch.IOUtil.write(IOUtil.java:65) at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:470) at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:122) at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101) at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:173) at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:139) at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:197) at org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41) at org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:320) at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:84) at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:257) at org.apache.coyote.Response.doWrite(Response.java:523) at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:391) ... 50 more Aug 20, 2015 7:19:24 PM org.apache.catalina.core.ApplicationDispatcher invoke SEVERE: Servlet.service() for servlet jsp threw exception java.lang.IllegalStateException: getOutputStream() has already been called for this response at org.apache.catalina.connector.Response.getWriter(Response.java:535) at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:212) at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:103) at org.apache.jasper.runtime.JspWriterImpl.initOut(JspWriterImpl.java:115) at org.apache.jasper.runtime.JspWriterImpl.flushBuffer(JspWriterImpl.java:108) at org.apache.jasper.runtime.PageContextImpl.release(PageContextImpl.java:173) at org.apache.jasper.runtime.JspFactoryImpl.internalReleasePageContext(JspFactoryImpl.java:120) at org.apache.jasper.runtime.JspFactoryImpl.releasePageContext(JspFactoryImpl.java:75) at org.apache.jsp.WEB_002dINF.jsp.index_jsp._jspService(index_jsp.java:93) at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70) at javax.servlet.http.HttpServlet.service(HttpServlet.java:725) at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432) at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:403) at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:347) at javax.servlet.http.HttpServlet.service(HttpServlet.java:725) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721) at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584) at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:523) at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:201) at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:267) at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1221) at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1005) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:952) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852) at javax.servlet.http.HttpServlet.service(HttpServlet.java:618) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837) at javax.servlet.http.HttpServlet.service(HttpServlet.java:725) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721) at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639) at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605) at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208) at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405) at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646) at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) 

I can simply ignore these exceptions, but that seems wrong.

So my questions are:

  • What causes the initial ClientAbortException? Should I do something else?
  • What are the best practices for this kind of thing, preferably so as not to throw exceptions?
  • Is there a better way to track DeferredResults for a long survey?
  • Is there a way for subsequent exceptions getOutputStream() has already been called for this response , which, as I understand it, is caused by the exception handler error page?

I have a mavenized version of this project available on GitHub here if you want to try it yourself.

In the end, I'm trying to add a notification system to my Spring website, similar to the StackOverflow notification system. If there is a better way to do this with Spring and a long poll, I'm all ears.

Edit: I did not receive any answers (or even comments), so I added generosity. I would definitely appreciate any feedback!

+6
source share
3 answers

The reason this happens is because when the first user closes the browser, Stream becomes closed, and when you try to set the DeferredResult result, spring tries to send it to the client, causing an error.

You should try to check if DeferredResult is in a healthy state by calling the isSetOrExpired() method before sending the result:

  while(iterator.hasNext()){ DeferredResult<String> result = iterator.next(); System.out.println("Setting result."); if(!result.isSetOrExpired()){ result.setResult("Somebody clicked the thing! Click count: " + clickCount); } iterator.remove(); } 

If spring still does not cancel the cancellation of the Delayed when the client closes the connection, then there is no need to do too much to eliminate the exception that actually occurs: Detecting client disconnection in the tomcat servlet? .

Note that long processing and the comet as a whole is a difficult thing to create from scratch, you should consider using something like Atmosphere for this. It will provide you with both websites and comets for old browser compatibility.

About other issues

  • What are the best methods for this kind of thing, preferably so as not to throw exceptions?

    • For this you should use WebSockets. spring 4.2 comes with some nice features for it, and also checks the Atmosphere
    • You can also provide server events.
  • Is there a better way to track DeferredResults for a long survey?

    • I assume that your path is in order, if everyone can press a button and notify all the listeners. If you want even more focused behavior (for example, by specially notifying the user about your individual events), you must save the user map and wait a long time for the DeferredResults poll.
  • Is there a way for subsequent getOutputStream () to be called for these response exceptions, which, as I understand it, are caused by the error handler exception page?

    • This error occurs for the same reason as the first. As an error occurs, the servlet container attempts to print the JSP error in the closed stream, causing an error. The way to resolve this issue simply does not make mistakes when the request is ever closed before receiving an answer :-).
+4
source

What causes the initial ClientAbortException? Should I do something else?

When we use DeferredResult or Callable:

ClientAbortException: the client made another request / clicked another link. this means that the current action is paused and it raises a ClientAbortException in the current thread.

An established connection was interrupted by your host software.

Most likely, the Windows firewall can also perform such a cancel function for it once.

What are the best practices for this kind of thing, preferably so as not to throw exceptions?

Websockets

Is there a better way to track DeferredResults for a long survey?

Define a collection for different users along with a long survey.

Is there a way for the subsequent getOutputStream () to have already been called for these exception exceptions, which, as I understand it, are caused by the exception handler error page?

It may also cause the current current exception to fail.

0
source
 <a href="clickTheThing">Click this thing.</a> . If you use a tag href this web page will reload. should use preventDefault to disable reload page. 

Then the connection will not be closed by the browser.

-1
source

All Articles