Added interruption flag

Signed-off-by: Emmanuel Bigeon <emmanuel@bigeon.fr>
This commit is contained in:
Emmanuel Bigeon 2018-12-02 13:33:15 -05:00
parent 79ee5394d4
commit 901469792d
6 changed files with 92 additions and 44 deletions

View File

@ -49,7 +49,7 @@
<dependency> <dependency>
<groupId>net.bigeon</groupId> <groupId>net.bigeon</groupId>
<artifactId>gclc</artifactId> <artifactId>gclc</artifactId>
<version>2.0.12</version> <version>2.1.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.bigeon.test</groupId> <groupId>net.bigeon.test</groupId>

View File

@ -68,6 +68,7 @@ package net.bigeon.gclc.process.io;
* #L% * #L%
*/ */
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.function.Supplier; import java.util.function.Supplier;
import net.bigeon.gclc.manager.ConsoleInput; import net.bigeon.gclc.manager.ConsoleInput;
@ -82,25 +83,27 @@ public final class ConnectingConsoleInput implements ConsoleInput {
private static final ConstantString EMPTY_STRING = new ConstantString(""); private static final ConstantString EMPTY_STRING = new ConstantString("");
/** If the input is closed. */ /** If the input is closed. */
private boolean close = false; private boolean close = false;
/** The prompt string. */ /** The prompt string. */
private Supplier<String> prompt = EMPTY_STRING; private Supplier<String> prompt = EMPTY_STRING;
/** If the input is currently in prompting state. /** If the input is currently in prompting state.
* <p> * <p>
* To change it you should be in a promptLock. */ * To change it you should be in a promptLock. */
private boolean prompting = false; private boolean prompting = false;
/** The synchronization lock for the prompting status. */ /** The synchronization lock for the prompting status. */
private final Object promptLock = new Object(); private final Object promptLock = new Object();
/** The synchronization lock for the connection status. */ /** The synchronization lock for the connection status. */
private final Object connectionLock = new Object(); private final Object connectionLock = new Object();
/** The connected console input. /** The connected console input.
* <p> * <p>
* To use it, you should be in a promptLock and connectionLock. */ * To use it, you should be in a promptLock and connectionLock. */
private ConsoleInput connected = null; private ConsoleInput connected = null;
/** The connection state. /** The connection state.
* <p> * <p>
* To read or modify it, you should be in a connectionLock synchronize block. */ * To read or modify it, you should be in a connectionLock synchronize block. */
private boolean disconnection = false; private boolean disconnection = false;
private boolean interrupting = false;
/* (non-Javadoc) /* (non-Javadoc)
* @see fr.bigeon.gclc.manager.ConsoleInput#close() */ * @see fr.bigeon.gclc.manager.ConsoleInput#close() */
@ -149,6 +152,9 @@ public final class ConnectingConsoleInput implements ConsoleInput {
public void interruptPrompt() { public void interruptPrompt() {
synchronized (connectionLock) { synchronized (connectionLock) {
synchronized (promptLock) { synchronized (promptLock) {
if (prompting) {
interrupting = true;
}
connectionLock.notifyAll(); connectionLock.notifyAll();
if (connected != null) { if (connected != null) {
connected.interruptPrompt(); connected.interruptPrompt();
@ -201,14 +207,21 @@ public final class ConnectingConsoleInput implements ConsoleInput {
actualConnected = connected; actualConnected = connected;
} }
if (connect) { if (connect) {
final String res = actualConnected.prompt(message); try {
synchronized (promptLock) { final String res = actualConnected.prompt(message);
synchronized (promptLock) {
if (prompting) {
return res;
}
}
} catch (final InterruptedIOException e) {
// The inner console was interrupted. This can mean we are
// disconnecting or actually interrupted.
if (disconnection) { if (disconnection) {
disconnection = false; disconnection = false;
} else if (prompting) {
return res;
} else { } else {
// prompt interrupted, lose the result. interrupting = false;
throw e;
} }
} }
} }
@ -241,14 +254,21 @@ public final class ConnectingConsoleInput implements ConsoleInput {
} }
if (connect) { if (connect) {
synchronized (promptLock) { synchronized (promptLock) {
final String res = actualConnected.prompt(message, try {
end - System.currentTimeMillis()); final String res = actualConnected.prompt(message,
if (disconnection) { end - System.currentTimeMillis());
disconnection = false; if (prompting) {
} else if (prompting) { return res;
return res; }
} else { } catch (final InterruptedIOException e) {
// prompt interrupted, lose the result. // The inner console was interrupted. This can mean we are
// disconnecting or actually interrupted.
if (disconnection) {
disconnection = false;
} else {
throw e;
}
} }
} }
} }
@ -257,7 +277,7 @@ public final class ConnectingConsoleInput implements ConsoleInput {
} }
private void getConnection(final long timeout) { private void getConnection(final long timeout) throws InterruptedIOException {
boolean connect; boolean connect;
synchronized (connectionLock) { synchronized (connectionLock) {
connect = connected != null; connect = connected != null;
@ -268,6 +288,10 @@ public final class ConnectingConsoleInput implements ConsoleInput {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
if (interrupting) {
interrupting = false;
throw new InterruptedIOException("Prompt ws interrupted");
}
} }
} }

View File

@ -12,14 +12,16 @@ import org.junit.Test;
import net.bigeon.gclc.exception.CommandRunException; import net.bigeon.gclc.exception.CommandRunException;
import net.bigeon.gclc.exception.CommandRunExceptionType; import net.bigeon.gclc.exception.CommandRunExceptionType;
import net.bigeon.gclc.utils.PipedConsoleInput; import net.bigeon.gclc.manager.PipedConsoleInput;
import net.bigeon.gclc.utils.PipedConsoleOutput; import net.bigeon.gclc.manager.PipedConsoleOutput;
/** @author Emmanuel Bigeon */ /** @author Emmanuel Bigeon */
public class ForkTaskTest { public class ForkTaskTest {
@Test @Test
public void testGenericForkTask() throws IOException { public void testGenericForkTask() throws IOException {
// FIXME This test fail to complete on Jenkins.
final ForkTask task = new ForkTask(5) { final ForkTask task = new ForkTask(5) {
@Override @Override
@ -36,15 +38,11 @@ public class ForkTaskTest {
throw new CommandRunException(CommandRunExceptionType.INTERACTION, throw new CommandRunException(CommandRunExceptionType.INTERACTION,
"Unable to prompt user"); "Unable to prompt user");
} }
if ("ok".equals(msg)) { out.println(msg);
out.println("Message");
} else {
out.println("fail");
}
} }
}; };
final Thread execThread = new Thread(task); final Thread execThread = new Thread(task, "Task");
execThread.start(); execThread.start();
try { try {
execThread.join(100); execThread.join(100);
@ -55,11 +53,11 @@ public class ForkTaskTest {
try (PipedConsoleOutput pco = new PipedConsoleOutput(); try (PipedConsoleOutput pco = new PipedConsoleOutput();
PipedConsoleInput pci = new PipedConsoleInput(null)) { PipedConsoleInput pci = new PipedConsoleInput(null)) {
pci.type("ok");
while (!pco.available()) { while (!pco.available()) {
pci.type("ok");
task.join(pco, pci, 1000); task.join(pco, pci, 1000);
} }
assertEquals("Execution should work", "Message", pco.readNextLine()); assertEquals("Positive execution", "ok", pco.readNextLine());
} }
assertFalse("Running state should be updated by task on its completion", assertFalse("Running state should be updated by task on its completion",
task.isRunning()); task.isRunning());

View File

@ -14,8 +14,8 @@ import org.junit.Test;
import net.bigeon.gclc.exception.CommandRunException; import net.bigeon.gclc.exception.CommandRunException;
import net.bigeon.gclc.exception.CommandRunExceptionType; import net.bigeon.gclc.exception.CommandRunExceptionType;
import net.bigeon.gclc.manager.PipedConsoleOutput;
import net.bigeon.gclc.process.mocks.TaskMock; import net.bigeon.gclc.process.mocks.TaskMock;
import net.bigeon.gclc.utils.PipedConsoleOutput;
/** @author Emmanuel Bigeon */ /** @author Emmanuel Bigeon */
public class ProcessListTest { public class ProcessListTest {

View File

@ -4,8 +4,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -13,9 +15,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test; import org.junit.Test;
import net.bigeon.gclc.manager.PipedConsoleInput;
import net.bigeon.gclc.manager.StreamConsoleInput;
import net.bigeon.gclc.tools.ConstantString; import net.bigeon.gclc.tools.ConstantString;
import net.bigeon.gclc.utils.PipedConsoleInput;
import net.bigeon.gclc.utils.StreamConsoleInput;
import net.bigeon.test.junitmt.ATestRunnable; import net.bigeon.test.junitmt.ATestRunnable;
import net.bigeon.test.junitmt.FunctionalTestRunnable; import net.bigeon.test.junitmt.FunctionalTestRunnable;
import net.bigeon.test.junitmt.TestFunction; import net.bigeon.test.junitmt.TestFunction;
@ -81,10 +83,26 @@ public class ConnectingConsoleInputTest {
@Override @Override
public void apply() throws Exception { public void apply() throws Exception {
assertNull("Interrupted should return null", in.prompt("m1", -1)); try {
assertNull("Interrupted should return null", in.prompt("m2", 5000)); in.prompt("m1", -1);
ended.set(true); fail("interruption of infinite waiting prompt should cause error");
assertNull("Overtime should return null", in.prompt("m3", 200)); } catch (final InterruptedIOException e) {
// ok
}
try {
in.prompt("m2", 5000);
fail("interruption of finite waiting prompt should cause error");
} catch (final InterruptedIOException e) {
// ok
}
synchronized (ended) {
ended.set(true);
try {
assertNull("Overtime should return null", in.prompt("m3", 200));
} catch (final InterruptedIOException e) {
fail("Unexpected interruption error in overtime");
}
}
} }
}; };
final ATestRunnable runnable = new FunctionalTestRunnable(one); final ATestRunnable runnable = new FunctionalTestRunnable(one);
@ -95,12 +113,16 @@ public class ConnectingConsoleInputTest {
public void run() { public void run() {
while (!ended.get()) { while (!ended.get()) {
try { try {
th.join(500); th.join(100);
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
in.interruptPrompt(); synchronized (ended) {
if (!ended.get()) {
in.interruptPrompt();
}
}
} }
} }
}); });
@ -112,7 +134,7 @@ public class ConnectingConsoleInputTest {
final PipedOutputStream os = new PipedOutputStream(); final PipedOutputStream os = new PipedOutputStream();
final PipedInputStream pis = new PipedInputStream(os); final PipedInputStream pis = new PipedInputStream(os);
in.connect(new StreamConsoleInput(System.out, pis, StandardCharsets.UTF_8)); in.connect(new StreamConsoleInput(null, pis, StandardCharsets.UTF_8));
final ATestRunnable runnable2 = new FunctionalTestRunnable(one); final ATestRunnable runnable2 = new FunctionalTestRunnable(one);
final Thread th2 = new Thread(runnable2); final Thread th2 = new Thread(runnable2);
final Thread inter2 = new Thread(new Runnable() { final Thread inter2 = new Thread(new Runnable() {
@ -121,12 +143,16 @@ public class ConnectingConsoleInputTest {
public void run() { public void run() {
while (!ended.get()) { while (!ended.get()) {
try { try {
th2.join(500); th2.join(100);
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
in.interruptPrompt(); synchronized (ended) {
if (!ended.get()) {
in.interruptPrompt();
}
}
} }
} }
}); });

View File

@ -8,7 +8,7 @@ import java.io.IOException;
import org.junit.Test; import org.junit.Test;
import net.bigeon.gclc.utils.PipedConsoleOutput; import net.bigeon.gclc.manager.PipedConsoleOutput;
public class ConnectingConsoleOutputTest { public class ConnectingConsoleOutputTest {