Scala: Context propagation and async interceptors
- Langage dominant
- Java
- Étoiles
- 12.1k
- Forks
- 4k
- Merge moyen
- 2 j 17 h
- PR mergées (30 j)
- 37
Description
I want to pass some values from interceptor to rpc handler. I've read that this can be done with contexts. But the problem is, that my interceptor is asynchronous, i.e. it "waits" for the future to resolve before calling next listener. The context is lost in this situation. My code is in Scala:
```.scala
case class AsyncContextawareInterceptor[A](
f: Metadata ⇒ Future[Either[Status, (Context.Key[A], A)]]
)(implicit val system: ActorSystem)
extends ServerInterceptor
with AnyLogging {
import system.dispatcher
sealed trait Msg
case object HalfClose extends Msg
case object Cancel extends Msg
case object Complete extends Msg
case object Ready extends Msg
case class Message[T](msg: T) extends Msg
override def interceptCall[ReqT, RespT](call: ServerCall[ReqT, RespT],
headers: Metadata,
next: ServerCallHandler[ReqT, RespT]): ServerCall.Listener[ReqT] =
new ServerCall.Listener[ReqT] {
private val stash = new java.util.concurrent.ConcurrentLinkedQueue[Msg]()
private var interceptor: Option[ServerCall.Listener[ReqT]] = None
private def enqueueAndProcess(msg: Msg) =
if (interceptor.isDefined) processMessage(msg) else stash.add(msg)
private def processMessage(msg: Msg) = msg match {
case HalfClose ⇒ interceptor.foreach(_.onHalfClose)
case Cancel ⇒ interceptor.foreach(_.onCancel)
case Complete ⇒ interceptor.foreach(_.onComplete)
case Ready ⇒ interceptor.foreach(_.onReady)
case Message(msg: ReqT @unchecked) ⇒ interceptor.foreach(_.onMessage(msg))
}
private def processMessages() = while (!stash.isEmpty) {
Option(stash.poll).foreach(processMessage)
}
override def onHalfClose(): Unit = enqueueAndProcess(HalfClose)
override def onCancel(): Unit = enqueueAndProcess(Cancel)
override def onComplete(): Unit = enqueueAndProcess(Complete)
override def onReady(): Unit = enqueueAndProcess(Ready)
override def onMessage(message: ReqT): Unit = enqueueAndProcess(Message(message))
f(headers).map {
case Right((k, v)) ⇒
val context = Context.current.withValue(k, v)
interceptor = Some(Contexts.interceptCall(context, call, headers, next))
processMessages()
case Left(status) ⇒ call.close(status, new Metadata())
}.recover {
case t: Throwable ⇒
log.error(t, "AsyncContextawareInterceptor future failed")
call.close(Status.fromThrowable(t), new Metadata())
}
}
}
object AuthInterceptor {
val BOTID_CONTEXT_KEY: Context.Key[Int] = Context.key[Int]("botId")
val TOKEN_HEADER_KEY: Metadata.Key[String] = Metadata.Key.of[String]("token", Metadata.ASCII_STRING_MARSHALLER)
def authInterceptor(resolver: String ⇒ Future[Option[Int]])(implicit system: ActorSystem): ServerInterceptor =
AsyncContextawareInterceptor { metadata ⇒
import system.dispatcher
(for {
token ← OptionT.fromOption[Future](Option(metadata.get(TOKEN_HEADER_KEY)))
botId ← OptionT(resolver(token))
} yield botId).value.map {
case Some(id) ⇒ Right(BOTID_CONTEXT_KEY → id)
case None ⇒ Left(Status.PERMISSION_DENIED)
}
}
}
```
The problem is that BOTID_CONTEXT_KEY.get is `null` in RPC handler, even when the future was resolved and the not-null value was set.
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
Commencez par ServerInterceptor, Context.current et Contexts.interceptCall dans l’exemple, puis suivez comment le callback Future asynchrone atteint le gestionnaire RPC. Déterminez si le Context doit rester disponible après la résolution du Future et définissez l’achèvement comme le fait que BOTID_CONTEXT_KEY.get renvoie la valeur attendue dans le gestionnaire, tout en veillant à ce que les chemins d’échec et d’annulation continuent de fonctionner correctement.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- java, scala
- Domaine
- backend-api-design, distributed-systems
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- À l'abandon
- Clarté
- À clarifier
- Accessibilité débutants
- 35/100