Last active
January 1, 2016 09:49
-
-
Save EECOLOR/8127533 to your computer and use it in GitHub Desktop.
A wrapper for Spray's Http server that performs graceful shutdown. The server can be shutdown from the outside by calling the `stop()` method of from the service by sending a message: `context.parent ! Server.Stop`. These conversations helped me find a solution: [How to stop SimpleRoutingApp without getting an error](https://groups.google.com/d/…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.qirx.browserTests | |
import scala.concurrent.Future | |
import scala.concurrent.Promise | |
import scala.concurrent.duration._ | |
import scala.util.Try | |
import akka.actor.Actor | |
import akka.actor.ActorRef | |
import akka.actor.ActorSystem | |
import akka.actor.Props | |
import akka.io.IO | |
import akka.pattern.AskSupport | |
import akka.util.Timeout | |
import spray.can.Http | |
class Server( | |
val host: String, val port: Int, | |
serviceFactory:Props) { | |
val system = ActorSystem("custom-server-system") | |
val server = system.actorOf( | |
Props(new ServerActor(serviceFactory)), "custom-server") | |
def start(): Future[Server.Stopped] = { | |
val closed = Promise[Server.Stopped] | |
server ! Server.Start(host, port, closed.complete) | |
implicit val ec = system.dispatcher | |
closed.future.map { _ => | |
system.shutdown() | |
system.awaitTermination() | |
} | |
} | |
def stop(): Unit = if (!system.isTerminated) server ! Server.Stop | |
} | |
object Server { | |
case class Start(host: String, port: Int, closedHandler: StopHandler) | |
case object Stop | |
type Stopped = Any | |
type StopHandler = Try[Any] => Unit | |
} | |
class ServerActor(serviceFactory:Props) extends Actor with AskSupport { | |
import context.system | |
val service = context.actorOf(serviceFactory, "custom-server-service") | |
def receive = stopped orElse unknown | |
val unknown: Receive = { | |
case unknown => println("ServerActor got unknown message: " + unknown) | |
} | |
var stopHandler: Server.StopHandler = null | |
val stopped: Receive = { | |
case Server.Start(host, port, handler) => | |
stopHandler = handler | |
IO(Http) ! Http.Bind(service, host, port) | |
become(starting) | |
} | |
def starting: Receive = { | |
case Http.Bound(address) => | |
become(started(listener = sender)) | |
} | |
def started(listener: ActorRef): Receive = { | |
case Server.Stop => | |
listener ! Http.Unbind | |
become(stopping) | |
} | |
def stopping: Receive = { | |
case Http.Unbound => | |
implicit val timeout = Timeout(1.second) | |
implicit val executor = system.dispatcher | |
(IO(Http) ? Http.CloseAll).onComplete(stopHandler) | |
stopHandler = null | |
become(stopped) | |
} | |
def become(receive: Receive) = context.become(receive orElse unknown) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def freePort = { | |
val socket = new ServerSocket(0) | |
val port = socket.getLocalPort | |
socket.close() | |
port | |
} | |
val server = new Server("localhost", freePort, akka.actor.Props(new Service)) | |
val stopped = server.start() | |
def awaitStopped = { | |
val timeout = 5.seconds | |
try Await.ready(stopped, timeout) | |
catch { | |
case t: TimeoutException => | |
println("server not shutdown not called within " + timeout) | |
} | |
} | |
try { | |
code(server, driver) | |
awaitStopped | |
} finally { | |
server.stop() | |
awaitStopped | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
It took me quite some time to figure out how to clear the log of dead letters. A few of those were: | |
Message [akka.dispatch.sysmsg.Terminate] from Actor[akka://test-server/user/IO-HTTP/listener-0/0#-1209567360] to Actor[akka://test-server/user/IO-HTTP/listener-0/0#-1209567360] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. | |
Message [spray.io.TickGenerator$Tick$] from Actor[akka://test-server/user/IO-HTTP/listener-0#1862665875] to Actor[akka://test-server/user/IO-HTTP/listener-0#1862665875] was not delivered. [2] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. | |
Message [akka.dispatch.sysmsg.DeathWatchNotification] from Actor[akka://test-server/user/IO-HTTP/listener-0/0#761610642] to Actor[akka://test-server/user/IO-HTTP/listener-0/0#761610642] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. | |
Message [akka.io.Tcp$Bound] from Actor[akka://test-server/user/IO-HTTP/listener-0#2088427833] to Actor[akka://test-server/deadLetters] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. |
It doesn't compile for me, line 30 for first gist snippet, for closed.complete parameter of Server.Start message: "Type mismatch, expected: Server.StopHandler, actual: (Try[Server.Stopped]) => closed.type"
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that this does not do a "graceful shutdown" in the sense of allowing open requests to finish before terminating the server.
See [https://groups.google.com/forum/#!msg/spray-user/Sfh8x5yWibU/I1gDhwoULYsJ] for a discussion of how to achieve that.