An Introduction to the Vert.x Context Object

Under the hood, the vert.x Con­text class plays a crit­i­cal part in main­tain­ing the thread-​safety guar­an­tees of ver­ti­cles. Most of the time, vert.x coders don’t need to make use of Con­text ob­jects di­rectly. How­ever, some­times you may need to. This ar­ti­cle pro­vides a brief in­tro­duc­tion to the vert.x Con­text class, which cov­ers why it’s im­por­tant, and why and when you might wish to make use of the Con­text di­rectly, based on the au­thor’s ex­pe­ri­ence of build­ing a generic async li­brary which can be used with vert.x.

This is a re-​publication of the fol­low­ing blog post

Introduction

Re­cently I’ve been look­ing at the pos­si­bil­ity of build­ing an asyn­chro­nous ver­sion of the pac4j li­brary, with a view to then mi­grat­ing the vertx-​pac4j im­ple­men­ta­tion to use the asyn­chro­nous ver­sion of pac4j by de­fault.

I’m keen (for ob­vi­ous rea­sons) that the async ver­sion of pac4j is not tightly cou­pled to one par­tic­u­lar asyn­chro­nous/non-​blocking frame­work, I de­cided to ex­pose the API via the Com­pletable­Fu­ture class, using this to wrap val­ues which will be de­ter­mined in the fu­ture. How­ever, I opted to use the vert.x frame­work for my asyn­chro­nous test­ing as a way of test­ing the API as it emerged. This in turn has led me to learn some as­pects of the vert.x Con­text class which I didn’t re­ally un­der­stand be­fore.

The in­for­ma­tion pre­sented re­lates to Vert.x ver­sion 3.3.3. It is con­ceiv­able that later ver­sions of vert.x could ren­der as­pects of this ar­ti­cle in­cor­rect.

Introduction to the Context class

When­ever a vert.x Han­dler is ex­e­cuted, or the start or step method of a ver­ti­cle is called, then that ex­e­cu­tion is as­so­ci­ated with a spe­cific con­text. Gen­er­ally a con­text is an event-​loop con­text and is there­fore as­so­ci­ated with an event loop thread (ex­cep­tions are cov­ered in the Fur­ther Read­ing ref­er­enced below). Con­texts are prop­a­gated. When a han­dler is set by code run­ning on a spe­cific con­text, then that han­dler will also be ex­e­cuted on the same con­text. This means for ex­am­ple, that if the start method of a ver­ti­cle in­stance sets a num­ber of event bus han­dlers (as many do), then they will all run on the same con­text as the start method for that ver­ti­cle (so all han­dlers for that ver­ti­cle in­stance will share a com­mon con­text).

A schematic of the re­la­tion­ships be­tween non-​worker ver­ti­cles, con­texts and event­loop threads is shown in Fig­ure 1.

Vertx Context/Thread/Verticle Relationships

Note that each ver­ti­cle ef­fec­tively has only one con­text for han­dlers cre­ated by its start method, and each con­text is bound to a sin­gle event-​loop thread. A given event-​loop thread can, how­ever, have mul­ti­ple con­texts bound to it.

When are contexts not propagated?

When a ver­ti­cle’s start method is called, a new con­text is cre­ated. If 4 iden­ti­cal ver­ti­cles are de­ployed via the in­stances pa­ra­me­ter on De­ploy­men­tOp­tions, the start method of each will be on a new con­text. This is log­i­cal as we may not want all non-​worker ver­ti­cles to be bound to a sin­gle event­loop thread when mul­ti­ple event­loop threads are avail­able.

Threading Guarantees

There are cer­tain con­se­quences of the prop­a­ga­tion of con­texts to han­dlers as men­tioned above. The most im­por­tant one is that since all han­dlers in a given event­loop ver­ti­cle run on the same con­text (the one on which its start method ran), they all run on the same event­loop thread. This gives rise to the thread­ing guar­an­tee within vert.x, that as long as a given ver­ti­cle is the only one to ever ac­cess a piece of state, then that state is being ac­cessed by only one thread, so no syn­chro­niza­tion will be nec­es­sary.

Exception Handling

Each con­text can have its own ex­cep­tion han­dler at­tached for han­dling ex­cep­tions which occur dur­ing event loop pro­cess­ing.

Why might you not want the default exception handler?

As one ex­am­ple, you might have some ver­ti­cles run­ning whose job it is to mon­i­tor other ver­ti­cles, and if some­thing ap­pears to go wrong with them, un­de­ploy and restart them, a fre­quent pat­tern in an actor-​ or microservices-​ style archic­tec­ture. So one op­tion could be that when a su­per­vised ver­ti­cle en­coun­ters an un­re­cov­er­able error, it could sim­ply no­tify its su­per­vi­sor that it has gone wrong via an event­bus mes­sage, and its su­per­vi­sor could then un­de­ploy and re­de­ploy (and after a num­ber of fail­ures in rapid suc­ces­sion pos­si­bly give up hope or es­ca­late to its own su­per­vi­sor.

Going off-context and getting back onto a particular context

There are sev­eral rea­sons why you might ex­e­cute code off-​context and then want to op­er­ate back on a vert.x con­text when com­plete. I’ll out­line a cou­ple of sce­nar­ios below

Running code on a separate thread

Firstly you might be using an asyn­chro­nous dri­ver which is en­tirely vertx-​unaware. Its code will run on non-​eventloop threads but it’s pos­si­ble you may then want to use the re­sults of that code to up­date in­for­ma­tion within your ver­ti­cle. If you don’t get back onto the cor­rect con­text, you can’t make any guar­an­tees about thread-​safety, so your sub­se­quent pro­cess­ing needs to be run back on the cor­rect event­loop thread.

Using asynchronous Java 8 APIs

APIs such as Com­pletable­Fu­ture are context-​unaware. In one ex­am­ple, I cre­ated an al­ready com­pleted fu­ture on the vert.x event loop in a test. I then at­tached sub­se­quent pro­cess­ing to it via then run:

@RunWith(VertxUnitRunner.class)
public class ImmediateCompletionTest {
    @Rule
    public final RunTestOnContext rule = new RunTestOnContext();

    @Test
    public void testImmediateCompletion(TestContext context) {

        final Async async = context.async();
        final Vertx vertx = rule.vertx();
        final CompletableFuture<Integer> toComplete = new CompletableFuture<>();
        // delay future completion by 500 ms
        final String threadName = Thread.currentThread().getName();
        toComplete.complete(100);
        toComplete.thenRun(() -> {
            assertThat(Thread.currentThread().getName(), is(threadName));
            async.complete();
        });
    }
}

Naively one might ex­pect this to au­to­mat­i­cally run on the con­text, since it hasn’t left the event­loop thread on which the fu­ture was com­pleted, and in­deed it’s prov­able that it is on the cor­rect thread. How­ever, it will not be on the cor­rect con­text. This would mean that it wouldn’t, for ex­am­ple, in­voke any mod­i­fied ex­cep­tion han­dler at­tached to the con­text.

Getting back on context

For­tu­nately, once we’ve left the con­text, it’s quite straight­for­ward to re­turn to it. Prior to de­f­i­n­i­tion of the code block within then­Run, we can use Vertx.cur­rent­Con­text() or vertx.getOr­Cre­ate­Con­text() to get a han­dle to the con­text on which our event­loop code is run­ning, We can then ex­e­cute the code block in­side a call to Con­text::runOn­Con­text, sim­i­lar to

final Context currentContext = vertx.getOrCreateContext();
toComplete.thenRun(() -> {
        currentContext.runOnContext(v -> {
        assertThat(Thread.currentThread().getName(), is(threadName));
        async.complete();
    }
});

While get­ting back onto the cor­rect con­text may not be crit­i­cal if you have re­mained on the event loop thread through­out, it is crit­i­cal if you are going to in­voke sub­se­quent vert.x han­dlers, up­date ver­ti­cle state or any­thing sim­i­lar, so it’s a sen­si­ble gen­eral ap­proach.

Further Reading

The vert.x team them­selves offer an ex­cel­lent blog about the Vert.x event­loop, with ex­cel­lent ma­te­r­ial on the con­text, on Github.

Thanks

Thanks very much to the vert.x core team for their clear github pages on the event­loop, and also to Alexan­der Lehmann for his an­swers to my stu­pid and naive ques­tions on the Vert.x google group.

Next post

Vert.x 3.4.0.Beta1 release

We have released 3.4.0.Beta1, this release is the biggest since Vert.x 3.0.0 with plenty of great features.

Read more
Previous post

Building services and APIs with AMQP 1.0

Microservices and APIs are everywhere. Everyone talks about them, presentation slides are full of them ... some people are actually even building them.

Read more
Related posts

Some Rest with Vert.x

This post is part of the Introduction to Vert.x series. Let’s go a bit further this time and develop a CRUD-ish application

Read more

My first Vert.x 3 Application

Let's say, you heard someone saying that Vert.x is awesome. Ok great, but you may want to try it by yourself. Well, the next natural question is “where do I start ?”

Read more

Real-time bidding with Websockets and Vert.x

The expectations of users for interactivity with web applications have changed over the past few years. Users during bidding in auction no longer want to press the refresh button.

Read more