Skip to content

[DE-535] Provide the capability to reuse an existing Vertx instance #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jamesnetherton opened this issue May 9, 2024 · 24 comments · Fixed by #558
Closed

[DE-535] Provide the capability to reuse an existing Vertx instance #557

jamesnetherton opened this issue May 9, 2024 · 24 comments · Fixed by #558
Assignees

Comments

@jamesnetherton
Copy link

My project is based on a Framework (Quarkus) that uses Vert.x.

When I use the ArangoDB Java Driver to connect to a server I see:

WARN  [io.ver.cor.imp.VertxImpl] (executor-thread-1) You're already on a Vert.x context, are you sure you want to create a new Vertx instance?

It happens because HttpConnection, creates its own Vertx:

vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(true).setEventLoopPoolSize(1));

In my case, there's already an existing Vertx instance managed by Quarkus. So it'd be useful if there were some way to reuse it in the ArangoDB Java Driver.

@rashtao rashtao self-assigned this May 15, 2024
@rashtao rashtao changed the title Provide the capability to reuse an existing Vertx instance [DE-535] Provide the capability to reuse an existing Vertx instance May 17, 2024
@rashtao
Copy link
Collaborator

rashtao commented Jun 7, 2024

@jamesnetherton
Thanks for reporting this.
In the next release, the driver can be configured with the Vert.x instance to use, see related PR.

@andtur
Copy link

andtur commented Sep 2, 2024

Hi,

i have project that uses Vertx as framework. In one of the verticles I create a connection to arangoDB with java driver. With a configuration:
ArangoDB arango = new ArangoDB.Builder().host("localhost", 8529).user("some").password("some").build();
I get as expected:
WARNUNG: Found an existing Vert.x instance, you can reuse it by setting:
new ArangoDB.Builder()
// ...
.protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build())
.build();**

if I add .protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build()) to the build call, then my request:
arango.db("myDB").exists()
hangs without returning back.
What could be the problem?
Thank you

@rashtao
Copy link
Collaborator

rashtao commented Sep 3, 2024

Hi,
note that arango.db("myDB").exists() uses the blocking driver API.

Are you invoking it from a Vert.x thread? In such case, you would block the Vert.x thread at the calling site and the driver would have no executor thread available to perform the request.

Instead, from within Vert.x threads you need to use the async driver API:

ArangoDBAsync arango = new ArangoDB.Builder()
// ...
.protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build())
.build()
.async();

// ...

CompletableFuture<Boolean> existsCF = arango.db("myDB").exists();
// ...

Furthermore beware of the caveats of using CompletableFuture from within Vert.x threads, see https://github.com/cescoffier/vertx-completable-future

Or convert the CompletableFuture to Vert.x Future:

Future<Boolean> existsF = Future.fromCompletionStage(existsCF);
// ...

@andtur
Copy link

andtur commented Sep 3, 2024

Hi,

Thank you very much for your answer, but that does not explain why does it work, when you are creating a new vertx instance inside of HTTPConnection.

Regards,
Andrey

@rashtao
Copy link
Collaborator

rashtao commented Sep 3, 2024

The Vert.x instance created inside HTTPConnection is used exclusively from the driver connection and it is never blocked. In such case, you would have multiple Vert.x instances in your applications, each one using different threads. So if you use the blocking API in your Vert.x threads you do not prevent the driver from executing the request, which is executed on different threads. Note that even in this case, you would block Vert.x threads, thus preventing execution of other Vert.x tasks (not from the driver, but related to other components of your application).

On the other side, when reusing the existing Vert.x instance, the driver does not create new Vert.x instaces or new threads, but reuses the existing ones.

@andtur
Copy link

andtur commented Sep 3, 2024

Thank you very much for clarification. But that generally means, that if i use now the driver inside of my Vertx application and reuse the existing context, for each call to db (query, create, update) i need now create a Future? Before in my application I got a request, sent it over eventBus to DB vertical and there directly did a query.

@andtur
Copy link

andtur commented Sep 3, 2024

So if i have a simple application like:

public class MainVerticle extends AbstractVerticle {

@Override
public void start(Promise<Void> startPromise) throws Exception {
	ArangoDB arango = new ArangoDB.Builder().host("localhost", 8529).user("user").password("pwd")

.protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build()).build();
System.out.println(arango.db("myDB").exists());
}
}
I will have to pack now each execution of DB in blocking or Future?
That is not really practical, as the driver still returns boolean and not futures, so this:
CompletableFuture existsCF = arango.db("myDB").exists();
will not work.
And even this
ArangoDB arango = new ArangoDB.Builder().host("localhost", 8529).user("user").password("pwd").maxConnections(1)
.protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build())
.build();
ArangoDBAsync adbAsync = arango.async();
CompletableFuture version = adbAsync.db("myDB").exists();
hangs in the exists call.

Thank you,
Andrey

@rashtao
Copy link
Collaborator

rashtao commented Sep 4, 2024

I will have to pack now each execution of DB in blocking or Future?

No, using the async API of the driver you get CompletableFuture back.

ArangoDB arango = new ArangoDB.Builder().host("localhost", 8529).user("user").password("pwd").maxConnections(1)
.protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build())
.build();
ArangoDBAsync adbAsync = arango.async();
CompletableFuture version = adbAsync.db("myDB").exists();

this should work and not hang.
Are you sure it is hanging because the Vert.x thread is blocked? Can you please report the related thread dump or provide a code snippet to reproduce the issue?

@andtur
Copy link

andtur commented Sep 4, 2024

Yes, i am, as i get the following:
WARNUNG: Thread Thread[vert.x-eventloop-thread-1,5,main] has been blocked for 5620 ms, time limit is 2000 ms
io.vertx.core.VertxException: Thread blocked

Sure, i start it now directly from IDE with io.vertx.core.Launcher run var.verticles.MainVerticle
MainVerticle:
public class MainVerticle extends AbstractVerticle {

@Override
public void start(Promise<Void> startPromise) throws Exception {
	ArangoDB arango = new ArangoDB.Builder().host("localhost", 8529).user("user").password("pwd").maxConnections(1)
			.protocolConfig(HttpProtocolConfig.builder().vertx(Vertx.currentContext().owner()).build())
			.build();
	ArangoDBAsync adbAsync = arango.async();
	CompletableFuture ex = adbAsync.db("myDB").exists();
	// This works
	ex.whenComplete((res, exp)->{
		System.out.println(res);
	});
	// This, the same as you use in CommunicationProtocol, hangs
	ex.get();
	System.out.println("Done");
}

}

@rashtao
Copy link
Collaborator

rashtao commented Sep 4, 2024

You are blocking the thread in your code:

ex.get();

Note that CommunicationProtocol invokes .get() only if you are using the sync API.
When using the async API CommunicationProtocol#executeAsync() is used.

@andtur
Copy link

andtur commented Sep 4, 2024

Yes, i found it out two. But for all who used previous version in Vertx it will be quite a big change, even if you put aside all the changes to serializer.

@rashtao
Copy link
Collaborator

rashtao commented Sep 4, 2024

Using any blocking API from Vert.x would be a mistake. I.e. also using the driver synch API without reusing the existing Vert.x. Doing that you are actually still blocking the Vert.x threads. See https://vertx.io/docs/vertx-core/java/#_dont_block_me

@andtur
Copy link

andtur commented Sep 4, 2024

No, not really. It will also not work in the case that you have a HTTP Vertical that listens to a request, then send to eventBus and Database Vertical sends a request to DB and answers back. You will have to add also in this case an async call with all the callbacks. I just do not understand why to use Vertx at all just to add a WebClient.
Regards,
Andrey

@rashtao
Copy link
Collaborator

rashtao commented Sep 4, 2024

Also blocking the thread from an event bus handler would be a mistake. Maybe you are just fine with that, as long as you serve few requests and they take short time to execute.

If you like keeping this approach, you can just avoid reusing the Vert.x instance, i.e. do not configure .protocolConfig(...):

ArangoDB arango = new ArangoDB.Builder()
  .host("localhost", 8529)
  .user("user")
  .password("pwd")
  .maxConnections(1)
.build();

// use arango as before

This will create a new Vert.x instance internal to the driver which will not interfere with your one.

@andtur
Copy link

andtur commented Sep 4, 2024

OK, got it. Thank you for clarification.
But still, why did you decide to use Vertx, if you actually only use WebClient?

Regards,
Andrey

@rashtao
Copy link
Collaborator

rashtao commented Sep 4, 2024

You are welcome.
As we introduced it, we used Vert.x to enforce thread confinement of mutable variables. As of now, you are right, we could just use WebClient.

Note that this is currently just the internal implementation of the HttpConnection.
We are currently evaluating whether to introduce Vert.x API in the next major release, e.g. returning Vert.x Future from driver API, in addition to sync and async API.

Feel free to contribute with your feedback and ideas about it.

@andtur
Copy link

andtur commented Sep 4, 2024

Ah, got it. The problem is that the code with .protocolConfig does not work even if you use these:
A worker verticle is just like a standard verticle but it’s executed using a thread from the Vert.x worker thread pool, rather than using an event loop. Worker verticles are designed for calling blocking code, as they won’t block any event loops.

So even if you connect to DB from the worker verticle (which i do) i get the same probem.

Andrey

@rashtao
Copy link
Collaborator

rashtao commented Sep 4, 2024

Also in the case of worker verticles, the same consideration holds. When using the sync API, the thread will be blocked to wait the pending request to complete, i.e. in CommunicationProtocol#execute(). Therefore the thread cannot be used by the driver to perform the request.
Note that this happens also if you use in the worker verticle, for example, the WebClient directly and block to get the result synchronously, e.g:

webClient
                                        .get(...)
                                        .send()
                                        .toCompletionStage()
                                        .toCompletableFuture()
                                        .get(); 

@andtur
Copy link

andtur commented Sep 5, 2024

Hi, after reading a documentation a bit more and searching for best practice, i would disagree with you. Worker verticles are exactly designed for calling blocking code. And it allows you to control how many concurrent connection you would like to have in your application by deploying needed amount of verticles. So i would say it is a bug of the 7.7.0, that allowed to provide your own Vertx context. It should be possible just to use synchronous api inside of the worker verticle.

@rashtao
Copy link
Collaborator

rashtao commented Sep 5, 2024

You can try running my code from above in a worker verticle, you will see that it will hang forever.

webClient
                                        .get(...)
                                        .send()
                                        .toCompletionStage()
                                        .toCompletableFuture()
                                        .get(); 

@andtur
Copy link

andtur commented Sep 5, 2024

Yes, can be, but i do not really care about the webClient, i just want to open connection to a DB and execute synchronous request against it.
And please do not understand me wrong, it is not about me, just any person who is using Vertx (or comparable) will get the same problem

@rashtao
Copy link
Collaborator

rashtao commented Sep 5, 2024

Unfortunately this is not possible because of the way Vert.x works. If the user blocks the thread, then the same thread cannot be used by Vert.x! If you really want to do it, then you need 2 threads: one user thread which will be blocked on the calling site and another executing the request.

The example above about the WebClient is to explain how things are. Under the hood, the driver executes the requests with the Vert.x WebClient.

@andtur
Copy link

andtur commented Sep 8, 2024

@jamesnetherton how did you organize the requests?

@jamesnetherton
Copy link
Author

@jamesnetherton how did you organize the requests?

My use case is probably quite a bit different to yours. The context of mine is a Quarkus application that uses the Apache Camel ArangoDB component.

In Quarkus, there's a singleton Vertx CDI bean. Camel resolves it from the CDI bean factory and then passes it to the ArangoDB client builder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants