Skip to content

Commit 12eee12

Browse files
bizybotYogesh Gaikwad
authored and
Yogesh Gaikwad
committed
CLI: Close subcommands in MultiCommand (#28954)
* 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
1 parent 0bdc4ae commit 12eee12

File tree

3 files changed

+84
-2
lines changed

3 files changed

+84
-2
lines changed

server/cli/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ archivesBaseName = 'elasticsearch-cli'
3636

3737
dependencies {
3838
compile 'net.sf.jopt-simple:jopt-simple:5.0.2'
39+
compile "org.elasticsearch:elasticsearch-core:${version}"
3940
}
4041

4142
test.enabled = false

server/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919

2020
package org.elasticsearch.cli;
2121

22+
import java.io.Closeable;
23+
import java.io.IOException;
2224
import java.util.Arrays;
2325
import java.util.LinkedHashMap;
2426
import java.util.Map;
2527

2628
import joptsimple.NonOptionArgumentSpec;
2729
import joptsimple.OptionSet;
2830

31+
import org.elasticsearch.core.internal.io.IOUtils;
32+
2933
/**
3034
* A cli tool which is made up of multiple subcommands.
3135
*/
@@ -74,4 +78,10 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception {
7478
}
7579
subcommand.mainWithoutErrorHandling(Arrays.copyOfRange(args, 1, args.length), terminal);
7680
}
81+
82+
@Override
83+
public void close() throws IOException {
84+
IOUtils.close(subcommands.values());
85+
}
86+
7787
}

server/src/test/java/org/elasticsearch/cli/MultiCommandTests.java

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,57 @@
2222
import joptsimple.OptionSet;
2323
import org.junit.Before;
2424

25+
import java.io.IOException;
26+
import java.util.concurrent.atomic.AtomicBoolean;
27+
2528
public class MultiCommandTests extends CommandTestCase {
2629

2730
static class DummyMultiCommand extends MultiCommand {
31+
32+
final AtomicBoolean closed = new AtomicBoolean();
33+
2834
DummyMultiCommand() {
29-
super("A dummy multi command", () -> {});
35+
super("A dummy multi command", () -> {
36+
});
37+
}
38+
39+
@Override
40+
public void close() throws IOException {
41+
super.close();
42+
if (this.closed.compareAndSet(false, true) == false) {
43+
throw new IllegalStateException("DummyMultiCommand already closed");
44+
}
3045
}
3146
}
3247

3348
static class DummySubCommand extends Command {
49+
final boolean throwsExceptionOnClose;
50+
final AtomicBoolean closeCalled = new AtomicBoolean();
51+
3452
DummySubCommand() {
35-
super("A dummy subcommand", () -> {});
53+
this(false);
3654
}
55+
56+
DummySubCommand(final boolean throwsExceptionOnClose) {
57+
super("A dummy subcommand", () -> {
58+
});
59+
this.throwsExceptionOnClose = throwsExceptionOnClose;
60+
}
61+
3762
@Override
3863
protected void execute(Terminal terminal, OptionSet options) throws Exception {
3964
terminal.println("Arguments: " + options.nonOptionArguments().toString());
4065
}
66+
67+
@Override
68+
public void close() throws IOException {
69+
if (this.closeCalled.compareAndSet(false, true) == false) {
70+
throw new IllegalStateException("DummySubCommand already closed");
71+
}
72+
if (throwsExceptionOnClose) {
73+
throw new IOException("Error occurred while closing DummySubCommand");
74+
}
75+
}
4176
}
4277

4378
DummyMultiCommand multiCommand;
@@ -102,4 +137,40 @@ public void testSubcommandArguments() throws Exception {
102137
assertFalse(output, output.contains("command1"));
103138
assertTrue(output, output.contains("Arguments: [foo, bar]"));
104139
}
140+
141+
public void testClose() throws Exception {
142+
DummySubCommand subCommand1 = new DummySubCommand();
143+
DummySubCommand subCommand2 = new DummySubCommand();
144+
multiCommand.subcommands.put("command1", subCommand1);
145+
multiCommand.subcommands.put("command2", subCommand2);
146+
multiCommand.close();
147+
assertTrue("MultiCommand was not closed when close method is invoked", multiCommand.closed.get());
148+
assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closeCalled.get());
149+
assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closeCalled.get());
150+
}
151+
152+
public void testCloseWhenSubCommandCloseThrowsException() throws Exception {
153+
final boolean command1Throws = randomBoolean();
154+
final boolean command2Throws = randomBoolean();
155+
final DummySubCommand subCommand1 = new DummySubCommand(command1Throws);
156+
final DummySubCommand subCommand2 = new DummySubCommand(command2Throws);
157+
multiCommand.subcommands.put("command1", subCommand1);
158+
multiCommand.subcommands.put("command2", subCommand2);
159+
if (command1Throws || command2Throws) {
160+
// verify exception is thrown, as well as other non failed sub-commands closed
161+
// properly.
162+
IOException ioe = expectThrows(IOException.class, multiCommand::close);
163+
assertEquals("Error occurred while closing DummySubCommand", ioe.getMessage());
164+
if (command1Throws && command2Throws) {
165+
assertEquals(1, ioe.getSuppressed().length);
166+
assertTrue("Missing suppressed exceptions", ioe.getSuppressed()[0] instanceof IOException);
167+
assertEquals("Error occurred while closing DummySubCommand", ioe.getSuppressed()[0].getMessage());
168+
}
169+
} else {
170+
multiCommand.close();
171+
}
172+
assertTrue("SubCommand1 was not closed when close method is invoked", subCommand1.closeCalled.get());
173+
assertTrue("SubCommand2 was not closed when close method is invoked", subCommand2.closeCalled.get());
174+
}
175+
105176
}

0 commit comments

Comments
 (0)