Spark Frameworkでstop()を呼ぶとInterruptedExceptionが発生して固まる

状況

Spark FrameworkというSinatraっぽい軽量サーバを使ってGroovyでWebAPIのサーバを書いてます。

sparkjava.com

サーバを停止する処理を呼ぶと停止処理中に固まってしまうという現象が起きてました。

実装

サーバを停止する方法は公式ドキュメントにある通り簡単なものです。

Stopping the Server

By calling the stop() method the server is stopped and all routes are cleared.

stop();

以下のようにAPIとして呼べるようにしてあります。

get "/stop", { req, res -> stop() }

が、以下のようなエラーが出てしまいます。

[Thread-1] INFO org.eclipse.jetty.util.log - Logging initialized @975ms
[Thread-1] INFO spark.webserver.JettySparkServer - == Spark has ignited ...
[Thread-1] INFO spark.webserver.JettySparkServer - >> Listening on 0.0.0.0:8010
[Thread-1] INFO org.eclipse.jetty.server.Server - jetty-9.3.2.v20150730
[Thread-1] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@3ab422de{HTTP/1.1,[http/1.1]}{0.0.0.0:8010}
[Thread-1] INFO org.eclipse.jetty.server.Server - Started @1089ms
[qtp1972788076-17] INFO spark.webserver.MatcherFilter - The requested route [/] has not been mapped in Spark
[qtp1972788076-20] INFO spark.webserver.JettySparkServer - >>> Spark shutting down ...
[qtp1972788076-20] INFO org.eclipse.jetty.server.ServerConnector - Stopped ServerConnector@3ab422de{HTTP/1.1,[http/1.1]}{0.0.0.0:8010}
[qtp1972788076-20] ERROR spark.webserver.JettySparkServer - stop failed
java.lang.InterruptedException
        at java.lang.Object.wait(Native Method)
        at java.lang.Thread.join(Thread.java:1253)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.doStop(QueuedThreadPool.java:161)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.stop(AbstractLifeCycle.java:89)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.stop(ContainerLifeCycle.java:143)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStop(ContainerLifeCycle.java:161)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStop(AbstractHandler.java:73)
        at org.eclipse.jetty.server.Server.doStop(Server.java:476)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.stop(AbstractLifeCycle.java:89)
        at spark.webserver.JettySparkServer.stop(JettySparkServer.java:148)
        at spark.SparkInstance.stop(SparkInstance.java:317)
        at spark.Spark.stop(Spark.java:987)
        at spark.Spark$stop$0.callStatic(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:56)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:194)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:198)
        at FooWebapiService$_main_closure5.doCall(fooWebapi.groovy:47)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
        at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1019)
        at groovy.lang.Closure.call(Closure.java:426)
        at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:53)
        at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:105)
        at com.sun.proxy.$Proxy5.handle(Unknown Source)
        at spark.RouteImpl$1.handle(RouteImpl.java:58)
        at spark.webserver.MatcherFilter.doFilter(MatcherFilter.java:162)
        at spark.webserver.JettyHandler.doHandle(JettyHandler.java:61)
        at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:189)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:119)
        at org.eclipse.jetty.server.Server.handle(Server.java:517)
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:302)
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:242)
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:245)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
        at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:75)
        at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceAndRun(ExecuteProduceConsume.java:213)
        at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:147)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:654)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:572)
        at java.lang.Thread.run(Thread.java:745)

修正

調べるとGitHubにIssueがありました

github.com

どうやら停止するときは別スレッドにしておくと良いらしい。なるほどー。
さっそく修正してみる。

// 別ThreadにしないとInterruptedExceptionが発生してしまう
get "/stop", { req, res -> Thread.start {stop()} }

直ったー。(^_^)v

[Thread-1] INFO org.eclipse.jetty.util.log - Logging initialized @899ms
[Thread-1] INFO spark.webserver.JettySparkServer - == Spark has ignited ...
[Thread-1] INFO spark.webserver.JettySparkServer - >> Listening on 0.0.0.0:8010
[Thread-1] INFO org.eclipse.jetty.server.Server - jetty-9.3.2.v20150730
[Thread-1] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@6591cb3b{HTTP/1.1,[http/1.1]}{0.0.0.0:8010}
[Thread-1] INFO org.eclipse.jetty.server.Server - Started @1015ms
[Thread-11] INFO spark.webserver.JettySparkServer - >>> Spark shutting down ...
[Thread-11] INFO org.eclipse.jetty.server.ServerConnector - Stopped ServerConnector@6591cb3b{HTTP/1.1,[http/1.1]}{0.0.0.0:8010}
[Thread-11] INFO spark.webserver.JettySparkServer - done