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}