On Using Vert.x Locals and Duplicated Contexts

974 views
Skip to first unread message

clement escoffier

unread,
Feb 7, 2022, 8:20:51 AM2/7/22
to Quarkus Development mailing list

Hello, 


Last week, I was made aware that Hibernate Reactive relies on "duplicated contexts", a Vertx 4 feature that allows storing variables safely in a reactive pipeline. 


Hibernate Reactive stores the session object in the context, meaning that the subsequent invocations happening in this context will use the same session. It's a bit like thread locals. 


So, what's the problem? It looks great!


When we did the Vertx 4 integration last year, I wondered about using this feature. I decided not to use it because it would require significant changes in all the extensions receiving "things" (requests, messages...).


Currently, only the HTTP server is dispatching on duplicated context, as the underlying Vertx HTTP server does. 

But, it's not the case with the messaging connectors and many other components. 


But, how does it work with the event loops?


In Vertx 3, the Vert.x Context was representing the event loop. So, multiple unrelated processing could use the same context when they run on the same event loop. When you do "context.runOnContext", it would run the action on the same context/event-loop. 


In Vertx 4, you still have the context, but a context object can be:

  • an event loop (it's a root context)

  • a local view of the event loop (it's a duplicated context)


The behavior of "context.runOnContext" depends on which context you are in:

  • in a root context, you will be run on the same event loop

  • in a duplicated context, you will be run in that context, and so has access to the local storage of this context. Note that you must NOT share this context with any unrelated processing in this case. 


Most extensions use the root context or ignore the difference between the root and duplicated contexts. But Hibernate Reactive requires to be run on duplicated contexts. Without it, it could leak the session between unrelated processing. 


So, it means:

  • we need to create duplicated contexts,

  • we need to dispatch method in that context,

  • we need to make sure that any unrelated processing runs in that context


So, that's the plan? 


First, be aware that despite the plan may look simple, it can lead to highly complex logic. 


When receiving something, you need to create a duplicated context with "((ContextInternal) Vertx.currentContext()).duplicate()". 

  1. Be aware that "currentContext" returns `null` if you are not on a Vertx thread. 

  2.  It's an internal API, so the cast is necessary.

  3. When dispatching a method, you need to be in the proper context. 

  4. If your extension can be invoked from multiple threads or contexts, you will need to dispatch the method on the right one. So, you will need to store the context somehow and go back to it.

  5. Be sure never to use a duplicated context to run unrelated tasks. It seems easy; it's not when you do streams as the Reactive Streams request protocol may lead to calling the processing of the next item on the previous context. 


At least reactive messaging, most probably gRPC, the Reactive Rest client, and Fault-Tolerance may need to handle this. 


The do's and don'ts


  • Do not use put/get/remove: these methods store data in the root context, which will leak. Do NOT use them.

  • Do not use “putLocal”, “getLocal”, and “removeLocal” from a root context, as they will be leaked. Unfortunately, there is no easy way to know if we are on the root or a view (except "instanceOf EventLoopContext")

  • Calling duplicate() on a duplicated context creates a sibling, not a child, and do NOT copy the data. So, do NOT recreate a duplicated context from a duplicated context as you will lose the previously saved data.

  • Only use “putLocal”, “getLocal”, and “removeLocal” on a view context. 

  • If you use “executeBlocking”, be aware that the context available in the blocking block is the proper context, even if you are not running on the event loop thread. However, be sure to call “executeBlocking” from the duplicated context. 


Summary

Until an extension switches to that model, it could be dangerous to use Hibernate Reactive with that extension. Once done, it will not only enable Hibernate Reactive but provide a way to propagate objects on isolated contexts.


I have started updating reactive messaging to use that mechanism (each message will get its duplicated context). But, as I said before, it's complicated, as the stream semantic goes a bit against that idea. I hope to have something by the end of this week. 


Clement

clement escoffier

unread,
Feb 7, 2022, 9:32:25 AM2/7/22
to Quarkus Development mailing list
Sorry, I just realized that I forgot one important aspect regarding thread safety.

I mentioned "executeBlocking" in my previous email. 
The fact that the blocking code uses the duplicated context means that you can have concurrent access to the object stored in the context. The context is backed by a ConcurrentHashMap, but you need to make sure that the stored objects are also thread-safe. 

Clement

Max Rydahl Andersen

unread,
Feb 8, 2022, 7:08:46 AM2/8/22
to clement escoffier, Quarkus Development mailing list

great writeup Clement - just so I grok it right; there isn't really any alternative
for any extension that is reactive and needs a shared state in a pipeline than to do
this, is there?


/max


--
You received this message because you are subscribed to the Google Groups "Quarkus Development mailing list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to quarkus-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/quarkus-dev/CAKW6fide9xOuNvdmfgCZWFBqoNCn_%3Dp3sV3gvW%3DGPV2%3DNz9YvA%40mail.gmail.com.

clement escoffier

unread,
Feb 9, 2022, 2:51:56 AM2/9/22
to Max Rydahl Andersen, Quarkus Development mailing list
Le mar. 8 févr. 2022 à 13:08, Max Rydahl Andersen <mand...@redhat.com> a écrit :

great writeup Clement - just so I grok it right; there isn't really any alternative
for any extension that is reactive and needs a shared state in a pipeline than to do
this, is there?

Mutiny has a built-in context propagation support that could be used. However, it would require re-implementing Hibernate Reactive to use Mutiny internally instead of CompletionStage. That alternative got discarded during my discussion with Sanne. 

Clement

clement escoffier

unread,
Feb 9, 2022, 10:22:31 AM2/9/22
to Max Rydahl Andersen, Quarkus Development mailing list
I just opened: https://github.com/quarkusio/quarkus/pull/23552.
It restricts access to the "context" methods, which would prevent some leaks (not all as you may use the wrong duplicated context). 

Clement
Reply all
Reply to author
Forward
0 new messages