Skip to content

Commit

Permalink
[52] Have the shutdown and kill methods return a CompletableFuture wh…
Browse files Browse the repository at this point in the history
…ich on get() will wait for the server to shut down or the process to end.

Signed-off-by: James R. Perkins <jperkins@redhat.com>
  • Loading branch information
jamezp committed Jul 18, 2024
1 parent 3f0844c commit a7f5a12
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -28,7 +30,6 @@
@SuppressWarnings("unused")
abstract class AbstractServerManager<T extends ModelControllerClient> implements ServerManager {
private static final Logger LOGGER = Logger.getLogger(AbstractServerManager.class);
private static final ThreadLocal<Boolean> SKIP_STATE_CHECK = ThreadLocal.withInitial(() -> false);

protected final ProcessHandle process;
final T client;
Expand Down Expand Up @@ -72,11 +73,17 @@ public String launchType() {
}

@Override
public void kill() {
public CompletableFuture<ServerManager> kill() {
final CompletableFuture<ServerManager> cf = new CompletableFuture<>();
if (process != null && process.isAlive()) {
internalClose(false);
process.destroyForcibly();
if (process.destroyForcibly()) {
cf.thenCombine(process.onExit(), (serverManager, processHandle) -> serverManager);
}
} else {
cf.complete(this);
}
return cf;
}

@Override
Expand Down Expand Up @@ -129,6 +136,7 @@ public String takeSnapshot() throws IOException, OperationExecutionException {
* Reloads the server and returns immediately.
*
* @param reloadOp the reload operation to execute
*
* @throws OperationExecutionException if the reload operation fails
*/
@Override
Expand All @@ -143,6 +151,18 @@ public void executeReload(final ModelNode reloadOp) throws IOException, Operatio
}
}

@Override
public void shutdown(final long timeout) throws IOException {
checkState();
internalShutdown(client, timeout);
}

@Override
public CompletableFuture<ServerManager> shutdownAsync(final long timeout) {
checkState();
return shutdown(client(), timeout);
}

@Override
public boolean isClosed() {
return closed.get();
Expand All @@ -154,30 +174,91 @@ public void close() {
}

void checkState() {
if (!SKIP_STATE_CHECK.get() && closed.get()) {
if (closed.get()) {
throw new IllegalStateException("The server manager has been closed and cannot process requests");
}
}

void internalClose(final boolean shutdownOnClose) {
if (closed.compareAndSet(false, true)) {
try {
SKIP_STATE_CHECK.set(true);
if (shutdownOnClose) {
try {
shutdown();
} catch (IOException e) {
LOGGER.error("Failed to shutdown the server while closing the server manager.", e);
}
if (shutdownOnClose) {
try {
shutdown(client, 0).get();
} catch (ExecutionException | InterruptedException e) {
LOGGER.error("Failed to shutdown the server while closing the server manager.", e);
}
}
try {
client.close();
} catch (IOException e) {
LOGGER.error("Failed to close the client.", e);
}
}
}

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

private CompletableFuture<ServerManager> shutdown(final ModelControllerClient client, final long timeout) {
final ServerManager serverManager = this;
if (process != null) {
return CompletableFuture.supplyAsync(() -> {
try {
client.close();
internalShutdown(client, timeout);
} catch (IOException e) {
LOGGER.error("Failed to close the client.", e);
throw new CompletionException("Failed to shutdown server.", e);
}
} finally {
SKIP_STATE_CHECK.remove();
return null;
})
.thenCombine(process.onExit(), (outcome, processHandle) -> null)
.handle((ignore, error) -> {
if (error != null && process.isAlive()) {
if (process.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());
} else {
LOGGER.warnf(error,
"Failed to shutdown server and destroy the process %d. The server may still be running in a process.",
process.pid());
}
}
return serverManager;
});
}
return CompletableFuture.supplyAsync(() -> {
try {
internalShutdown(client, timeout);
} catch (IOException e) {
throw new CompletionException("Failed to shutdown server.", e);
}
return null;
}).thenApply(outcome -> {
// Wait for the server manager to finish shutting down
while (ServerManager.isRunning(client)) {
Thread.onSpinWait();
}
return serverManager;
});
}

/**
* Executes the operation with the {@code client} returning the result or throwing an {@link OperationExecutionException}
* if the operation failed.
*
* @param client the client used to execute the operation
* @param op the operation to execute
*
* @return the result of the operation
*
* @throws IOException if an error occurs communicating with the server
* @throws OperationExecutionException if the operation failed
*/
static ModelNode executeOperation(final ModelControllerClient client, final ModelNode op)
throws IOException, OperationExecutionException {
final ModelNode result = client.execute(op);
if (Operations.isSuccessfulOutcome(result)) {
return Operations.readResult(result);
}
throw new OperationExecutionException(op, result);
}
}
53 changes: 19 additions & 34 deletions src/main/java/org/wildfly/plugin/tools/server/DomainManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.helpers.ClientConstants;
import org.jboss.as.controller.client.helpers.Operations;
import org.jboss.as.controller.client.helpers.domain.DomainClient;
Expand Down Expand Up @@ -72,40 +73,6 @@ public boolean isRunning() {
return CommonOperations.isDomainRunning(client(), false);
}

@Override
public void shutdown() throws IOException, OperationExecutionException {
shutdown(0);
}

@Override
public void shutdown(final long timeout)
throws IOException {
// Note the following two operations used to shutdown a domain don't seem to work well in a composite operation.
// The operation occasionally sees a java.util.concurrent.CancellationException because the operation client
// is likely closed before the AsyncFuture.get() is complete. Using a non-composite operation doesn't seem to
// have this issue.

// First shutdown the servers
final ModelNode stopServersOp = Operations.createOperation("stop-servers");
stopServersOp.get("blocking").set(true);
stopServersOp.get("timeout").set(timeout);
executeOperation(stopServersOp);

// Now shutdown the host
final ModelNode address = determineHostAddress();
final ModelNode shutdownOp = Operations.createOperation("shutdown", address);
executeOperation(shutdownOp);
// Wait until the process has died
while (true) {
final boolean running = (process == null ? CommonOperations.isDomainRunning(client(), true) : process.isAlive());
if (running) {
Thread.onSpinWait();
} else {
break;
}
}
}

@Override
public void executeReload() throws IOException, OperationExecutionException {
executeReload(Operations.createOperation("reload-servers"));
Expand Down Expand Up @@ -157,4 +124,22 @@ public void reloadIfRequired(final long timeout, final TimeUnit unit) throws IOE
LOGGER.warnf("Cannot reload and wait for the server to start with a server type of %s.", launchType);
}
}

@Override
void internalShutdown(final ModelControllerClient client, final long timeout) throws IOException {
// Note the following two operations used to shutdown a domain don't seem to work well in a composite operation.
// The operation occasionally sees a java.util.concurrent.CancellationException because the operation client
// is likely closed before the AsyncFuture.get() is complete. Using a non-composite operation doesn't seem to
// have this issue.
// First shutdown the servers
final ModelNode stopServersOp = Operations.createOperation("stop-servers");
stopServersOp.get("blocking").set(true);
stopServersOp.get("timeout").set(timeout);
executeOperation(client, stopServersOp);

// Now shutdown the host
final ModelNode address = CommonOperations.determineHostAddress(client);
final ModelNode shutdownOp = Operations.createOperation("shutdown", address);
executeOperation(client, shutdownOp);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.wildfly.plugin.tools.server;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.jboss.as.controller.client.ModelControllerClient;
Expand Down Expand Up @@ -79,7 +80,7 @@ public boolean isRunning() {
* Not allowed, throws an {@link UnsupportedOperationException}
*/
@Override
public void kill() {
public CompletableFuture<ServerManager> kill() {
throw new UnsupportedOperationException("Cannot kill an managed server");
}

Expand Down Expand Up @@ -109,6 +110,16 @@ public void shutdown(final long timeout) throws IOException {
throw new UnsupportedOperationException("Cannot shutdown an managed server");
}

@Override
public CompletableFuture<ServerManager> shutdownAsync() {
throw new UnsupportedOperationException("Cannot shutdown an managed server");
}

@Override
public CompletableFuture<ServerManager> shutdownAsync(final long timeout) {
throw new UnsupportedOperationException("Cannot shutdown an managed server");
}

@Override
public void executeReload() throws IOException {
delegate.executeReload();
Expand Down
48 changes: 44 additions & 4 deletions src/main/java/org/wildfly/plugin/tools/server/ServerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,14 @@ static DomainManager start(final DomainCommandBuilder commandBuilder, final Conf
* If a process is available and {@linkplain ProcessHandle#isAlive() alive}, the process is attempted to be
* {@linkplain ProcessHandle#destroyForcibly() killed}. Note in cases where the process is not associated with this
* server manager, this method does nothing.
* <p>
* The returned {@link ServerManager} is the same instance of this server manager. You can use the
* {@link CompletableFuture#get()} to wait until the process, if available, to exit.
* </p>
*
* @return a completable future that on a {@link CompletableFuture#get()} will wait for the process, if available, exits
*/
default void kill() {
default CompletableFuture<ServerManager> kill() {
throw new UnsupportedOperationException("Not et implemented");
}

Expand Down Expand Up @@ -517,14 +523,18 @@ default boolean waitFor(final long startupTimeout) throws InterruptedException {
boolean waitFor(long startupTimeout, TimeUnit unit) throws InterruptedException;

/**
* Shuts down the server.
* Shuts down the server without a graceful shutdown timeout and wait for the server to be shutdown. This is a
* shortcut for @link #shutdown(long) shutdown(0)}.
*
* @throws IOException if an error occurs communicating with the server
* @see #shutdown(long)
*/
void shutdown() throws IOException;
default void shutdown() throws IOException {
shutdown(0);
}

/**
* Shuts down the server.
* Shuts down the server and wait for the servers to be shutdown.
*
* @param timeout the graceful shutdown timeout, a value of {@code -1} will wait indefinitely and a value of
* {@code 0} will not attempt a graceful shutdown
Expand All @@ -533,6 +543,36 @@ default boolean waitFor(final long startupTimeout) throws InterruptedException {
*/
void shutdown(long timeout) throws IOException;

/**
* Shuts down the server without a graceful shutdown timeout. This is a shortcut for
* {@link #shutdownAsync(long) shutdown(0)}.
* <p>
* The returned {@link ServerManager} is the same instance of this server manager. You can use the
* {@link CompletableFuture#get()} to wait until the process, if available, to exit.
* </p>
*
* @return a completable future that on a {@link CompletableFuture#get()} will wait for the process, if available, exits
*
* @see #shutdownAsync(long)
*/
default CompletableFuture<ServerManager> shutdownAsync() {
return shutdownAsync(0);
}

/**
* Shuts down the server.
* <p>
* The returned {@link ServerManager} is the same instance of this server manager. You can use the
* {@link CompletableFuture#get()} to wait until the process, if available, to exit.
* </p>
*
* @param timeout the graceful shutdown timeout, a value of {@code -1} will wait indefinitely and a value of
* {@code 0} will not attempt a graceful shutdown
*
* @return a completable future that on a {@link CompletableFuture#get()} will wait for the process, if available, exits
*/
CompletableFuture<ServerManager> shutdownAsync(long timeout);

/**
* Reloads the server and returns immediately.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,9 @@ public boolean isRunning() {
}

@Override
public void shutdown() throws IOException {
shutdown(0);
}

@Override
public void shutdown(final long timeout) throws IOException {
void internalShutdown(final ModelControllerClient client, final long timeout) throws IOException {
final ModelNode op = Operations.createOperation("shutdown");
op.get("timeout").set(timeout);
executeOperation(op);
while (true) {
final boolean running = (process == null ? isRunning() : process.isAlive());
if (running) {
Thread.onSpinWait();
} else {
break;
}
}
executeOperation(client, op);
}
}

0 comments on commit a7f5a12

Please sign in to comment.