{"action":"created","issue":{"url":"https://api.github.com/repos/spring-projects/spring-boot/issues/4021","labels_url":"https://api.github.com/repos/spring-projects/spring-boot/issues/4021/labels{/name}","comments_url":"https://api.github.com/repos/spring-projects/spring-boot/issues/4021/comments","events_url":"https://api.github.com/repos/spring-projects/spring-boot/issues/4021/events","html_url":"https://github.com/spring-projects/spring-boot/issues/4021","id":108159868,"number":4021,"title":"Poor SseEmitter handling of timeouts","user":{"login":"konrad-garus","id":242481,"avatar_url":"https://avatars.githubusercontent.com/u/242481?v=3","gravatar_id":"","url":"https://api.github.com/users/konrad-garus","html_url":"https://github.com/konrad-garus","followers_url":"https://api.github.com/users/konrad-garus/followers","following_url":"https://api.github.com/users/konrad-garus/following{/other_user}","gists_url":"https://api.github.com/users/konrad-garus/gists{/gist_id}","starred_url":"https://api.github.com/users/konrad-garus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konrad-garus/subscriptions","organizations_url":"https://api.github.com/users/konrad-garus/orgs","repos_url":"https://api.github.com/users/konrad-garus/repos","events_url":"https://api.github.com/users/konrad-garus/events{/privacy}","received_events_url":"https://api.github.com/users/konrad-garus/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":5,"created_at":"2015-09-24T15:54:11Z","updated_at":"2015-09-25T03:37:12Z","closed_at":null,"body":"Whenever Tomcat async request times out, an error appears in the log and unexpected JSON message is emitted in response. For example, it happens with embedded Tomcat that arrives with Boot, where asyncTimeout appears to be 30 seconds.\r\n\r\nTo reproduce, create a Spring Boot app with embedded Tomcat. Just spring-boot-starter-web, version 1.3.0.M5. Let the main class look like:\r\n\r\n```\r\n@SpringBootApplication\r\n@Controller\r\npublic class Application {\r\n    private static final Logger log = LoggerFactory.getLogger(Application.class);\r\n\r\n    @RequestMapping(path = \"/api/events\", method = RequestMethod.GET)\r\n    public SseEmitter getEvents() throws Exception {\r\n        SseEmitter emitter = new SseEmitter();\r\n        ScheduledExecutorService pingThread = Executors.newScheduledThreadPool(1);\r\n        emitter.onCompletion(() -> {\r\n            log.info(\"Complete\");\r\n            pingThread.shutdown();\r\n        });\r\n        emitter.onTimeout(() -> {\r\n            log.info(\"Timeout\");\r\n        });\r\n        pingThread.scheduleAtFixedRate(() -> {\r\n            try {\r\n                emitter.send(SseEmitter.event().data(\"Hello\").name(\"ping\"));\r\n            } catch (Exception e) {\r\n                log.error(\"Unable to emit\", e);\r\n                throw new RuntimeException(e);\r\n            }\r\n        } , 1000, 1000, TimeUnit.MILLISECONDS);\r\n        return emitter;\r\n    }\r\n\r\n    public static void main(String[] args) throws Exception {\r\n        SpringApplication.run(Application.class, args);\r\n    }\r\n}\r\n```\r\n\r\nRun the app.\r\n\r\nGet the event stream, and observe:\r\n\r\n```\r\n$ curl -v http://localhost:8080/api/events\r\n* Hostname was NOT found in DNS cache\r\n*   Trying 127.0.0.1...\r\n* Connected to localhost (127.0.0.1) port 8080 (#0)\r\n> GET /api/events HTTP/1.1\r\n> User-Agent: curl/7.38.0\r\n> Host: localhost:8080\r\n> Accept: */*\r\n> \r\n< HTTP/1.1 200 OK\r\n* Server Apache-Coyote/1.1 is not blacklisted\r\n< Server: Apache-Coyote/1.1\r\n< Content-Type: text/event-stream;charset=UTF-8\r\n< Transfer-Encoding: chunked\r\n< Date: Thu, 24 Sep 2015 15:51:58 GMT\r\n< \r\ndata:Hello\r\nevent:ping\r\n\r\ndata:Hello\r\nevent:ping\r\n\r\n# ... repeated 30 times ...\r\n\r\n* Connection #0 to host localhost left intact\r\n{\"timestamp\":1443109923142,\"status\":200,\"error\":\"OK\",\"message\":\"No message available\",\"path\":\"/api/events\"}\r\n```\r\n\r\nNote the JSON at the very bottom.\r\n\r\nIn addition, when it happens the server log prints:\r\n\r\n```\r\n2015-09-24 17:53:18.601  WARN 9056 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Handler execution resulted in exception: Could not find acceptable representation\r\n```"},"comment":{"url":"https://api.github.com/repos/spring-projects/spring-boot/issues/comments/143113126","html_url":"https://github.com/spring-projects/spring-boot/issues/4021#issuecomment-143113126","issue_url":"https://api.github.com/repos/spring-projects/spring-boot/issues/4021","id":143113126,"user":{"login":"rstoyanchev","id":401908,"avatar_url":"https://avatars.githubusercontent.com/u/401908?v=3","gravatar_id":"","url":"https://api.github.com/users/rstoyanchev","html_url":"https://github.com/rstoyanchev","followers_url":"https://api.github.com/users/rstoyanchev/followers","following_url":"https://api.github.com/users/rstoyanchev/following{/other_user}","gists_url":"https://api.github.com/users/rstoyanchev/gists{/gist_id}","starred_url":"https://api.github.com/users/rstoyanchev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rstoyanchev/subscriptions","organizations_url":"https://api.github.com/users/rstoyanchev/orgs","repos_url":"https://api.github.com/users/rstoyanchev/repos","events_url":"https://api.github.com/users/rstoyanchev/events{/privacy}","received_events_url":"https://api.github.com/users/rstoyanchev/received_events","type":"User","site_admin":false},"created_at":"2015-09-25T03:37:12Z","updated_at":"2015-09-25T03:37:12Z","body":"Several things going on..\r\n\r\nThe Servlet API has a timeout notification for async requests. In Spring MVC you can register timeout logic in `SseEmitter#onTimeout` (per request) or also globally via `WebAsyncConfigurer#configureAsyncSupport`. By default on timeout Spring MVC sets the response to 503 if response is not committed. However with HTTP streaming in Spring Framework 4.2, the response is likely committed so no timeout handling from the Spring MVC side.\r\n\r\nIn the absence of any concrete timeout logic, Tomcat turns the timeout into an ERROR dispatch which is then picked up by Boot's \"/error\" mapping and the default ErrorController is responsible for the JSON at the end. Tomcat actually does this with an \"include\" since the response is committed, and that allows Boot to append to the response.\r\n\r\nSo my suggestion for Spring Boot is to check if the response is already committed and avoid writing anything in `BasicErrorController`. I'd argue in such situations you can't make any assumptions about what the response is and what status, headers, and content have been written so far, so it shouldn't be altered further.\r\n\r\nIt gets stranger from there as shown in https://jira.spring.io/browse/SPR-13498. If the client connects again the \"old\" response suddenly starts writing to the \"new\" request. That I believe is a Tomcat bug and I confirmed that Jetty doesn't behave like that. I've created a bug in Tomcat https://bz.apache.org/bugzilla/show_bug.cgi?id=58457. This is not related to Spring Boot but I mention it for completeness.\r\n\r\n\r\n\r\n"}}