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

CLI: Close subcommands in MultiCommand #28954

Merged
merged 16 commits into from
Mar 15, 2018
Merged

Conversation

bizybot
Copy link
Contributor

@bizybot bizybot commented Mar 9, 2018

Changes are done in MultiCommand, to override the close method and calling close on each subcommand to release resources.

Yogesh Gaikwad added 2 commits March 9, 2018 12:42
… properly

Changes done to override close method and call close on subcommands.

Closes elastic#28953
@bizybot bizybot requested review from rjernst and jaymode March 9, 2018 03:20
@bizybot bizybot added :Core/Infra/Scripting Scripting abstractions, Painless, and Mustache v7.0.0 labels Mar 9, 2018
Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

I left a couple comments. Also, please reword the PR title to indicate what is changing, eg "CLI: Close subcommands in MultiCommand". And for future reference, there is no need to create an associated issue with a PR.


@Override
public void close() throws IOException {
if (subcommands.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think close() is the place to be erroring if subcommands were never setup. This would already happen in execute.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed the review comment, by removing checks in the close method. Thank you.

if (subcommands.isEmpty()) {
throw new IllegalStateException("No subcommands configured");
}
for (Command command : subcommands.values()) {
Copy link
Member

Choose a reason for hiding this comment

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

Please use IOUtils.close() instead of iterating over the subcommands, as this will ensure all subcommands are closed, even on failures.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Ryan, Not sure but is IOUtils from org.apache.lucene.util package? and can CLI be dependent on the Lucene utils? Please suggest so I can add the dependency. Thank you.

Copy link
Member

Choose a reason for hiding this comment

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

I forgot cli classes were moved out to their own jar. We definitely shouldn't add lucene core just to get IOUtils. But we do need to mimic the behavior here, otherwise one command failing to close could cause the others not to close.

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 I will take it up and add similar behavior as in IOUtils#close. Thank you.

@rjernst
Copy link
Member

rjernst commented Mar 9, 2018

One more thing: please add a test for this to MultiCommandTests.

@bizybot bizybot changed the title CLI Command: MultiCommand must close subcommands to release resources properly CLI: Close subcommands in MultiCommand Mar 9, 2018
@jasontedor jasontedor added :Delivery/Packaging RPM and deb packaging, tar and zip archives, shell and batch scripts and removed :Core/Infra/Scripting Scripting abstractions, Painless, and Mustache labels Mar 9, 2018
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra

throw new IllegalStateException("No subcommands configured");
}
for (Command command : subcommands.values()) {
if (command != null) {
Copy link
Member

@jasontedor jasontedor Mar 9, 2018

Choose a reason for hiding this comment

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

If a command is null there is a problem; let us not be lenient like this.

Yogesh Gaikwad added 2 commits March 13, 2018 08:40
Handling sub command close exceptions, so others can be closed
and then rethrow exception.
Adding unit tests.

Closes elastic#28953
@@ -74,4 +76,48 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception {
}
subcommand.mainWithoutErrorHandling(Arrays.copyOfRange(args, 1, args.length), terminal);
}

@Override
public void close() throws IOException, RuntimeException {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should copy this here. I think if we are going to copy it, it should be available everywhere and replace the usage of the Lucene IOUtils. I opened #29012.

Copy link
Contributor Author

@bizybot bizybot Mar 13, 2018

Choose a reason for hiding this comment

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

Thanks, Jason. I was not sure if we needed other utils but looks like from your description of the PR there are others who use the same across source code.

Just one question, around can CLI be dependent on elasticsearch-core? In current project dependencies, it is not listed. I can update my change to depend on the internal IOUtils.

@bizybot bizybot added the review label Mar 13, 2018
Copy link
Member

@jaymode jaymode left a comment

Choose a reason for hiding this comment

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

Left a few comments about the testing. I also added the v6.3.0 label as this should be backported to 6.x.

Command spySubCommand2 = spy(new DummySubCommand());
multiCommand.subcommands.put("command1", subCommand1);
multiCommand.subcommands.put("command2", spySubCommand2);
IOException ioe = null;
Copy link
Member

Choose a reason for hiding this comment

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

use expectThrows here instead of the try catch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thank you.

@@ -102,4 +110,32 @@ public void testSubcommandArguments() throws Exception {
assertFalse(output, output.contains("command1"));
assertTrue(output, output.contains("Arguments: [foo, bar]"));
}

public void testClose() throws Exception {
Command spySubCommand1 = spy(new DummySubCommand());
Copy link
Member

Choose a reason for hiding this comment

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

I like mockito when it is necessary but sometimes it isn't and I feel that way here. I'd suggest having an AtomicBoolean or AtomicInteger that gets modified in the close method of a anonymous Command class. Then you can assert on that value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, Thank you.

}

public void testCloseWhenSubCommandCloseThrowsException() throws Exception {
Command subCommand1 = mock(DummySubCommand.class);
Copy link
Member

Choose a reason for hiding this comment

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

same comment about not really needing mockito

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, Thank you.

@jaymode jaymode added the v6.3.0 label Mar 14, 2018
@bizybot
Copy link
Contributor Author

bizybot commented Mar 15, 2018

@elasticmachine Test this, please.

Copy link
Member

@jaymode jaymode left a comment

Choose a reason for hiding this comment

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

I left some more comments

public class MultiCommandTests extends CommandTestCase {

static class DummyMultiCommand extends MultiCommand {

AtomicBoolean closed = new AtomicBoolean();
Copy link
Member

Choose a reason for hiding this comment

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

make this field final and add a line after for readability

DummyMultiCommand() {
super("A dummy multi command", () -> {});
}
@Override
Copy link
Member

Choose a reason for hiding this comment

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

This is a personal preference but given that we're adding to this class, I find it hard to read without empty lines between methods/constructor/member fields. Can you please add them?

}

static class DummySubCommand extends Command {
AtomicBoolean throwsExceptionOnClose = new AtomicBoolean();
Copy link
Member

Choose a reason for hiding this comment

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

why does this need to be an AtomicBoolean? I think it can just be a final boolean

}

static class DummySubCommand extends Command {
AtomicBoolean throwsExceptionOnClose = new AtomicBoolean();
AtomicBoolean closed = new AtomicBoolean();
Copy link
Member

Choose a reason for hiding this comment

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

make this final

DummySubCommand() {
super("A dummy subcommand", () -> {});
}
DummySubCommand(boolean throwsExceptionOnClose) {
super("A dummy subcommand", () -> {});
this.throwsExceptionOnClose.compareAndSet(false, throwsExceptionOnClose);
Copy link
Member

Choose a reason for hiding this comment

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

when you use compareAndSet you need to check the value returned by this method. In this case there is something really wrong if it returns false.

}

public void testCloseWhenSubCommandCloseThrowsException() {
boolean throwExceptionWhenClosed = true;
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to test the other scenarios or add some randomization to this test in terms of which subcommand(s) throws an exception:

final boolean command1Throws = randomBoolean();
final boolean command2Throws = randomBoolean();
...
assertTrue(command1Throws ^ subCommand1.closed.get());
assertTrue(command2Throws ^ subCommand2.closed.get());

multiCommand.subcommands.put("command1", subCommand1);
multiCommand.subcommands.put("command2", subCommand2);
multiCommand.close();
assertTrue("MultiCommand must have been closed when close method is invoked", multiCommand.closed.get());
Copy link
Member

Choose a reason for hiding this comment

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

for me must have been closed points me in the wrong direction; I'd use should have been or was not closed

Copy link
Member

@rjernst rjernst left a comment

Choose a reason for hiding this comment

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

I left a couple more comments. No need from me for another review if you agree with them all. Thanks for all the iterations!

@Override
public void close() throws IOException {
super.close();
if (!this.closed.compareAndSet(false, true)) {
Copy link
Member

Choose a reason for hiding this comment

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

Typically we use == false for negations (much easier to spot when glancing through some code).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, sure. Thank you. I will disable this in sonarLint which I use where it flags it as minor when == false.

public void close() throws IOException {
super.close();
if (!this.closed.compareAndSet(false, true)) {
throw new IOException("DummyMultiCommand closed");
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be "DummyMultiCommand already closed"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

throw new IOException();
} else {
if (!this.closed.compareAndSet(false, true)) {
throw new IOException("DummySubCommand closed");
Copy link
Member

Choose a reason for hiding this comment

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

Same comments here as above.

multiCommand.subcommands.put("command2", subCommand2);
// verify exception is thrown, as well as other non failed sub-commands closed
// properly.
IOException ioe = expectThrows(IOException.class, () -> multiCommand.close());
Copy link
Member

Choose a reason for hiding this comment

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

fyi, () -> multiCommand.close() can be multiCommand:close


@Override
public void close() throws IOException {
if (throwsExceptionOnClose) {
Copy link
Member

Choose a reason for hiding this comment

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

If you have this condition+throw separate from setting closed (maybe rename closed to closeCalled) then you don't need the difficult to read xor logic in the last test.

@bizybot
Copy link
Contributor Author

bizybot commented Mar 15, 2018

@elasticmachine Test this, please.

Copy link
Member

@jaymode jaymode left a comment

Choose a reason for hiding this comment

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

I left a few minor comments, otherwise this LGTM. Once that's addressed and CI is green you should be good to merge this in!

public void close() throws IOException {
super.close();
if (this.closed.compareAndSet(false, true) == false) {
throw new IOException("DummyMultiCommand already closed");
Copy link
Member

Choose a reason for hiding this comment

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

IMO this is not really an issue with IO so IOException doesn't make much sense; how about we change this to an IllegalStateException?

@Override
public void close() throws IOException {
if (this.closeCalled.compareAndSet(false, true) == false) {
throw new IOException("DummySubCommand already closed");
Copy link
Member

Choose a reason for hiding this comment

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

same thing here, how about using IllegalStateException

@bizybot bizybot merged commit a685784 into elastic:master Mar 15, 2018
@bizybot bizybot deleted the fix/28953 branch March 15, 2018 22:59
martijnvg added a commit that referenced this pull request Mar 16, 2018
* es/master: (97 commits)
  Clarify requirements of strict date formats. (#29090)
  Clarify that dates are always rendered as strings. (#29093)
  Compilation fix for #29067
  [Docs] Fix link to Grok patterns (#29088)
  Store offsets in index prefix fields when stored in the parent field (#29067)
  Fix starting on Windows from another drive (#29086)
  Use removeTask instead of finishTask in PersistentTasksClusterService (#29055)
  Added minimal docs for reindex api in java-api docs
  Allow overriding JVM options in Windows service (#29044)
  Clarify how to set compiler and runtime JDKs (#29101)
  CLI: Close subcommands in MultiCommand (#28954)
  TEST: write ops should execute under shard permit (#28966)
  [DOCS] Add X-Pack upgrade details (#29038)
  Revert "Improve error message for installing plugin (#28298)"
  Docs: HighLevelRestClient#exists (#29073)
  Validate regular expressions in dynamic templates. (#29013)
  [Tests] Fix GetResultTests and DocumentFieldTests failures (#29083)
  Reenable LiveVersionMapTests.testRamBytesUsed on Java 9. (#29063)
  Mute failing GetResultTests and DocumentFieldTests
  Improve error message for installing plugin (#28298)
  ...
bizybot added a commit that referenced this pull request Mar 20, 2018
* CLI Command: MultiCommand must close subcommands to release resources properly

- Changes are done to override the close method and call close on subcommands using IOUtils#close
- Unit Test

Closes #28953
martijnvg added a commit that referenced this pull request Mar 21, 2018
* es/6.x: (46 commits)
  Docs: Add note about missing mapping for doc values field (#29036)
  [DOCS] Removed 6.1.4, 6.2.2, and 6.2.3 coming tags
  Remove BytesArray and BytesReference usage from XContentFactory (#29151)
  Fix BWC issue for PreSyncedFlushResponse
  Add pluggable XContentBuilder writers and human readable writers (#29120)
  Add unreleased version 6.2.4 (#29171)
  Add unreleased version 6.1.5 (#29168)
  Add a note about using the `retry_failed` flag before accepting data loss (#29160)
  Fix typo in percolate-query.asciidoc (#29155)
  Add release notes for 6.1.4 and 6.2.3
  Require HTTP::Tiny 0.070 for release notes script
  REST high-level client: add clear cache API (#28866)
  Relax remote check for bwc project checkouts (#28666)
  Set Java 9 checkstyle to depend on checkstyle conf (#28383)
  Docs: Add example of resetting index setting (#29048)
  Plugins: Fix module name conflict check for meta plugins (#29146)
  Build: Fix meta plugin bundled plugin names (#29147)
  Build: Simplify rest spec hack configuration (#29149)
  CLI: Close subcommands in MultiCommand (#28954)
  Build: Fix meta modules to not install as plugin in tests (#29150)
  ...
@mark-vieira mark-vieira added the Team:Delivery Meta label for Delivery team label Nov 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>bug :Delivery/Packaging RPM and deb packaging, tar and zip archives, shell and batch scripts Team:Delivery Meta label for Delivery team v6.3.0 v7.0.0-beta1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants