Implement a keep-alive strategy to detect stales connections, fixes #47

This commit is contained in:
Guillaume Nodet
2020-10-21 23:06:24 +02:00
parent 7dbb371a06
commit 22f608d5b4
19 changed files with 571 additions and 88 deletions

View File

@@ -38,6 +38,9 @@ public class ClientLayout extends Layout {
private final Path settings;
private final Path javaHome;
private final Path logbackConfigurationPath;
private final int idleTimeoutMs;
private final int keepAliveMs;
private final int maxLostKeepAlive;
public static ClientLayout getEnvInstance() {
if (ENV_INSTANCE == null) {
@@ -68,6 +71,21 @@ public class ClientLayout extends Layout {
.orFail()
.asPath()
.toAbsolutePath().normalize();
final int idleTimeoutMs = Environment.DAEMON_IDLE_TIMEOUT_MS
.systemProperty()
.orLocalProperty(mvndProperties, mvndPropertiesPath)
.orDefault(() -> Integer.toString(Environment.DEFAULT_IDLE_TIMEOUT))
.asInt();
final int keepAliveMs = Environment.DAEMON_KEEP_ALIVE_MS
.systemProperty()
.orLocalProperty(mvndProperties, mvndPropertiesPath)
.orDefault(() -> Integer.toString(Environment.DEFAULT_KEEP_ALIVE))
.asInt();
final int maxLostKeepAlive = Environment.DAEMON_MAX_LOST_KEEP_ALIVE
.systemProperty()
.orLocalProperty(mvndProperties, mvndPropertiesPath)
.orDefault(() -> Integer.toString(Environment.DEFAULT_MAX_LOST_KEEP_ALIVE))
.asInt();
ENV_INSTANCE = new ClientLayout(
mvndPropertiesPath,
mvndHome,
@@ -76,18 +94,23 @@ public class ClientLayout extends Layout {
Environment.findJavaHome(mvndProperties, mvndPropertiesPath),
findLocalRepo(),
null,
Environment.findLogbackConfigurationPath(mvndProperties, mvndPropertiesPath, mvndHome));
Environment.findLogbackConfigurationPath(mvndProperties, mvndPropertiesPath, mvndHome),
idleTimeoutMs, keepAliveMs, maxLostKeepAlive);
}
return ENV_INSTANCE;
}
public ClientLayout(Path mvndPropertiesPath, Path mavenHome, Path userDir, Path multiModuleProjectDirectory, Path javaHome,
Path localMavenRepository, Path settings, Path logbackConfigurationPath) {
Path localMavenRepository, Path settings, Path logbackConfigurationPath, int idleTimeoutMs, int keepAliveMs,
int maxLostKeepAlive) {
super(mvndPropertiesPath, mavenHome, userDir, multiModuleProjectDirectory);
this.localMavenRepository = localMavenRepository;
this.settings = settings;
this.javaHome = Objects.requireNonNull(javaHome, "javaHome");
this.logbackConfigurationPath = logbackConfigurationPath;
this.idleTimeoutMs = idleTimeoutMs;
this.keepAliveMs = keepAliveMs;
this.maxLostKeepAlive = maxLostKeepAlive;
}
/**
@@ -96,7 +119,7 @@ public class ClientLayout extends Layout {
*/
public ClientLayout cd(Path newUserDir) {
return new ClientLayout(mvndPropertiesPath, mavenHome, newUserDir, multiModuleProjectDirectory, javaHome,
localMavenRepository, settings, logbackConfigurationPath);
localMavenRepository, settings, logbackConfigurationPath, idleTimeoutMs, keepAliveMs, maxLostKeepAlive);
}
/**
@@ -122,6 +145,18 @@ public class ClientLayout extends Layout {
return logbackConfigurationPath;
}
public int getIdleTimeoutMs() {
return idleTimeoutMs;
}
public int getKeepAliveMs() {
return keepAliveMs;
}
public int getMaxLostKeepAlive() {
return maxLostKeepAlive;
}
static Path findLocalRepo() {
return Environment.MAVEN_REPO_LOCAL.systemProperty().asPath();
}

View File

@@ -15,12 +15,18 @@
*/
package org.jboss.fuse.mvnd.client;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jboss.fuse.mvnd.common.DaemonConnection;
import org.jboss.fuse.mvnd.common.DaemonException;
import org.jboss.fuse.mvnd.common.DaemonException.ConnectException;
import org.jboss.fuse.mvnd.common.DaemonException.MessageIOException;
import org.jboss.fuse.mvnd.common.DaemonException.StaleAddressException;
import org.jboss.fuse.mvnd.common.DaemonInfo;
import org.jboss.fuse.mvnd.common.Message;
@@ -31,21 +37,31 @@ import org.slf4j.LoggerFactory;
* File origin:
* https://github.com/gradle/gradle/blob/v5.6.2/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.java
*/
public class DaemonClientConnection {
public class DaemonClientConnection implements Closeable {
private final static Logger LOG = LoggerFactory.getLogger(DaemonClientConnection.class);
private final DaemonConnection<Message> connection;
private final DaemonInfo daemon;
private final StaleAddressDetector staleAddressDetector;
private final boolean newDaemon;
private boolean hasReceived;
private final Lock dispatchLock = new ReentrantLock();
private final int maxKeepAliveMs;
private final BlockingQueue<Message> queue = new ArrayBlockingQueue<>(16);
private final Thread receiver;
private final AtomicBoolean running = new AtomicBoolean(true);
private final AtomicReference<Exception> exception = new AtomicReference<>();
public DaemonClientConnection(DaemonConnection<Message> connection, DaemonInfo daemon,
StaleAddressDetector staleAddressDetector) {
StaleAddressDetector staleAddressDetector, boolean newDaemon, int maxKeepAliveMs) {
this.connection = connection;
this.daemon = daemon;
this.staleAddressDetector = staleAddressDetector;
this.newDaemon = newDaemon;
this.maxKeepAliveMs = maxKeepAliveMs;
this.receiver = new Thread(this::doReceive);
this.receiver.start();
}
public DaemonInfo getDaemon() {
@@ -71,22 +87,48 @@ public class DaemonClientConnection {
}
}
public Message receive() throws DaemonException.ConnectException {
try {
return connection.receive();
} catch (DaemonException.MessageIOException e) {
LOG.debug("Problem receiving message to the daemon. Performing 'on failure' operation...");
if (!hasReceived && staleAddressDetector.maybeStaleAddress(e)) {
throw new DaemonException.StaleAddressException("Could not receive a message from the daemon.", e);
public Message receive() throws ConnectException, StaleAddressException {
while (true) {
try {
Message m = queue.poll(maxKeepAliveMs, TimeUnit.MILLISECONDS);
Exception e = exception.get();
if (e != null) {
throw e;
} else if (m != null) {
return m;
} else {
throw new IOException("No message received within " + maxKeepAliveMs + "ms, daemon may have crashed");
}
} catch (Exception e) {
LOG.debug("Problem receiving message to the daemon. Performing 'on failure' operation...");
if (!hasReceived && newDaemon) {
throw new ConnectException("Could not receive a message from the daemon.", e);
} else if (staleAddressDetector.maybeStaleAddress(e)) {
throw new StaleAddressException("Could not receive a message from the daemon.", e);
}
} finally {
hasReceived = true;
}
throw new DaemonException.ConnectException("Could not receive a message from the daemon.", e);
} finally {
hasReceived = true;
}
}
public void stop() {
protected void doReceive() {
try {
while (running.get()) {
Message m = connection.receive();
queue.put(m);
}
} catch (Exception e) {
if (running.get()) {
exception.set(e);
}
}
}
public void close() {
LOG.debug("thread {}: connection stop", Thread.currentThread().getId());
running.set(false);
receiver.interrupt();
connection.close();
}

View File

@@ -90,7 +90,7 @@ public class DaemonConnector {
public DaemonClientConnection maybeConnect(DaemonInfo daemon) {
try {
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon), false);
} catch (DaemonException.ConnectException e) {
LOGGER.debug("Cannot connect to daemon {} due to {}. Ignoring.", daemon, e);
}
@@ -225,7 +225,7 @@ public class DaemonConnector {
private DaemonClientConnection findConnection(List<DaemonInfo> compatibleDaemons) {
for (DaemonInfo daemon : compatibleDaemons) {
try {
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon), false);
} catch (DaemonException.ConnectException e) {
LOGGER.debug("Cannot connect to daemon {} due to {}. Trying a different daemon...", daemon, e);
}
@@ -238,7 +238,7 @@ public class DaemonConnector {
LOGGER.debug("Started Maven daemon {}", daemon);
long start = System.currentTimeMillis();
do {
DaemonClientConnection daemonConnection = connectToDaemonWithId(daemon);
DaemonClientConnection daemonConnection = connectToDaemonWithId(daemon, true);
if (daemonConnection != null) {
return daemonConnection;
}
@@ -277,11 +277,8 @@ public class DaemonConnector {
args.add("-Dlogback.configurationFile=" + layout.getLogbackConfigurationPath());
args.add("-Ddaemon.uid=" + uid);
args.add("-Xmx4g");
final String timeout = Environment.DAEMON_IDLE_TIMEOUT.systemProperty().asString();
if (timeout != null) {
args.add(Environment.DAEMON_IDLE_TIMEOUT.asCommandLineProperty(timeout));
}
args.add(Environment.DAEMON_IDLE_TIMEOUT_MS.asCommandLineProperty(Integer.toString(layout.getIdleTimeoutMs())));
args.add(Environment.DAEMON_KEEP_ALIVE_MS.asCommandLineProperty(Integer.toString(layout.getKeepAliveMs())));
args.add(MavenDaemon.class.getName());
command = String.join(" ", args);
@@ -314,13 +311,14 @@ public class DaemonConnector {
}
}
private DaemonClientConnection connectToDaemonWithId(String daemon) throws DaemonException.ConnectException {
private DaemonClientConnection connectToDaemonWithId(String daemon, boolean newDaemon)
throws DaemonException.ConnectException {
// Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that nobody else will
// grab it.
DaemonInfo daemonInfo = registry.get(daemon);
if (daemonInfo != null) {
try {
return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo), newDaemon);
} catch (DaemonException.ConnectException e) {
DaemonDiagnostics diag = new DaemonDiagnostics(daemon, layout.daemonLog(daemon));
throw new DaemonException.ConnectException("Could not connect to the Maven daemon.\n" + diag.describe(), e);
@@ -330,11 +328,13 @@ public class DaemonConnector {
}
private DaemonClientConnection connectToDaemon(DaemonInfo daemon,
DaemonClientConnection.StaleAddressDetector staleAddressDetector) throws DaemonException.ConnectException {
DaemonClientConnection.StaleAddressDetector staleAddressDetector, boolean newDaemon)
throws DaemonException.ConnectException {
LOGGER.debug("Connecting to Daemon");
try {
int maxKeepAliveMs = layout.getKeepAliveMs() * layout.getMaxLostKeepAlive();
DaemonConnection<Message> connection = connect(daemon.getAddress());
return new DaemonClientConnection(connection, daemon, staleAddressDetector);
return new DaemonClientConnection(connection, daemon, staleAddressDetector, newDaemon, maxKeepAliveMs);
} catch (DaemonException.ConnectException e) {
staleAddressDetector.maybeStaleAddress(e);
throw e;
@@ -345,11 +345,9 @@ public class DaemonConnector {
private class CleanupOnStaleAddress implements DaemonClientConnection.StaleAddressDetector {
private final DaemonInfo daemon;
private final boolean exposeAsStale;
public CleanupOnStaleAddress(DaemonInfo daemon, boolean exposeAsStale) {
public CleanupOnStaleAddress(DaemonInfo daemon) {
this.daemon = daemon;
this.exposeAsStale = exposeAsStale;
}
@Override
@@ -360,7 +358,7 @@ public class DaemonConnector {
"by user or operating system");
registry.storeStopEvent(stopEvent);
registry.remove(daemon.getUid());
return exposeAsStale;
return true;
}
}

View File

@@ -35,6 +35,7 @@ import org.jboss.fuse.mvnd.common.Message;
import org.jboss.fuse.mvnd.common.Message.BuildEvent;
import org.jboss.fuse.mvnd.common.Message.BuildException;
import org.jboss.fuse.mvnd.common.Message.BuildMessage;
import org.jboss.fuse.mvnd.common.Message.KeepAliveMessage;
import org.jboss.fuse.mvnd.common.Message.MessageSerializer;
import org.jboss.fuse.mvnd.common.logging.ClientOutput;
import org.jboss.fuse.mvnd.common.logging.TerminalOutput;
@@ -104,8 +105,6 @@ public class DefaultClient implements Client {
debug = true;
args.add(arg);
break;
case "--install":
throw new IllegalStateException("The --install option was removed in mvnd 0.0.2");
default:
if (arg.startsWith("-D")) {
final int eqPos = arg.indexOf('=');
@@ -184,50 +183,54 @@ public class DefaultClient implements Client {
final DaemonConnector connector = new DaemonConnector(layout, registry, buildProperties, new MessageSerializer());
List<String> opts = new ArrayList<>();
DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts),
s -> output.accept(null, s));
try (DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts),
s -> output.accept(null, s))) {
daemon.dispatch(new Message.BuildRequest(
args,
layout.userDir().toString(),
layout.multiModuleProjectDirectory().toString(), System.getenv()));
daemon.dispatch(new Message.BuildRequest(
args,
layout.userDir().toString(),
layout.multiModuleProjectDirectory().toString(),
System.getenv()));
while (true) {
Message m = daemon.receive();
if (m instanceof BuildException) {
final BuildException e = (BuildException) m;
output.error(e.getMessage(), e.getClassName(), e.getStackTrace());
return new DefaultResult(argv,
new Exception(e.getClassName() + ": " + e.getMessage() + "\n" + e.getStackTrace()));
} else if (m instanceof BuildEvent) {
BuildEvent be = (BuildEvent) m;
switch (be.getType()) {
case BuildStarted:
int projects = 0;
int cores = 0;
Properties props = new Properties();
try {
props.load(new StringReader(be.getDisplay()));
projects = Integer.parseInt(props.getProperty("projects"));
cores = Integer.parseInt(props.getProperty("cores"));
} catch (Exception e) {
// Ignore
while (true) {
Message m = daemon.receive();
if (m instanceof BuildException) {
final BuildException e = (BuildException) m;
output.error(e.getMessage(), e.getClassName(), e.getStackTrace());
return new DefaultResult(argv,
new Exception(e.getClassName() + ": " + e.getMessage() + "\n" + e.getStackTrace()));
} else if (m instanceof BuildEvent) {
BuildEvent be = (BuildEvent) m;
switch (be.getType()) {
case BuildStarted:
int projects = 0;
int cores = 0;
Properties props = new Properties();
try {
props.load(new StringReader(be.getDisplay()));
projects = Integer.parseInt(props.getProperty("projects"));
cores = Integer.parseInt(props.getProperty("cores"));
} catch (Exception e) {
// Ignore
}
output.startBuild(be.getProjectId(), projects, cores);
break;
case BuildStopped:
return new DefaultResult(argv, null);
case ProjectStarted:
case MojoStarted:
output.projectStateChanged(be.getProjectId(), be.getDisplay());
break;
case ProjectStopped:
output.projectFinished(be.getProjectId());
break;
}
output.startBuild(be.getProjectId(), projects, cores);
break;
case BuildStopped:
return new DefaultResult(argv, null);
case ProjectStarted:
case MojoStarted:
output.projectStateChanged(be.getProjectId(), be.getDisplay());
break;
case ProjectStopped:
output.projectFinished(be.getProjectId());
break;
} else if (m instanceof BuildMessage) {
BuildMessage bm = (BuildMessage) m;
output.accept(bm.getProjectId(), bm.getMessage());
} else if (m instanceof KeepAliveMessage) {
output.keepAlive();
}
} else if (m instanceof BuildMessage) {
BuildMessage bm = (BuildMessage) m;
output.accept(bm.getProjectId(), bm.getMessage());
}
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import org.slf4j.Logger;
@@ -39,9 +40,17 @@ public enum Environment {
MAVEN_MULTIMODULE_PROJECT_DIRECTORY("maven.multiModuleProjectDirectory", null),
MVND_PROPERTIES_PATH("mvnd.properties.path", "MVND_PROPERTIES_PATH"),
DAEMON_DEBUG("daemon.debug", null),
DAEMON_IDLE_TIMEOUT("daemon.idleTimeout", null),
DAEMON_IDLE_TIMEOUT_MS("daemon.idleTimeoutMs", null),
DAEMON_KEEP_ALIVE_MS("daemon.keepAliveMs", null),
DAEMON_MAX_LOST_KEEP_ALIVE("daemon.maxLostKeepAlive", null),
DAEMON_UID("daemon.uid", null);
public static final int DEFAULT_IDLE_TIMEOUT = (int) TimeUnit.HOURS.toMillis(3);
public static final int DEFAULT_KEEP_ALIVE = (int) TimeUnit.SECONDS.toMillis(1);
public static final int DEFAULT_MAX_LOST_KEEP_ALIVE = 3;
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
static Properties properties = System.getProperties();
static Map<String, String> env = System.getenv();

View File

@@ -83,7 +83,7 @@ public abstract class Message {
this(t.getMessage(), t.getClass().getName(), getStackTrace(t));
}
static String getStackTrace(Throwable t) {
public static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
@@ -180,12 +180,20 @@ public abstract class Message {
}
}
public static class KeepAliveMessage extends Message {
@Override
public String toString() {
return "KeepAliveMessage{}";
}
}
public static class MessageSerializer implements Serializer<Message> {
final int BUILD_REQUEST = 0;
final int BUILD_EVENT = 1;
final int BUILD_MESSAGE = 2;
final int BUILD_EXCEPTION = 3;
final int KEEP_ALIVE = 4;
@Override
public Message read(DataInputStream input) throws EOFException, Exception {
@@ -202,6 +210,8 @@ public abstract class Message {
return readBuildMessage(input);
case BUILD_EXCEPTION:
return readBuildException(input);
case KEEP_ALIVE:
return new KeepAliveMessage();
}
throw new IllegalStateException("Unexpected message type: " + type);
}
@@ -220,6 +230,8 @@ public abstract class Message {
} else if (value instanceof BuildException) {
output.write(BUILD_EXCEPTION);
writeBuildException(output, (BuildException) value);
} else if (value instanceof KeepAliveMessage) {
output.write(KEEP_ALIVE);
} else {
throw new IllegalStateException();
}

View File

@@ -30,4 +30,6 @@ public interface ClientOutput extends AutoCloseable {
void error(String message, String className, String stackTrace);
void keepAlive();
}

View File

@@ -76,7 +76,8 @@ public class TerminalOutput implements ClientOutput {
LOG,
ERROR,
END_OF_STREAM,
INPUT
INPUT,
KEEP_ALIVE
}
static class Event {
@@ -168,6 +169,15 @@ public class TerminalOutput implements ClientOutput {
}
}
@Override
public void keepAlive() {
try {
queue.put(new Event(EventType.KEEP_ALIVE, null, null));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
void readInputLoop() {
try {
while (!closing) {

View File

@@ -57,6 +57,7 @@ import org.jboss.fuse.mvnd.common.Message.BuildEvent.Type;
import org.jboss.fuse.mvnd.common.Message.BuildException;
import org.jboss.fuse.mvnd.common.Message.BuildMessage;
import org.jboss.fuse.mvnd.common.Message.BuildRequest;
import org.jboss.fuse.mvnd.common.Message.KeepAliveMessage;
import org.jboss.fuse.mvnd.common.Message.MessageSerializer;
import org.jboss.fuse.mvnd.daemon.DaemonExpiration.DaemonExpirationResult;
import org.jboss.fuse.mvnd.daemon.DaemonExpiration.DaemonExpirationStrategy;
@@ -72,7 +73,6 @@ public class Server implements AutoCloseable, Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(Server.class);
public static final int CANCEL_TIMEOUT = 10 * 1000;
public static final int DEFAULT_IDLE_TIMEOUT = (int) TimeUnit.HOURS.toMillis(3);
private final String uid;
private final ServerSocketChannel socket;
@@ -96,9 +96,9 @@ public class Server implements AutoCloseable, Runnable {
registry = new DaemonRegistry(layout.registry());
socket = ServerSocketChannel.open().bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
final int idleTimeout = Environment.DAEMON_IDLE_TIMEOUT
final int idleTimeout = Environment.DAEMON_IDLE_TIMEOUT_MS
.systemProperty()
.orDefault(() -> String.valueOf(DEFAULT_IDLE_TIMEOUT))
.orDefault(() -> String.valueOf(Environment.DEFAULT_IDLE_TIMEOUT))
.asInt();
executor = Executors.newScheduledThreadPool(1);
strategy = DaemonExpiration.master();
@@ -389,6 +389,8 @@ public class Server implements AutoCloseable, Runnable {
private void handle(DaemonConnection<Message> connection, BuildRequest buildRequest) {
updateState(Busy);
try {
int keepAlive = Environment.DAEMON_KEEP_ALIVE_MS.systemProperty().asInt();
LOGGER.info("Executing request");
CliRequest req = new CliRequestBuilder()
.arguments(buildRequest.getArgs())
@@ -403,11 +405,22 @@ public class Server implements AutoCloseable, Runnable {
AbstractLoggingSpy.instance(loggingSpy);
Thread pumper = new Thread(() -> {
try {
boolean flushed = true;
while (true) {
Message m = queue.poll();
if (m == null) {
connection.flush();
m = queue.take();
Message m;
if (flushed) {
m = queue.poll(keepAlive, TimeUnit.MILLISECONDS);
if (m == null) {
m = new KeepAliveMessage();
}
flushed = false;
} else {
m = queue.poll();
if (m == null) {
connection.flush();
flushed = true;
continue;
}
}
if (m == STOP) {
connection.flush();
@@ -462,6 +475,8 @@ public class Server implements AutoCloseable, Runnable {
return 97;
} else if (m == STOP) {
return 99;
} else if (m instanceof KeepAliveMessage) {
return 100;
} else {
throw new IllegalStateException();
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.fuse.mvnd.it;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
import javax.inject.Inject;
import org.assertj.core.api.Assertions;
import org.jboss.fuse.mvnd.client.Client;
import org.jboss.fuse.mvnd.client.ClientLayout;
import org.jboss.fuse.mvnd.common.DaemonException;
import org.jboss.fuse.mvnd.common.logging.ClientOutput;
import org.jboss.fuse.mvnd.junit.MvndTest;
import org.jboss.fuse.mvnd.junit.TestUtils;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertThrows;
@MvndTest(projectDir = "src/test/projects/daemon-crash")
public class DaemonCrashTest {
@Inject
Client client;
@Inject
ClientLayout layout;
@Test
void cleanInstall() throws IOException, InterruptedException {
final Path helloPath = layout.multiModuleProjectDirectory().resolve("hello/target/hello.txt");
try {
Files.deleteIfExists(helloPath);
} catch (IOException e) {
throw new RuntimeException("Could not delete " + helloPath);
}
final Path localMavenRepo = layout.getLocalMavenRepository();
TestUtils.deleteDir(localMavenRepo);
final Path[] installedJars = {
localMavenRepo.resolve(
"org/jboss/fuse/mvnd/test/daemon-crash/daemon-crash-maven-plugin/0.0.1-SNAPSHOT/daemon-crash-maven-plugin-0.0.1-SNAPSHOT.jar"),
};
Stream.of(installedJars).forEach(jar -> Assertions.assertThat(jar).doesNotExist());
final ClientOutput output = Mockito.mock(ClientOutput.class);
assertThrows(DaemonException.StaleAddressException.class,
() -> client.execute(output, "clean", "install", "-e", "-Dmvnd.log.level=DEBUG").assertFailure());
}
}

View File

@@ -203,7 +203,10 @@ public class MvndTestExtension implements BeforeAllCallback, BeforeEachCallback,
multiModuleProjectDirectory,
Paths.get(System.getProperty("java.home")).toAbsolutePath().normalize(),
localMavenRepository, settingsPath,
logback);
logback,
Environment.DEFAULT_IDLE_TIMEOUT,
Environment.DEFAULT_KEEP_ALIVE,
Environment.DEFAULT_MAX_LOST_KEEP_ALIVE);
final TestRegistry registry = new TestRegistry(layout.registry());
return new MvndResource(layout, registry, isNative, timeoutMs);

View File

@@ -22,9 +22,10 @@ public class TestLayout extends ClientLayout {
private final Path testDir;
public TestLayout(Path testDir, Path mvndPropertiesPath, Path mavenHome, Path userDir, Path multiModuleProjectDirectory,
Path javaHome, Path localMavenRepository, Path settings, Path logbackConfigurationPath) {
Path javaHome, Path localMavenRepository, Path settings, Path logbackConfigurationPath,
int idleTimeout, int keepAlive, int maxLostKeepAlive) {
super(mvndPropertiesPath, mavenHome, userDir, multiModuleProjectDirectory, javaHome, localMavenRepository,
settings, logbackConfigurationPath);
settings, logbackConfigurationPath, idleTimeout, keepAlive, maxLostKeepAlive);
this.testDir = testDir;
}

View File

@@ -0,0 +1,61 @@
<!--
Copyright 2019 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jboss.fuse.mvnd.test.daemon-crash</groupId>
<artifactId>daemon-crash</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>module-and-plugin-hello</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jboss.fuse.mvnd.test.daemon-crash</groupId>
<artifactId>daemon-crash-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<executions>
<execution>
<id>hello</id>
<goals>
<goal>hello</goal>
</goals>
<phase>compile</phase>
<configuration>
<file>${basedir}/target/hello.txt</file>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.fuse.mvnd.test.multi.module.hello;
public class Hello {
public String greet() {
return "Hello";
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.fuse.mvnd.test.multi.module.hello;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class HelloTest {
@Test
void greet() throws IOException {
final String actual = new Hello().greet();
Assertions.assertEquals("Hello", actual);
/* Make sure the plugin was run before this test */
final String content = new String(Files.readAllBytes(Paths.get("target/hello.txt")), StandardCharsets.UTF_8);
Assertions.assertTrue("Hi".equals(content) || "Hello".equals(content));
}
}

View File

@@ -0,0 +1,46 @@
<!--
Copyright 2019 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jboss.fuse.mvnd.test.daemon-crash</groupId>
<artifactId>daemon-crash</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>daemon-crash-maven-plugin</artifactId>
<packaging>maven-plugin</packaging>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<scope>provided</scope>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.fuse.mvnd.test.module.plugin.mojo;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
/**
*/
@Mojo(name = "hello", requiresProject = true)
public class HelloMojo extends AbstractMojo {
@Parameter
File file;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
System.exit(-1);
}
}

View File

@@ -0,0 +1,76 @@
<!--
Copyright 2019 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jboss.fuse.mvnd.test.daemon-crash</groupId>
<artifactId>daemon-crash</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven-clean-plugin.version>2.5</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-install-plugin.version>2.4</maven-install-plugin.version>
<maven-resources-plugin.version>2.6</maven-resources-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
</properties>
<modules>
<module>hello</module>
<module>plugin</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>