Search code examples
scalaakkaakka-streamakka-http

Why does it terminate immediately?


I have the following code and would like to understand, why it stops immediately, when I execute it:

    import akka.actor.ActorSystem
    import akka.http.scaladsl.Http
    import akka.http.scaladsl.model._
    import akka.http.scaladsl.server.Directives._
    import akka.stream.ActorMaterializer
    import scala.io.StdIn

    object WebServer {
      def main(args: Array[String]) {
    
        implicit val system = ActorSystem("my-system")
        implicit val materializer = ActorMaterializer()
        // needed for the future flatMap/onComplete in the end
        implicit val executionContext = system.dispatcher
    
        val route =
          path("hello") {
            get {
              complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
            }
          }
    
        val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
        bindingFuture
          .flatMap(_.unbind()) // trigger unbinding from the port
          .onComplete(_ => system.terminate()) // and shutdown when done
      }
    }
    

As you can see, it is a simple web application and it should not stop to run.
But when I remove the following code:

bindingFuture
  .flatMap(_.unbind()) // trigger unbinding from the port
  .onComplete(_ => system.terminate()) // and shutdown when done 
  

Then it runs forever until I terminated it.
The question is, why without

bindingFuture
  .flatMap(_.unbind()) // trigger unbinding from the port
  .onComplete(_ => system.terminate()) // and shutdown when done 
  

the application is not going to terminate?


Solution

  • Your bindingFuture gets completed in both cases. If you keep

    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done 
    

    Then unbind and terminate get run as soon as the server is listening, which causes your program to finish immediately.

    If you omit that code, then your program will keep running until all its threads are done. Because the actor system is running threads in the background and you never terminate it, this will never happen and you'll have to kill your program to stop it.

    One way to gracefully shutdown on any key press would be to use this (taken from official documentation)

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
    println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    StdIn.readLine() // let it run until user presses return
    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done
    

    In this case, the StdIn.readLine() will block the main thread until a key is pressed, at which point the actor system and the http server will be shut down.

    If you want to wait for a custom "event" rather than a keypress, you could simply wait on a custom Future like this:

    // Just build a Future that will get completed when you want to shutdown
    // This can be done manually using a `Promise[Unit]`, but you might also be using 
    // a library that will give you a Future to wait on.
    val myFuture: Future[Unit] = ??? 
    myFuture.flatMap(_ => bindingFuture)
        .flatMap(_.unbind())
        .onComplete(_ => system.terminate())