in rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java [133:331]
public void shutdown(final boolean stopAppsFirst, final boolean forceShutdownOnError,
String shutdownTimeoutRaw, String requestTimeoutRaw, String delayForHttpReturnRaw,
Long delayMillis) {
if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null))
throw WebResourceUtils.forbidden(USER_OPERATION_NOT_AUTHORIZED_MSG, Entitlements.getEntitlementContext().user());
if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SHUTDOWN, null))
throw WebResourceUtils.forbidden("User '%s' is not authorized for shutdown", Entitlements.getEntitlementContext().user());
log.info("REST call to shutdown server, stopAppsFirst="+stopAppsFirst+", delayForHttpReturn="+shutdownTimeoutRaw);
if (stopAppsFirst && !isMaster()) {
log.warn("REST call to shutdown non-master server while stopping apps is disallowed");
throw WebResourceUtils.forbidden("Not allowed to stop all apps when server is not master");
}
final Duration shutdownTimeout = parseDuration(shutdownTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
Duration requestTimeout = parseDuration(requestTimeoutRaw, Duration.of(20, TimeUnit.SECONDS));
final Duration delayForHttpReturn;
if (delayMillis == null) {
delayForHttpReturn = parseDuration(delayForHttpReturnRaw, Duration.FIVE_SECONDS);
} else {
log.warn("'delayMillis' is deprecated, use 'delayForHttpReturn' instead.");
delayForHttpReturn = Duration.of(delayMillis, TimeUnit.MILLISECONDS);
}
Preconditions.checkState(delayForHttpReturn.nanos() >= 0, "Only positive or 0 delay allowed for delayForHttpReturn");
boolean isSingleTimeout = shutdownTimeout.equals(requestTimeout);
final AtomicBoolean completed = new AtomicBoolean();
final AtomicBoolean hasAppErrorsOrTimeout = new AtomicBoolean();
//shutdownHandler & mgmt is thread local
final ShutdownHandler handler = shutdownHandler.getContext(ShutdownHandler.class);
final ManagementContext mgmt = mgmt();
new Thread("shutdown") {
@Override
public void run() {
boolean terminateTried = false;
try {
if (stopAppsFirst) {
CountdownTimer shutdownTimeoutTimer = null;
if (!shutdownTimeout.equals(Duration.ZERO)) {
shutdownTimeoutTimer = shutdownTimeout.countdownTimer();
}
log.debug("Stopping applications");
List<Task<?>> stoppers = new ArrayList<Task<?>>();
int allStoppableApps = 0;
for (Application app: mgmt.getApplications()) {
allStoppableApps++;
Lifecycle appState = EntityAttributesUtils.tryGetAttribute(app, Attributes.SERVICE_STATE_ACTUAL);
if (app instanceof StartableApplication &&
// Don't try to stop an already stopping app. Subsequent stops will complete faster
// cancelling the first stop task.
appState != Lifecycle.STOPPING) {
stoppers.add(Entities.invokeEffector(app, app, StartableApplication.STOP));
} else {
log.debug("App " + app + " is already stopping, will not stop second time. Will wait for original stop to complete.");
}
}
log.debug("Waiting for " + allStoppableApps + " apps to stop, of which " + stoppers.size() + " stopped explicitly.");
for (Task<?> t: stoppers) {
if (!waitAppShutdown(shutdownTimeoutTimer, t)) {
//app stop error
hasAppErrorsOrTimeout.set(true);
}
}
// Wait for apps which were already stopping when we tried to shut down.
if (hasStoppableApps(mgmt)) {
log.debug("Apps are still stopping, wait for proper unmanage.");
while (hasStoppableApps(mgmt) && (shutdownTimeoutTimer == null || !shutdownTimeoutTimer.isExpired())) {
Duration wait;
if (shutdownTimeoutTimer != null) {
wait = Duration.min(shutdownTimeoutTimer.getDurationRemaining(), Duration.ONE_SECOND);
} else {
wait = Duration.ONE_SECOND;
}
Time.sleep(wait);
}
if (hasStoppableApps(mgmt)) {
hasAppErrorsOrTimeout.set(true);
}
}
}
terminateTried = true;
((ManagementContextInternal)mgmt).terminate();
} catch (Throwable e) {
Throwable interesting = Exceptions.getFirstInteresting(e);
if (interesting instanceof TimeoutException) {
//timeout while waiting for apps to stop
log.warn("Timeout shutting down: "+Exceptions.collapseText(e));
log.debug("Timeout shutting down: "+e, e);
hasAppErrorsOrTimeout.set(true);
} else {
// swallow fatal, so we notify the outer loop to continue with shutdown
log.error("Unexpected error shutting down: "+Exceptions.collapseText(e), e);
}
hasAppErrorsOrTimeout.set(true);
if (!terminateTried) {
mgmtInternal().terminate();
}
} finally {
complete();
if (!hasAppErrorsOrTimeout.get() || forceShutdownOnError) {
//give the http request a chance to complete gracefully, the server will be stopped in a shutdown hook
Time.sleep(delayForHttpReturn);
if (handler != null) {
handler.onShutdownRequest();
} else {
log.warn("ShutdownHandler not set, exiting process");
System.exit(0);
}
} else {
// There are app errors, don't exit the process, allowing any exception to continue throwing
log.warn("Abandoning shutdown because there were errors and shutdown was not forced.");
}
}
}
private boolean hasStoppableApps(ManagementContext mgmt) {
for (Application app : mgmt.getApplications()) {
if (app instanceof StartableApplication) {
Lifecycle state = EntityAttributesUtils.tryGetAttribute(app, Attributes.SERVICE_STATE_ACTUAL);
if (state != Lifecycle.STOPPING && state != Lifecycle.STOPPED) {
log.warn("Shutting down, expecting all apps to be in stopping state, but found application " + app + " to be in state " + state + ". Just started?");
}
return true;
}
}
return false;
}
private void complete() {
synchronized (completed) {
completed.set(true);
completed.notifyAll();
}
}
private boolean waitAppShutdown(CountdownTimer shutdownTimeoutTimer, Task<?> t) throws TimeoutException {
Duration waitInterval = null;
//wait indefinitely if no shutdownTimeoutTimer (shutdownTimeout == 0)
if (shutdownTimeoutTimer != null) {
waitInterval = Duration.of(SHUTDOWN_TIMEOUT_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
}
// waitInterval == null - blocks indefinitely
while(!t.blockUntilEnded(waitInterval)) {
if (shutdownTimeoutTimer.isExpired()) {
log.warn("Timeout while waiting for applications to stop at "+t+".\n"+t.getStatusDetail(true));
throw new TimeoutException();
}
}
if (t.isError()) {
log.warn("Error stopping application "+t+" during shutdown (ignoring)\n"+t.getStatusDetail(true));
return false;
} else {
return true;
}
}
}.start();
synchronized (completed) {
if (!completed.get()) {
try {
long waitTimeout = 0;
//If the timeout for both shutdownTimeout and requestTimeout is equal
//then better wait until the 'completed' flag is set, rather than timing out
//at just about the same time (i.e. always wait for the shutdownTimeout in this case).
//This will prevent undefined behaviour where either one of shutdownTimeout or requestTimeout
//will be first to expire and the error flag won't be set predictably, it will
//toggle depending on which expires first.
//Note: shutdownTimeout is checked at SHUTDOWN_TIMEOUT_CHECK_INTERVAL interval, meaning it is
//practically rounded up to the nearest SHUTDOWN_TIMEOUT_CHECK_INTERVAL.
if (!isSingleTimeout) {
waitTimeout = requestTimeout.toMilliseconds();
}
completed.wait(waitTimeout);
} catch (InterruptedException e) {
throw Exceptions.propagate(e);
}
}
}
if (hasAppErrorsOrTimeout.get()) {
WebResourceUtils.badRequest("Error or timeout while stopping applications. See log for details.");
}
}