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 javax.annotation.concurrent.ThreadSafe;
023import java.time.Duration;
024
025import static java.util.Objects.requireNonNull;
026
027/**
028 * Immutable specification for one bounded, synchronous cleanup action for an
029 * ingress-exclusive application resource. Returning after merely delegating
030 * asynchronous work is not proof that the resource was released.
031 * <p>
032 * At the cleanup deadline, Soklet requests interruption of a running action and
033 * stops waiting for it. Interruption is cooperative and may cause
034 * interruption-sensitive I/O to fail; it does not forcibly terminate arbitrary
035 * cleanup code. Actions should respond promptly to interruption and apply their
036 * own bounds to blocking work.
037 */
038@ThreadSafe
039public final class ShutdownCleanup {
040        @NonNull
041        private final Duration timeout;
042        @NonNull
043        private final Action action;
044
045        private ShutdownCleanup(@NonNull Duration timeout,
046                        @NonNull Action action) {
047                Duration exactTimeout = requireNonNull(timeout);
048                final long timeoutNanos;
049                try {
050                        timeoutNanos = exactTimeout.toNanos();
051                } catch (ArithmeticException exception) {
052                        throw new IllegalArgumentException(
053                                        "Shutdown cleanup timeout exceeds signed nanoseconds",
054                                        exception);
055                }
056                if (timeoutNanos <= 0L)
057                        throw new IllegalArgumentException(
058                                        "Shutdown cleanup timeout must be greater than zero");
059                this.timeout = exactTimeout;
060                this.action = requireNonNull(action);
061        }
062
063        /**
064         * Creates a cleanup specification from its deadline budget and synchronous
065         * action.
066         *
067         * @param timeout positive cleanup deadline budget representable as signed
068         * nanoseconds
069         * @param action synchronous cleanup action
070         * @return immutable cleanup specification
071         * @throws IllegalArgumentException if {@code timeout} is non-positive or
072         * exceeds signed-nanosecond range
073         */
074        @NonNull
075        public static ShutdownCleanup fromTimeoutAndAction(
076                        @NonNull Duration timeout, @NonNull Action action) {
077                return new ShutdownCleanup(timeout, action);
078        }
079
080        /** @return positive cleanup deadline budget */
081        @NonNull
082        public Duration getTimeout() {
083                return this.timeout;
084        }
085
086        @NonNull
087        Action action() {
088                return this.action;
089        }
090
091        /**
092         * One synchronous cleanup action for an ingress-exclusive application
093         * resource. The standalone runner invokes an action at most once and never
094         * concurrently; implementations are not required to be thread-safe.
095         */
096        @NotThreadSafe
097        @FunctionalInterface
098        public interface Action {
099                /**
100                 * Performs cleanup after complete core shutdown.
101                 *
102                 * @param shutdownResult exact complete core shutdown result
103                 * @throws Exception if cleanup fails
104                 */
105                void performCleanup(@NonNull ShutdownResult shutdownResult)
106                                throws Exception;
107        }
108}