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
}