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.ThreadSafe; 022import java.util.Optional; 023 024/** 025 * Thread-safe cancelation signal for response stream producers. 026 * <p> 027 * Producers should check this token between expensive or blocking operations and stop producing when it becomes 028 * canceled. Soklet cancels the token when a streaming response can no longer continue, such as when the client 029 * disconnects, the server shuts down, the request HTTP version cannot support streaming, or a streaming timeout is 030 * reached. 031 * 032 * @author <a href="https://www.revetkn.com">Mark Allen</a> 033 */ 034@ThreadSafe 035public interface CancelationToken { 036 /** 037 * Is the associated operation canceled? 038 * 039 * @return {@code true} if canceled 040 */ 041 @NonNull 042 Boolean isCanceled(); 043 044 /** 045 * The cancelation reason, if cancelation has occurred. 046 * 047 * @return the cancelation reason, or {@link Optional#empty()} if not canceled 048 */ 049 @NonNull 050 Optional<StreamTerminationReason> getCancelationReason(); 051 052 /** 053 * The underlying cancelation cause, if available. 054 * 055 * @return the underlying cause, or {@link Optional#empty()} if no cause is available 056 */ 057 @NonNull 058 Optional<Throwable> getCancelationCause(); 059 060 /** 061 * Registers a callback that runs when the token is canceled. 062 * <p> 063 * The returned handle removes the callback when closed. If the token is already canceled, the callback may run 064 * before this method returns. 065 * <p> 066 * Callbacks run synchronously on the thread that performs cancelation. Keep callbacks fast and non-blocking; if 067 * cleanup may take meaningful time, dispatch it to an application-owned executor from the callback. 068 * 069 * @param callback the callback to run on cancelation 070 * @return a handle that removes the callback when closed 071 */ 072 @NonNull 073 AutoCloseable onCancel(@NonNull Runnable callback); 074 075 /** 076 * Throws if the token has been canceled. 077 * 078 * @throws StreamingResponseCanceledException if canceled 079 */ 080 default void throwIfCanceled() throws StreamingResponseCanceledException { 081 StreamTerminationReason reason = getCancelationReason().orElse(null); 082 083 if (reason != null) 084 throw new StreamingResponseCanceledException(reason, getCancelationCause().orElse(null)); 085 } 086}