Skip to content

Commit

Permalink
[61] Add an exitValue() and waitForTermination() method which both re…
Browse files Browse the repository at this point in the history
…turn an int for the exit code. This will use the process' exit if one was set. Otherwise, an unknown exit code of -1 is returned.

Signed-off-by: James R. Perkins <[email protected]>
  • Loading branch information
jamezp committed Nov 28, 2024
1 parent cf63dd3 commit 01e8d03
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,19 @@
abstract class AbstractServerManager<T extends ModelControllerClient> implements ServerManager {
private static final Logger LOGGER = Logger.getLogger(AbstractServerManager.class);

protected final ProcessHandle process;
protected final ProcessHandle processHandle;
private final Process process;
final T client;
private final boolean shutdownOnClose;
private final DeploymentManager deploymentManager;
private final AtomicBoolean closed;
private final AtomicBoolean shutdown;
private volatile Thread shutdownWaitThread;

protected AbstractServerManager(final ProcessHandle process, final T client,
protected AbstractServerManager(final Process process, final ProcessHandle processHandle, final T client,
final boolean shutdownOnClose) {
this.process = process;
this.processHandle = processHandle;
this.client = client;
this.shutdownOnClose = shutdownOnClose;
deploymentManager = DeploymentManager.create(client);
Expand Down Expand Up @@ -76,14 +79,14 @@ public String launchType() {

@Override
public CompletableFuture<ServerManager> kill() {
if (process != null) {
if (process.isAlive()) {
if (processHandle != null) {
if (processHandle.isAlive()) {
return CompletableFuture.supplyAsync(() -> {
internalClose(false, false);
return process.destroyForcibly();
return processHandle.destroyForcibly();
}).thenCompose((successfulRequest) -> {
if (successfulRequest) {
return process.onExit().thenApply((processHandle) -> this);
return processHandle.onExit().thenApply((ph) -> this);
}
return CompletableFuture.completedFuture(this);
});
Expand All @@ -102,15 +105,15 @@ public boolean waitFor(final long startupTimeout, final TimeUnit unit) throws In
break;
}
timeout -= (System.currentTimeMillis() - before);
if (process != null && !process.isAlive()) {
throw new ServerManagerException("The process %d is no longer active.", process.pid());
if (processHandle != null && !processHandle.isAlive()) {
throw new ServerManagerException("The process %d is no longer active.", processHandle.pid());
}
TimeUnit.MILLISECONDS.sleep(sleep);
timeout -= sleep;
}
if (timeout <= 0) {
if (process != null) {
process.destroy();
if (processHandle != null) {
processHandle.destroy();
}
return false;
}
Expand Down Expand Up @@ -169,7 +172,7 @@ public void shutdown(final long timeout) throws IOException {
public CompletableFuture<ServerManager> shutdownAsync(final long timeout) {
checkState();
final ServerManager serverManager = this;
if (process != null) {
if (processHandle != null) {
return CompletableFuture.supplyAsync(() -> {
try {
if (shutdown.compareAndSet(false, true)) {
Expand All @@ -180,17 +183,17 @@ public CompletableFuture<ServerManager> shutdownAsync(final long timeout) {
}
return null;
})
.thenCombine(process.onExit(), (outcome, processHandle) -> null)
.thenCombine(processHandle.onExit(), (outcome, processHandle) -> null)
.handle((ignore, error) -> {
if (error != null && process.isAlive()) {
if (process.destroyForcibly()) {
if (error != null && processHandle.isAlive()) {
if (processHandle.destroyForcibly()) {
LOGGER.warnf(error,
"Failed to shutdown the server. An attempt to destroy the process %d has been made, but it may still temporarily run in the background.",
process.pid());
processHandle.pid());
} else {
LOGGER.warnf(error,
"Failed to shutdown server and destroy the process %d. The server may still be running in a process.",
process.pid());
processHandle.pid());
}
}
return serverManager;
Expand All @@ -214,6 +217,29 @@ public CompletableFuture<ServerManager> shutdownAsync(final long timeout) {
});
}

@Override
public int waitForTermination() throws InterruptedException {
checkState();
shutdownWaitThread = Thread.currentThread();
try {
if (process != null) {
return process.waitFor();
}
waitForShutdown(client);
return UNKNOWN_EXIT_STATUS;
} finally {
shutdownWaitThread = null;
}
}

@Override
public int exitValue() throws IllegalStateException {
if (process != null) {
return process.exitValue();
}
return UNKNOWN_EXIT_STATUS;
}

@Override
public boolean isClosed() {
return closed.get();
Expand Down Expand Up @@ -245,26 +271,36 @@ void internalClose(final boolean shutdownOnClose, final boolean waitForShutdown)
}
}
try {
client.close();
} catch (IOException e) {
LOGGER.error("Failed to close the client.", e);
// Interrupt the shutdown thread if it's still running
final Thread shutdownThread = shutdownWaitThread;
if (shutdownThread != null) {
LOGGER.debugf("Interrupting shutdown thread %s.", shutdownThread.getName());
shutdownThread.interrupt();
}
} finally {
try {
client.close();
} catch (IOException e) {
LOGGER.error("Failed to close the client.", e);
}
}
}
}

abstract void internalShutdown(ModelControllerClient client, long timeout) throws IOException;

private void waitForShutdown(final ModelControllerClient client) {
if (process != null) {
if (processHandle != null) {
try {
process.onExit()
processHandle.onExit()
.get();
} catch (InterruptedException | ExecutionException e) {
throw new ServerManagerException(e, "Error waiting for process %d to exit.", process.pid());
throw new ServerManagerException(String.format("Error waiting for process %d to exit.", processHandle.pid()),
e);
}
} else {
// Wait for the server manager to finish shutting down
while (ServerManager.isRunning(client)) {
while (!closed.get() && ServerManager.isRunning(client)) {
Thread.onSpinWait();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
public class DomainManager extends AbstractServerManager<DomainClient> {
private static final Logger LOGGER = Logger.getLogger(DomainManager.class);

DomainManager(final ProcessHandle process, final DomainClient client,
DomainManager(final Process process, final ProcessHandle processHandle, final DomainClient client,
final boolean shutdownOnClose) {
super(process, client, shutdownOnClose);
super(process, processHandle, client, shutdownOnClose);
}

@Override
Expand Down Expand Up @@ -67,8 +67,8 @@ public ModelNode determineHostAddress() throws OperationExecutionException, IOEx
*/
@Override
public boolean isRunning() {
if (process != null) {
return process.isAlive() && CommonOperations.isDomainRunning(client(), false);
if (processHandle != null) {
return processHandle.isAlive() && CommonOperations.isDomainRunning(client(), false);
}
return CommonOperations.isDomainRunning(client(), false);
}
Expand Down
59 changes: 51 additions & 8 deletions src/main/java/org/wildfly/plugin/tools/server/ServerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@
*/
@SuppressWarnings("unused")
public interface ServerManager extends AutoCloseable {
int UNKNOWN_EXIT_STATUS = -1;

/**
* A builder used to build a {@link ServerManager}.
*/
class Builder {
private final Configuration<?> configuration;
private ProcessHandle process;
private Process process;
private ProcessHandle processHandle;

public Builder() {
this(new StandaloneConfiguration(null));
Expand Down Expand Up @@ -75,7 +77,7 @@ public Builder client(final ModelControllerClient client) {
* @return this builder
*/
public Builder process(final ProcessHandle process) {
this.process = process;
this.processHandle = process;
return this;
}

Expand All @@ -90,7 +92,8 @@ public Builder process(final ProcessHandle process) {
* @see #process(ProcessHandle)
*/
public Builder process(final Process process) {
this.process = process == null ? null : process.toHandle();
this.process = process;
this.processHandle = process == null ? null : process.toHandle();
return this;
}

Expand Down Expand Up @@ -143,7 +146,7 @@ public Builder shutdownOnClose(final boolean shutdownOnClose) {
* @return a new {@link StandaloneManager}
*/
public StandaloneManager standalone() {
return new StandaloneManager(process, configuration.client(), configuration.shutdownOnClose());
return new StandaloneManager(process, processHandle, configuration.client(), configuration.shutdownOnClose());
}

/**
Expand All @@ -154,7 +157,7 @@ public StandaloneManager standalone() {
* @return a new {@link DomainManager}
*/
public DomainManager domain() {
return new DomainManager(process, getOrCreateDomainClient(), configuration.shutdownOnClose());
return new DomainManager(process, processHandle, getOrCreateDomainClient(), configuration.shutdownOnClose());
}

/**
Expand All @@ -175,7 +178,8 @@ public DomainManager domain() {
public CompletableFuture<ServerManager> build() {
@SuppressWarnings("resource")
final ModelControllerClient client = configuration.client();
final ProcessHandle process = this.process;
final Process process = this.process;
final ProcessHandle processHandle = this.processHandle;
return CompletableFuture.supplyAsync(() -> {
// Wait until the server is running, then determine what type we need to return
while (!isRunning(client)) {
Expand All @@ -189,9 +193,10 @@ public CompletableFuture<ServerManager> build() {
final String launchType = launchType(client).orElseThrow(() -> new ServerManagerException(
"Could not determine the type of the server. Verify the server is running."));
if ("STANDALONE".equals(launchType)) {
return new StandaloneManager(process, client, configuration.shutdownOnClose());
return new StandaloneManager(process, processHandle, client, configuration.shutdownOnClose());
} else if ("DOMAIN".equals(launchType)) {
return new DomainManager(process, getOrCreateDomainClient(), configuration.shutdownOnClose());
return new DomainManager(process, processHandle, getOrCreateDomainClient(),
configuration.shutdownOnClose());
}
throw new ServerManagerException("Only standalone and domain servers are support. %s is not supported.",
launchType);
Expand Down Expand Up @@ -574,6 +579,44 @@ default CompletableFuture<ServerManager> shutdownAsync(long timeout) {
});
}

/**
* Waits for the server to be shutdown and the process, if defined, to terminate. This returns the exit status of
* the process if it was defined. Otherwise, {@link #UNKNOWN_EXIT_STATUS -1} will be returned.
* <p>
* Note this is a blocking action and will block the current thread until the server has been exited or this server
* manager has been {@linkplain #close() closed}. If this server manager has been closed, the thread waiting for
* terminate will be interrupted.
* </p>
*
* @return the exit status of the process, if defined. If not defined a value of {@code -1} will be returned
*
* @throws InterruptedException if the current thread is interrupted while waiting for the server to exit
* @since 1.2
*/
default int waitForTermination() throws InterruptedException {
while (!isClosed() && isRunning()) {
Thread.onSpinWait();
}
return UNKNOWN_EXIT_STATUS;
}

/**
* Returns the exit status of the process if it's defined. If the {@link Builder#process(Process)} was used and the
* process has not yet terminated, an {@link IllegalStateException} will be thrown.
* <p>
* If no process was set or the process is defined as a {@link ProcessHandle}, then {@link #UNKNOWN_EXIT_STATUS -1}
* will be returned.
* </p>
*
* @return the exit status of the process, if defined. If not defined a value of {@code -1} will be returned
*
* @throws IllegalStateException if the process has not yet terminated
* @since 1.2
*/
default int exitValue() throws IllegalStateException {
return UNKNOWN_EXIT_STATUS;
}

/**
* Reloads the server and returns immediately.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
public class StandaloneManager extends AbstractServerManager<ModelControllerClient> {
private static final Logger LOGGER = Logger.getLogger(StandaloneManager.class);

StandaloneManager(final ProcessHandle process, final ModelControllerClient client,
StandaloneManager(final Process process, final ProcessHandle processHandle, final ModelControllerClient client,
final boolean shutdownOnClose) {
super(process, client, shutdownOnClose);
super(process, processHandle, client, shutdownOnClose);
}

@Override
Expand Down Expand Up @@ -75,8 +75,8 @@ public void reloadIfRequired(final long timeout, final TimeUnit unit) throws IOE

@Override
public boolean isRunning() {
if (process != null) {
return process.isAlive() && CommonOperations.isStandaloneRunning(client());
if (processHandle != null) {
return processHandle.isAlive() && CommonOperations.isStandaloneRunning(client());
}
return CommonOperations.isStandaloneRunning(client());
}
Expand Down
6 changes: 5 additions & 1 deletion src/test/java/org/wildfly/plugin/tools/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,13 @@ public static StandaloneManager launchStandalone(final boolean shutdownOnClose)
}

public static DomainManager launchDomain() {
return launchDomain(true);
}

public static DomainManager launchDomain(final boolean shutdownOnClose) {
final DomainManager serverManager = ServerManager
.start(Configuration.create(DomainCommandBuilder.of(Environment.WILDFLY_HOME))
.shutdownOnClose(true)
.shutdownOnClose(shutdownOnClose)
.managementAddress(HOSTNAME)
.managementPort(PORT));
try {
Expand Down
Loading

0 comments on commit 01e8d03

Please sign in to comment.