001/******************************************************************************* 002 * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved. 003 * 004 * This file is part of AVPKit. 005 * 006 * AVPKit is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Lesser General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * AVPKit is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with AVPKit. If not, see <http://www.gnu.org/licenses/>. 018 *******************************************************************************/ 019 020package com.avpkit.ferry; 021 022import java.util.concurrent.atomic.AtomicReference; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Proxy for {@link java.lang.Thread} that is called by Ferry native code. 029 * <p> 030 * This class is called from native code to tell if a thread 031 * is interrupted. It only exposes the methods that are called 032 * from the native code, and adds logging to aid in debugging. 033 * </p> 034 * <p> 035 * If allows a global handler to be set. This can be useful for 036 * catching threads that are started from native code as there is no other 037 * way to detect those and interrupt them. If set, <b>the global handler 038 * will always be checked BEFORE the local thread</b> allowing users to 039 * always get a callback on each interrupt check if they want. 040 * </p> 041 * <p> 042 * Do not change the name or location of this class without also 043 * changing the JNIHelper.cpp code that refers to it. 044 * </p> 045 * @author aclarke 046 * @since 5.0 047 */ 048public class JNIThreadProxy extends Thread 049{ 050 /** 051 * Interface that global interrupt handlers should use. 052 * @author aclarke 053 * 054 */ 055 public interface Interruptable { 056 /** 057 * Called BEFORE native code calls {@link java.lang.Thread#interrupted()}. 058 * <p> 059 * Please note that this method can be called from any thread 060 * and it is the responsibility of the caller to ensure it is 061 * thread safe. 062 * </p> 063 * 064 * @return true if the JNIThreadProxy should continue on an call {@link java.lang.Thread#interrupted()}. false to stop 065 * processing now (and tell callers the thread is NOT interrupted). 066 */ 067 public boolean preInterruptCheck(); 068 069 /** 070 * Called AFTER native code calls {@link java.lang.Thread#interrupted()} and allows 071 * the caller to override the returned value if desired. 072 * @param interruptStatus The value returned by {@link Thread#interrupted()} on the current thread 073 * 074 * @return The value you actually want to return to native code. Allows you to override the value returned. 075 */ 076 public boolean postInterruptCheck(boolean interruptStatus); 077 } 078 private static final AtomicReference<Interruptable> mGlobalHandler = new AtomicReference<JNIThreadProxy.Interruptable>(); 079 080 private static final Logger log = LoggerFactory 081 .getLogger(JNIThreadProxy.class); 082 083 private static final ThreadLocal<JNIThreadProxy> mThreads = new ThreadLocal<JNIThreadProxy>(); 084 private final Thread mThread; 085 private JNIThreadProxy(Thread thread) 086 { 087 mThread = thread; 088 } 089 090 /** 091 * Gets the current global interrupt handler 092 * @return The handler, or null if not set. 093 */ 094 public static Interruptable getGlobalInterruptHandler() 095 { 096 return mGlobalHandler.get(); 097 } 098 /** 099 * Set a new global interrupt handler. 100 * @param handler The new handler to use, or null to disable. 101 * @return The previous handler, or null if none is set. 102 */ 103 public static Interruptable setGlobalInterruptable(Interruptable handler) 104 { 105 return mGlobalHandler.getAndSet(handler); 106 } 107 /** 108 * @see java.lang.Thread#currentThread 109 * @return java.lang.Thread 110 */ 111 public static Thread currentThread() 112 { 113 JNIThreadProxy retval = mThreads.get(); 114 if (retval == null) { 115 retval = new JNIThreadProxy(Thread.currentThread()); 116 // store it in thread local storage 117 mThreads.set(retval); 118 } 119 log.trace("currentThread: {}", retval.mThread); 120 return retval; 121 } 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public boolean isInterrupted() 128 { 129 final Thread thread = mThreads.get().mThread; 130 final Interruptable handler = getGlobalInterruptHandler(); 131 boolean retval = false; 132 if (handler != null) { 133 retval = handler.preInterruptCheck(); 134 if (!retval) 135 return retval; 136 } 137 retval = thread.isInterrupted(); 138 if (handler != null) { 139 retval = handler.postInterruptCheck(retval); 140 } 141 return retval; 142 } 143 144 /** 145 * {@inheritDoc} 146 */ 147 @Override 148 public void interrupt() 149 { 150 final Thread thread = mThreads.get().mThread; 151 log.trace("interrupt (thread {})", thread); 152 thread.interrupt(); 153 return; 154 } 155}