Skip to content
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

Shutdown hook in VertxHttpClientFactory prevents further client usage #6792

Open
tdiesler opened this issue Jan 15, 2025 · 9 comments
Open

Comments

@tdiesler
Copy link

Describe the bug

This library should not assume that it is in charge of shutting down the Http connection. Instead, this should be left to the client app.
In camel k8s we have we have a 'run' option which can be stopped with ctrl+c, upon which all k8s resources are also deleted from the cluster.

This code causes regression, such that these resources can no longer be deleted when executing our shutdown hook ...

    public VertxHttpClientFactory() {
        this(VertxHttpClientFactory.VertxHolder.INSTANCE);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (this.vertx != null) {
                this.vertx.close();
            }

        }));
    }

I checked the other http client variants and it seems that vertx is the only one that installs a shutdown hook. Please consider removing this code and instead rely on higher layers to close the k8s client when appropriate.

Fabric8 Kubernetes Client version

7.0.1

Steps to reproduce

See: https://issues.apache.org/jira/browse/CAMEL-21621

Expected behavior

Using this k8s client in a shutdown hook should be valid

Runtime

Kubernetes (vanilla)

Kubernetes API Server version

1.25.3@latest

Environment

Linux

Fabric8 Kubernetes Client Logs

Additional context

No response

@tdiesler tdiesler changed the title Shutdown hook in VertxHttpClientFactory prevents client usage in other shutdown hook Shutdown hook in VertxHttpClientFactory prevents further client usage Jan 15, 2025
@manusa
Copy link
Member

manusa commented Jan 16, 2025

Hi @tdiesler

I understand that this issue is in scope of https://issues.apache.org/jira/browse/CAMEL-21625

The VertxHttpClientFactory provides two constructors:

A no args constructor:

And one that accepts your own Vertx instance as a parameter:

The shutdown hook is only added to the prior one.
This is mainly because it's assumed that the Kubernetes Client owns it and is responsible for its lifecycle.

AFAIU, it's expected that the Vertx instance is closed before the application is terminated.
I also assume that this is especially important in contexts such as the Kubernetes Client, where you might have dangling threads running when SIGTERM is received.

The only reliable and simple way I found out to ensure this behavior is by leveraging the shutdown hook. Since the factory can be instantiated multiple times and the Vertx instance is a singleton shared among all the factories (in case you use the default empty args constructor), having a callback to close the Vertx instance would be rather complicated. In addition, you would probably face the same issue.

The only problem is that this method is not compatible with having other shutdown hooks that use the client and the default constructor factory, since depending on which hook run firsts you might run into the problem you describe.

So unless there's another way or it's OK not to close the Vert.x instance, the only way is for you to use the factory with a Vertx instance that you manage yourself.

/cc @shawkins @vietj

@tdiesler
Copy link
Author

tdiesler commented Jan 16, 2025

I'd say, that this library cannot "own" the client instance and its dependent vertx instance. Instead, whoever creates the k8s client is also responsible of closing it appropriately, which may happen in a shutdown hook. This would also align the vertx client with how the other http client variants work.

In other words, this library cannot possibly know when the application is done with using the k8s client. The assumption that this is true when it's own shutdown hook is called, is not valid.

@manusa
Copy link
Member

manusa commented Jan 16, 2025

Instead, whoever creates the k8s client is also responsible of closing it appropriately, which may happen in a shutdown hook.

That's fine, but we're talking about the client factory here (which by extension also implies the constructed client instances, sure).

The problem is that the factory should close the resources it creates, in this case the Vertx instance.
And there's no feasible way to achieve this.

Your current proposal is to leave this Vertx instance open which might or might not have serious consequences downstream. But I'm not sure what the consequences of not closing the Vertx instance are.

I'm perfectly fine removing the hook, but I'd like to see the opinion on this matter from someone more knowledgeable on Vertx.

@tdiesler
Copy link
Author

tdiesler commented Jan 16, 2025

We use code like this ...

        var client = new KubernetesClientBuilder().withConfig(config).build();
        printClientInfo(client);

we are (or it least should be) unaware of the http implementation details of the returned client. We assume responsibility to close the client when done ... and would prefer the k8s library not to make that decision for us. Yes, please remove that hook if you can.

@manusa
Copy link
Member

manusa commented Jan 16, 2025

I know and I understand, but we're talking about the factory, not the client.

Removing the shutdown hook might have unexpected consequences.

The current approach is only problematic in case of users (like you) using the client in a shutdown hook.
It's easier/simpler to add a warning and encourage users to use their own factory (which is a very simple change) in this case, than to force the entire user-base to close a client factory they might not even be aware of.

As I said, if someone knowledgeable in Vert.x assures us that there is no problem in not closing the Vertx instance, then I'm all in favor of removing the shutdown hook.
Otherwise, we need to properly analyze the tradeoffs of the available approaches.

@tdiesler
Copy link
Author

tdiesler commented Jan 16, 2025

I'd say it is a common convention in Java that when you acquire a closable resource - you also close it.

Like so ....

        devModeShutdownTask = new Thread(() -> {
            try (var client = createKubernetesClient()) {
                ... do stuff
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
       Runtime.getRuntime().addShutdownHook(devModeShutdownTask);

The code is both simple and correct. However, Java does not allow to register additional shutdown hooks while already in shutdown. Therefore, it'd be wrong for a library to assume that it can create a shutdown hook.

In case a closable resource A is shared by multiple other closable resources B, you would need a usage counter in B i.e. the last B.close() would call A.close()

If you really must have that shutdown hook - how about, you ignore the exception thrown by Runtime.getRuntime().addShutdownHook() in the factory and document the intricate details of why you ignore it. Not a clean solution though, because it might ignore exceptions that should in fact be thrown to the caller.

@manusa
Copy link
Member

manusa commented Jan 16, 2025

I'd say it is a common convention in Java that when you acquire a closable resource - you also close it.

Yes. I insist, we're talking about the factory, which IS NOT closable and not exposed to the user.
The shutdown hook IS NOT closing the client or clients, but the underlying vertx instance (which obviously renders the underlying clients unusable).

If you really must have that shutdown hook - how about, you ignore the exception thrown by Runtime.getRuntime().addShutdownHook() in the factory and document the intricate details of why you ignore it. Not a clean solution though, because it might ignore exceptions that should in fact be thrown to the caller.

Yes, the idea of this tradeoff, is to make it as safe as possible.
And also document the warning and alternative path in case you need to use the client in a shutdown hook (your exposed use-case).

Either this or finding a better solution, but I'm out of ideas.

@tdiesler
Copy link
Author

Ok, here again for future reference ... this code is broken in the k8s client ...

new KubernetesClientBuilder().withConfig(config).build();

@manusa
Copy link
Member

manusa commented Jan 16, 2025

Another alternative is that the default factory creates a Vertx instance per KubernetesClient instance.
However, I'm not sure how this would affect the overall performance.

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

No branches or pull requests

2 participants