detach

Executes the inner route inside a future.

Signature

def detach()(implicit ec: ExecutionContext): Directive0
def detach()(implicit refFactory: ActorRefFactory): Directive0
def detach(ec: ExecutionContext): Directive0

The signature shown is simplified, the real signature uses magnets. [1]

[1]See The Magnet Pattern for an explanation of magnet-based overloading.

Description

This directive needs either an implicit ExecutionContext (detach()) or an explicit one (detach(ec)).

Caution

It is a common mistake to access actor state from code run inside a future that is created inside an actor by accidentally accessing instance methods or variables of the actor that are available in the scope. This also applies to the detach directive if a route is run inside an actor which is the usual case. Make sure not to access any actor state from inside the detach block directly or indirectly.

A lesser known fact is that the current semantics of executing The Routing Tree encompasses that every route that rejects a request also runs the alternative routes chained with ~. This means that when a route is rejected out of a detach block, also all the alternatives tried afterwards are then run out of the future originally created for running the detach block and not any more from the original (actor) context starting the request processing. To avoid that use detach only at places inside the routing tree where no rejections are expected.

Example

val route =
  detach() {
    complete("Result") // route executed in future
  }
Get("/") ~> route ~> check {
  responseAs[String] === "Result"
}

This example demonstrates the effect of the note above:

/// / a custom directive to extract the id of the current thread
def currentThreadId: Directive1[Long] = extract(_ => Thread.currentThread().getId)
val route =
  currentThreadId { originThread =>
    path("rejectDetached") {
      detach() {
        reject()
      }
    } ~
    path("reject") {
      reject()
    } ~
    currentThreadId { alternativeThread =>
      complete(s"$originThread,$alternativeThread")
    }
  }

Get("/reject") ~> route ~> check {
  val Array(original, alternative) = responseAs[String].split(",")
  original === alternative
}
Get("/rejectDetached") ~> route ~> check {
  val Array(original, alternative) = responseAs[String].split(",")
  original !== alternative
}