fabric8io / kubernetes-client

Java client for Kubernetes & OpenShift
http://fabric8.io
Apache License 2.0
3.42k stars 1.46k forks source link

Non-daemon threads prevents application from stopping #6464

Open Hronom opened 1 month ago

Hronom commented 1 month ago

Describe the bug

Seems like kubernetes-client uses non-daemon threads under the hood. This prevents application from being closed.

Essentially, in spring boot framework, there possibility to create beans and if beans has method close it will be automatically closed by framework as soon, as application start to shutdown. But when you use non daemon threads - it's never happen.

Please switch to usage of daemon threads, so they will not stop application from shutdown. This helps to use this client in more comfortable way.

Fabric8 Kubernetes Client version

6.13.4

Steps to reproduce

Create spring application with configuration class like this:

@Configuration
public class KubernetesClientConfiguration {

    @Bean
    public KubernetesClient kubernetesClient() {
        return new KubernetesClientBuilder().build();
    }
}

Run app and see that it not stopping.

Expected behavior

Application not blocked from stopping.

Runtime

Kubernetes (vanilla)

Kubernetes API Server version

1.25.3@latest

Environment

Linux

Fabric8 Kubernetes Client Logs

No response

Additional context

No response

Hronom commented 1 month ago

Workaround - create custom OkHttpClientFactory:

import io.fabric8.kubernetes.client.okhttp.OkHttpClientFactory;
import okhttp3.Dispatcher;
import okhttp3.internal.Util;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomOkHttpClientFactory extends OkHttpClientFactory {

    @Override
    protected Dispatcher initDispatcher() {
        // Inspired by https://github.com/open-telemetry/opentelemetry-java/issues/3900#issue-1056599664
        // Matches OkHttp defaults(check Dispatcher) except daemon=true instead of false
        ThreadPoolExecutor daemonExecutorService = new ThreadPoolExecutor(
                0,
                Integer.MAX_VALUE,
                60,
                TimeUnit.SECONDS,
                new SynchronousQueue<>(),
                Util.threadFactory("OkHttp Daemon Dispatcher", true)
        );

        Dispatcher dispatcher = new Dispatcher(daemonExecutorService);
        // websockets and long-running http requests count against this and eventually starve
        // the work that can be done
        dispatcher.setMaxRequests(Integer.MAX_VALUE);
        // long-running http requests count against this and eventually exhaust
        // the work that can be done
        dispatcher.setMaxRequestsPerHost(Integer.MAX_VALUE);
        return dispatcher;
    }
}

and then create client like this:

@Configuration
public class KubernetesClientConfiguration {

    @Bean
    public KubernetesClient kubernetesClient() {
        return new KubernetesClientBuilder()
                .withHttpClientFactory(new CustomOkHttpClientFactory())
                .build();
    }
}

After this everything fine.