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}