Skip to content

Instantly share code, notes, and snippets.

@EECOLOR
Last active January 1, 2016 09:49
Show Gist options
  • Save EECOLOR/8127533 to your computer and use it in GitHub Desktop.
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/…
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)
}
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
}
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'.
@EECOLOR
Copy link
Author

EECOLOR commented Dec 29, 2013

Note that this does not work in all cases

@RichardBradley
Copy link

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.

@sslavic
Copy link

sslavic commented Jun 17, 2015

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