public void shutdown()

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.");
        }
    }