mirror of
https://github.com/apache/maven-mvnd.git
synced 2026-01-13 07:04:14 +08:00
Implement a keep-alive strategy to detect stales connections, fixes #47
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -30,4 +30,6 @@ public interface ClientOutput extends AutoCloseable {
|
||||
|
||||
void error(String message, String className, String stackTrace);
|
||||
|
||||
void keepAlive();
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
76
integration-tests/src/test/projects/daemon-crash/pom.xml
Normal file
76
integration-tests/src/test/projects/daemon-crash/pom.xml
Normal 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>
|
||||
Reference in New Issue
Block a user