One would expect that this code:
unmarshaller.unmarshal(entity, materializer)
will perform the unmarshalling in the ExecutionContext of the specified materializer.
However, this is not the case!
If unmarshaller
was created using Unmarshaller.map()
function, then there is a case where unmarshalling is performed right in the caller thread — not using the provided ExecutionContext.
This is because of the usage of FastFuture
inside Unmarshaller
:
def map[C](f: B => C): Unmarshaller[A, C] =
transform(implicit ec => _ => _.fast map f)
FastFuture.map()
calls out to transformWith()
:
def transformWith[B](s: A => Future[B], f: Throwable => Future[B])(implicit executor: ExecutionContext): Future[B] = {
def strictTransform[T](x: T, f: T => Future[B]) =
try f(x)
catch { case NonFatal(e) => ErrorFuture(e) }
future match {
case FulfilledFuture(a) => strictTransform(a, s)
// ...
Here we go - on the last line strictTransform
is used, instead of performing the computation in the specified ExecutionContext.
I think that this is very confusing and non-intuitive. Why is FastFuture used in Unmarshaller.map
? Maybe Unmarshaller
should offer two versions — one where a map is trivial and uses FastFuture
, and another where it uses the regular Future
for the case where map
actually does the hard work — as is the case in our code:
Unmarshaller.forMediaType(MediaTypes.APPLICATION_JSON, Unmarshaller.entityToString())
.thenApply { s -> fromJSON(mapper, s, expectedType) }
1 post - 1 participant