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

Exec.exec returning Future and hiding Process as an implementation detail #1322

Merged
merged 2 commits into from
Dec 3, 2020

Conversation

netikras
Copy link
Contributor

@netikras netikras commented Oct 10, 2020

as per #1310

@k8s-ci-robot
Copy link
Contributor

Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please follow instructions at https://git.k8s.io/community/CLA.md#the-contributor-license-agreement to sign the CLA.

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify. Thanks.


Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. label Oct 10, 2020
@k8s-ci-robot
Copy link
Contributor

Welcome @netikras!

It looks like this is your first PR to kubernetes-client/java 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes-client/java has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Oct 10, 2020
@netikras
Copy link
Contributor Author

I signed it

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels Oct 10, 2020
@netikras
Copy link
Contributor Author

image
If only I knew what's wrong... :)

@brendandburns
Copy link
Contributor

You need to run mvn spotless:apply

package io.kubernetes.client.custom;

import java.io.InputStream;
import java.util.concurrent.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No wildcard imports please.

import java.io.InputStream;
import java.util.concurrent.*;

public class AsyncPump implements Runnable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick JavaDoc for the use of this class.

}

public AsyncPump stop() {
if (thread != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curly braces around all conditions.

consumer.accept(giveAway);
}
onFinish.run();
} catch (Exception e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catch specific exceptions.

} catch (Exception e) {
try {
errorHandler.accept(e);
} catch (Exception exception) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too

promise.complete(null);
}

public interface NoisyConsumer<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of these interfaces?

import java.io.Reader;
import java.io.UnsupportedEncodingException;

import java.io.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above no wildcard imports please.

Copy link
Member

@yue9944882 yue9944882 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

can you format the code by running mvn spotless:apply?

@netikras
Copy link
Contributor Author

Absolutely. Done

io.setStdout(process.getInputStream());
io.setStderr(process.getErrorStream());
Thread processWaitingThread =
new Thread(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is going to be hard to test because of this. Can you use an ExecutorService instead? That can be injected in the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing!

  private void runAsync(String taskName, Runnable task) {
    if (this.executorService == null) {
      Thread thread = new Thread(task);
      thread.setName(taskName);
      thread.start();
    } else {
      executorService.submit(task);
    }
  }

Will this cut it? Using an ExecutorService if there is supplied one, and falling back to standalone Thread if there isn't one. And a simple Exec.setExecutorService() to supply the executor. Although the executor would also be used in this sole method (unless reused somewhere else later on).

I really don't feel like adding yet another parameter to the method, since there already are 8+ and the ES would be mostly optional.

Another option - use a protected field. or even a protected runAsync(String taskName, Runnable task){...}, which could be overridden in tests. Which approach would you prefer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is fine. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which is "this"? :)
I have gone with the protected runAsync(String taskName, Runnable task){...} (seemed more appropriate). Shall I leave it or replace it with the code block in my comment above?

@@ -47,6 +54,7 @@
private static final Logger log = LoggerFactory.getLogger(Exec.class);

private ApiClient apiClient;
private long graceTimeoutMs = 5_000L;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is only used in one method, make this a parameter instead? (or delete)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll delete it then.

log.warn("Process timed out after {} ms. Shutting down: {}", timeoutMs, cmdStr);
process.destroy();
try {
exited = process.waitFor(graceTimeoutMs, TimeUnit.MILLISECONDS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little weird to wait after the user specified timeout. I think we should delete all of the grace period logic. The user should just pass in whatever timeout they want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you ever closely monitored a Tomcat process shutting down? :) It takes a few seconds to clear all the pools, close all the connections, commit all the remaining transactions after the JVM receives a signal #15. Similar delays are observed in multi-classloader applications (e.g. osgi-based). Shutting down the application gracefully might take a tad more than just a heartbeat.

In line 303 I'm (hopefully) sending a sigterm, so the application starts its shutdown sequence. Some applications might take considerable amounts of time to shutdown (e.g. a JVM with a debugger attached), thus I cannot guarantee the process will be stopped immediately after the timeout in line #295. Telling the caller the process has stopped could lead to resources' exhaustion in some scenarios (i.e. BUGS). So I'm giving the process a chance to finish its business itself in 5 seconds (by default). If it fails to do so (could be many reasons), then I'm using my last card - sending (hopefully) a sigkill.

Sending a sigkill must always be the last resort.

I'd really like to keep the grace period in the code. I can hardcode the grace period if you'd like me to (could be extracted later on if required), but I'd sleep better knowing I've pushed the correct process termination procedure to a public repo :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this isn't a real process, it's a remote process running on a different service in a container on the cluster, so I don't think that stuff applies here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the obvious consequence of Liskov's principle violation :) Frankly, I don't know what does this client does when I ask it to terminate. So which one does reliably destroy the connection and remote process? destroy() or destroyForcibly()? I'll use the one you name and remove the grace period then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destroy() does the right thing here (it's not 100% 'reliable' in the sense that nothing in distributed systems is 100% "reliable"), but destroy closes the web socket connection which should have the net effect of stopping the process on the remote server (eventually), but from the client's perspective, it's terminated once the socket is closed.

Code is here:
https://github.com/kubernetes-client/java/blob/master/util/src/main/java/io/kubernetes/client/Exec.java#L476

@brendandburns
Copy link
Contributor

This looks good to me, can you add a unit test and squash the commits? Then it will be ready to merge.

Thanks!

@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Nov 21, 2020
Copy link
Member

@yue9944882 yue9944882 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/LGTM

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 23, 2020
Copy link
Member

@yue9944882 yue9944882 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/approve

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: netikras, yue9944882

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Dec 3, 2020
@k8s-ci-robot k8s-ci-robot merged commit edfdfcc into kubernetes-client:master Dec 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants