001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020
021import javax.annotation.concurrent.NotThreadSafe;
022import java.time.Duration;
023
024import static java.util.Objects.requireNonNull;
025
026/** Indicates that core shutdown completed but bounded application cleanup did not. */
027@NotThreadSafe
028public final class SokletShutdownCleanupException
029                extends SokletLifecycleException {
030        @NonNull
031        private final ShutdownCleanupFailureReason shutdownCleanupFailureReason;
032        @NonNull
033        private final Duration shutdownCleanupTimeout;
034
035        SokletShutdownCleanupException(
036                        @NonNull ShutdownCleanupFailureReason shutdownCleanupFailureReason,
037                        @NonNull Duration shutdownCleanupTimeout,
038                        @NonNull ShutdownResult shutdownResult,
039                        @NonNull Throwable cause) {
040                super(cleanupFailureMessage(requireNonNull(shutdownCleanupFailureReason)),
041                                requireNonNull(shutdownResult), requireNonNull(cause));
042                this.shutdownCleanupFailureReason = shutdownCleanupFailureReason;
043                this.shutdownCleanupTimeout = requireNonNull(shutdownCleanupTimeout);
044                if (!shutdownResult.isComplete())
045                        throw new IllegalArgumentException(
046                                        "Cleanup failure requires a complete core result");
047        }
048
049        SokletShutdownCleanupException(
050                        @NonNull ShutdownCleanupFailureReason shutdownCleanupFailureReason,
051                        @NonNull Duration shutdownCleanupTimeout,
052                        @NonNull InternalShutdownResult shutdownResult,
053                        @NonNull Throwable cause) {
054                this(shutdownCleanupFailureReason, shutdownCleanupTimeout,
055                                ShutdownResult.fromInternal(requireNonNull(shutdownResult)), cause);
056        }
057
058        /** @return cleanup failure reason */
059        @NonNull
060        public ShutdownCleanupFailureReason getShutdownCleanupFailureReason() {
061                return this.shutdownCleanupFailureReason;
062        }
063
064        /** @return configured cleanup timeout */
065        @NonNull
066        public Duration getShutdownCleanupTimeout() {
067                return this.shutdownCleanupTimeout;
068        }
069
070        @NonNull
071        private static String cleanupFailureMessage(
072                        @NonNull ShutdownCleanupFailureReason shutdownCleanupFailureReason) {
073                return switch (requireNonNull(shutdownCleanupFailureReason)) {
074                        case FAILED -> "Standalone Soklet cleanup failed";
075                        case TIMED_OUT -> "Standalone Soklet cleanup timed out; its daemon "
076                                        + "action may remain live";
077                };
078        }
079}