Scary looking stacktrace found in test logs
Nothing failed, so I suspect this is a racy shutdown:
16:14:21.837 [anInnocuousThread] ERROR org.http4s.blaze.util.Execution - Trampoline EC Exception caught
java.lang.IllegalStateException: Promise already completed.
at scala.concurrent.Promise.complete(Promise.scala:49)
at scala.concurrent.Promise.complete$(Promise.scala:48)
at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:183)
at scala.concurrent.Promise.failure(Promise.scala:100)
at scala.concurrent.Promise.failure$(Promise.scala:100)
at scala.concurrent.impl.Promise$DefaultPromise.failure(Promise.scala:183)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.run(Execution.scala:108)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.execute(Execution.scala:94)
at org.http4s.blaze.util.Execution$$anon$2.execute(Execution.scala:53)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.link(Promise.scala:337)
at scala.concurrent.impl.Promise$DefaultPromise.linkRootOf(Promise.scala:322)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:39)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.run(Execution.scala:108)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.execute(Execution.scala:94)
at org.http4s.blaze.util.Execution$$anon$2.execute(Execution.scala:53)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at scala.concurrent.Promise.trySuccess(Promise.scala:90)
at scala.concurrent.Promise.trySuccess$(Promise.scala:90)
at scala.concurrent.impl.Promise$DefaultPromise.trySuccess(Promise.scala:183)
at org.http4s.blaze.channel.nio2.ByteBufferHead$$anon$3.completed(ByteBufferHead.scala:114)
at org.http4s.blaze.channel.nio2.ByteBufferHead$$anon$3.completed(ByteBufferHead.scala:100)
at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishRead(UnixAsynchronousSocketChannelImpl.java:430)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:191)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:301)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
at sun.misc.InnocuousThread.run(InnocuousThread.java:74)
16:14:21.846 [anInnocuousThread] ERROR org.http4s.blaze.util.Execution - Trampoline EC Exception caught
java.lang.IllegalStateException: Promise already completed.
at scala.concurrent.Promise.complete(Promise.scala:49)
at scala.concurrent.Promise.complete$(Promise.scala:48)
at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:183)
at scala.concurrent.Promise.failure(Promise.scala:100)
at scala.concurrent.Promise.failure$(Promise.scala:100)
at scala.concurrent.impl.Promise$DefaultPromise.failure(Promise.scala:183)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.run(Execution.scala:108)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.execute(Execution.scala:94)
at org.http4s.blaze.util.Execution$$anon$2.execute(Execution.scala:53)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.link(Promise.scala:337)
at scala.concurrent.impl.Promise$DefaultPromise.linkRootOf(Promise.scala:322)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:39)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.run(Execution.scala:108)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.execute(Execution.scala:94)
at org.http4s.blaze.util.Execution$$anon$2.execute(Execution.scala:53)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at scala.concurrent.Promise.trySuccess(Promise.scala:90)
at scala.concurrent.Promise.trySuccess$(Promise.scala:90)
at scala.concurrent.impl.Promise$DefaultPromise.trySuccess(Promise.scala:183)
at org.http4s.blaze.channel.nio2.ByteBufferHead$$anon$3.completed(ByteBufferHead.scala:114)
at org.http4s.blaze.channel.nio2.ByteBufferHead$$anon$3.completed(ByteBufferHead.scala:100)
at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126)
at sun.nio.ch.Invoker$2.run(Invoker.java:218)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
at sun.misc.InnocuousThread.run(InnocuousThread.java:74)
16:14:21.848 [anInnocuousThread] ERROR org.http4s.blaze.util.Execution - Trampoline EC Exception caught
java.lang.IllegalStateException: Promise already completed.
at scala.concurrent.Promise.complete(Promise.scala:49)
at scala.concurrent.Promise.complete$(Promise.scala:48)
at scala.concurrent.impl.Promise$DefaultPromise.complete(Promise.scala:183)
at scala.concurrent.Promise.failure(Promise.scala:100)
at scala.concurrent.Promise.failure$(Promise.scala:100)
at scala.concurrent.impl.Promise$DefaultPromise.failure(Promise.scala:183)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.run(Execution.scala:108)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.execute(Execution.scala:94)
at org.http4s.blaze.util.Execution$$anon$2.execute(Execution.scala:53)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.link(Promise.scala:337)
at scala.concurrent.impl.Promise$DefaultPromise.linkRootOf(Promise.scala:322)
at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:39)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.run(Execution.scala:108)
at org.http4s.blaze.util.Execution$ThreadLocalTrampoline.execute(Execution.scala:94)
at org.http4s.blaze.util.Execution$$anon$2.execute(Execution.scala:53)
at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:68)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.$anonfun$tryComplete$1$adapted(Promise.scala:284)
at scala.concurrent.impl.Promise$DefaultPromise.tryComplete(Promise.scala:284)
at scala.concurrent.Promise.trySuccess(Promise.scala:90)
at scala.concurrent.Promise.trySuccess$(Promise.scala:90)
at scala.concurrent.impl.Promise$DefaultPromise.trySuccess(Promise.scala:183)
at org.http4s.blaze.channel.nio2.ByteBufferHead$$anon$3.completed(ByteBufferHead.scala:114)
at org.http4s.blaze.channel.nio2.ByteBufferHead$$anon$3.completed(ByteBufferHead.scala:100)
at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishRead(UnixAsynchronousSocketChannelImpl.java:430)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:191)
at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:301)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
at sun.misc.InnocuousThread.run(InnocuousThread.java:74)
It's only reproducible when running the tests test-only *ServerSpec in the blaze-http package, and it seems to pop every time. This consists of two different tests, Http1ServerSpec and Http2ServerSpec, both of which when run independently don't seem to have an issue.
A particularly interesting thing about that trace is that it seems that the ThreadLocalTrampoline has become re-entrant somehow.
It looks like the exception is happening on the client side of things: moving the server to NIO1 still shows the stack trace originating in nio2.
And since it's happening in those two tests, the HTTP/1 test uses the async-client, so it must be happening in the HTTP/2 client.
I've been able to change the ExecutionContext to directec in PriorKnowledgeHandshaker and get it to register in the stacktrace. There are a lot of flatMap operations in there so it seems consistent with where there may be an issue.
I'm stumped on this. I haven't found anything in the handshaker callpath that would try to force-set a promise again, and all the ones that use the tryComplete methods seem to be successfully setting the promise. I'm going to shelf this for now since it doesn't seem to be a real issue.