ring-sse icon indicating copy to clipboard operation
ring-sse copied to clipboard

Prevent IllegalStateException on close

Open dedeibel opened this issue 4 years ago • 0 comments

TL;DR: The committed code will not close the inner output-stream, write-body-to-stream will close that one for us and since it is a proxy that calls complete on the AsyncContext we did trigger it twice, provoking the IllegalStateException

Please accept my pull request. I noticed a minor problem where an IllegalStateException was thrown after sending asynchronous data and then closing the channel. Code like the example from the readme.

I 21-02-06 17:33:42,843      async-dispatch-8 g.                  wrserver.http.invoke                    :sse/on-client-disconnect {:status 200, :headers {Content-Type text/event-stream; charset=UTF-8, Connection close, Cache-Control no-cache}, :body #object[clojure.core.async.impl.channels.ManyToManyChannel 0x51df0104 clojure.core.async.impl.channels.ManyToManyChannel@51df0104]}
Exception in thread "async-thread-macro-1" java.lang.IllegalStateException: AsyncContext completed and/or Request lifecycle recycled
	at org.eclipse.jetty.server.AsyncContextState.state(AsyncContextState.java:52)
	at org.eclipse.jetty.server.AsyncContextState.complete(AsyncContextState.java:72)
	at ring.util.servlet$make_output_stream$fn__9013.invoke(servlet.clj:91)
	at ring.util.servlet.proxy$java.io.FilterOutputStream$ff19274a.close(Unknown Source)
	at ring.sse$eval18682$fn__18683$fn__18684.invoke(sse.clj:14)
	at clojure.core.async$thread_call$fn__16270.invoke(async.clj:484)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

According to my analysis this might happen if complete is called multiple times. complete is called at the following point by ring: https://github.com/ring-clojure/ring/blob/1.9.0/ring-servlet/src/ring/util/servlet.clj#L129

It is called in a proxy method to the output stream. write-body-to-stream will call close on that stream for the user: https://github.com/ring-clojure/ring/blob/1.9.0/ring-core/src/ring/core/protocols.clj#L12

So when ring-sse puts output-stream inside of the with-open block it will be "closed" twice and complete will be called twice causing the Exception.

I did not get the exception with the following changes.

dedeibel avatar Feb 06 '21 17:02 dedeibel