Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC SseEmitter - connection aborted Exception

I'm trying to use Springs SseEmitter like in this post: Angular 2 spring boot server side events. The push events are working but everytime I'm closing or refreshing the tab I've got the exception below.

The strange thing is that the exception is thrown within the send method of the emiter which is surrounded by a try catch block. The exception must be catched and logged and rethrown within the method. But how can I prevent it. I don't want to suppress the error log.

IOException: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen

IOEexception an established connection was aborted by the software in your host machine

Thanks!

SseController.java

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@RestController
public class SSEController {

    public static final List<SseEmitter> emitters = Collections.synchronizedList( new ArrayList<>());

    @RequestMapping(path = "/stream", method = RequestMethod.GET)
    public SseEmitter stream() throws IOException {

        SseEmitter emitter = new SseEmitter();

        emitters.add(emitter);
        emitter.onCompletion(() -> emitters.remove(emitter));

        return emitter;
    }
}

ServiceClass.java

@Scheduled
public void sendSseEventsToUI(Notification notification) { //your model class
        List<SseEmitter> sseEmitterListToRemove = new ArrayList<>();
        SSEController.emitters.forEach((SseEmitter emitter) -> {
            try {
                emitter.send(notification, MediaType.APPLICATION_JSON);
            } catch (Exception e) {
                emitter.complete();
                sseEmitterListToRemove.add(emitter);
            }
        });
        SSEController.emitters.removeAll(sseEmitterListToRemove);
    }

Exception:

> 2017-12-27 13:54:53.206  INFO 4248 --- [pool-4-thread-1]
> o.apache.coyote.http11.Http11Processor   : An error occurred in
> processing while on a non-container thread. The connection will be
> closed immediately
> 
> java.io.IOException: Eine bestehende Verbindung wurde
> softwaregesteuert durch den Hostcomputer abgebrochen  at
> sun.nio.ch.SocketDispatcher.write0(Native Method) ~[na:1.8.0_121]     at
> sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
> ~[na:1.8.0_121]   at
> sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
> ~[na:1.8.0_121]   at sun.nio.ch.IOUtil.write(IOUtil.java:65)
> ~[na:1.8.0_121]   at
> sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
> ~[na:1.8.0_121]   at
> org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:134)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:157)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1267)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:670)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.SocketWrapperBase.flushBlocking(SocketWrapperBase.java:607)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.SocketWrapperBase.flush(SocketWrapperBase.java:597)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.http11.Http11OutputBuffer.flushBuffer(Http11OutputBuffer.java:581)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.http11.Http11OutputBuffer.flush(Http11OutputBuffer.java:272)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.http11.Http11Processor.flush(Http11Processor.java:1560)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:283)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.Response.action(Response.java:173)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:317)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:284)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:118)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297)
> [na:1.8.0_121]    at
> sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) [na:1.8.0_121]
>   at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
> [na:1.8.0_121]    at
> org.springframework.util.StreamUtils.copy(StreamUtils.java:119)
> [spring-core-4.3.13.RELEASE.jar:4.3.13.RELEASE]   at
> org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106)
> [spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:41)
> [spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:227)
> [spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.sendInternal(ResponseBodyEmitterReturnValueHandler.java:207)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.send(ResponseBodyEmitterReturnValueHandler.java:200)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.sendInternal(ResponseBodyEmitter.java:166)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.send(ResponseBodyEmitter.java:159)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:126)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:107)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> com.example.demo.PushService.sendSseEventsToUI(PushService.java:22)
> [classes/:na]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native
> Method) ~[na:1.8.0_121]   at
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
> ~[na:1.8.0_121]   at
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
> ~[na:1.8.0_121]   at java.lang.reflect.Method.invoke(Method.java:498)
> ~[na:1.8.0_121]   at
> org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
> [spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
> [spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
> [na:1.8.0_121]    at
> java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
> [na:1.8.0_121]    at
> java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
> [na:1.8.0_121]    at
> java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
> [na:1.8.0_121]    at
> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
> [na:1.8.0_121]    at
> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
> [na:1.8.0_121]    at java.lang.Thread.run(Thread.java:745)
> [na:1.8.0_121]
like image 374
meleagros Avatar asked Dec 28 '17 08:12

meleagros


Video Answer


1 Answers

You are seeing a log from org.apache.coyote.http11.Http11Processor. You are getting it because the servlet api that doesn't notify when a client connection is closed. That's why it try to write on the socket anyway, more info at jira.spring.io/browse/SPR-13292

If you don't want to see this log change the log level for this package by adding this property (in your application.properties):

logging.level.org.apache.coyote.http11=ERROR

Or you can edit your logger configuration file.

Furthermore don't use static final collection for keeping your emitter do something like:

In your service

private final Collection<SseEmitter> emitters = Collections.synchronizedCollection(new HashSet<SseEmitter>());

public void register(SseEmitter emitter) {
    emitter.onTimeout(() -> timeout(emitter));
    emitter.onCompletion(() -> complete(emitter));

    emitters.add(emitter);
}

private void complete(SseEmitter emitter) {
    System.out.println("emitter completed");
    emitters.remove(emitter);
}

private void timeout(SseEmitter emitter) {
    System.out.println("emitter timeout");
    emitters.remove(emitter);
}

@Scheduled(fixedDelay = 3000)
public void sendSseEventsToUI() { //your model class
    for(SseEmitter emitter : emitters) {
        try {
            emitter.send(UUID.randomUUID().toString(), MediaType.APPLICATION_JSON);
        } catch (Throwable e) {
            emitter.complete();
        }
    };
}

In your controller:

@Autowired
public PushController(PushService service) {
    this.service = service;
}

@RequestMapping(path = "/", method = RequestMethod.GET)
public SseEmitter stream() {
    final SseEmitter emitter = new SseEmitter(0L);
    service.register(emitter);
    return emitter;
}
like image 114
JEY Avatar answered Oct 05 '22 03:10

JEY