private UnmodifiableFuture<Void> stop0(@Nullable U arg, boolean rollback) {
        lock();
        try {
            switch (state) {
                case STARTING:
                    if (!rollback) {
                        // Try again once started.
                        return UnmodifiableFuture.wrap(
                                future.exceptionally(unused -> null) // Ignore the exception.
                                      .thenComposeAsync(unused -> stop(arg), executor));
                    } else {
                        break;
                    }
                case STOPPING:
                case STOPPED:
                    @SuppressWarnings("unchecked")
                    final UnmodifiableFuture<Void> castFuture =
                            (UnmodifiableFuture<Void>) future;
                    return castFuture;
            }

            assert state == State.STARTED || rollback : "state: " + state + ", rollback: " + rollback;
            final State oldState = state;
            state = State.STOPPING;

            final CompletableFuture<Void> stopFuture = new CompletableFuture<>();
            boolean submitted = false;
            try {
                executor.execute(() -> {
                    try {
                        notifyListeners(State.STOPPING, null, arg, null);
                        final CompletionStage<Void> f = doStop(arg);
                        checkState(f != null, "doStop() returned null.");

                        f.handle((unused, cause) -> {
                            if (cause != null) {
                                stopFuture.completeExceptionally(cause);
                            } else {
                                stopFuture.complete(null);
                            }
                            return null;
                        });
                    } catch (Exception e) {
                        stopFuture.completeExceptionally(e);
                    }
                });
                submitted = true;
            } catch (Exception e) {
                return exceptionallyCompletedFuture(e);
            } finally {
                if (!submitted) {
                    state = oldState;
                }
            }

            final UnmodifiableFuture<Void> future = UnmodifiableFuture.wrap(
                    stopFuture.whenCompleteAsync((unused1, cause) -> enter(State.STOPPED, null, arg, null),
                                                 executor));
            this.future = future;
            return future;
        } finally {
            unlock();
        }
    }
Begins the shutdown procedure by calling {@link #doStop(Object)}, ensuring that neither
{@link #doStart(Object)} nor {@link #doStop(Object)} is invoked concurrently.

@param arg the argument to pass to {@link #doStop(Object)}, or {@code null} to pass no argument.
core/src/main/java/com/linecorp/armeria/common/util/StartStopSupport.java