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.lang.ref.WeakReference;
023import java.nio.ByteBuffer;
024import java.util.concurrent.atomic.AtomicLong;
025
026import com.avpkit.ferry.FerryJNI;
027
028/**
029 * Returned by {@link IBuffer#getByteBuffer(int, int, java.util.concurrent.atomic.AtomicReference)}
030 * for users that want to explicitly manage when the returned {@link java.nio.ByteBuffer}
031 * is released.
032 * <p>
033 * This class creates a {@link WeakReference} that Ferry classes will use for
034 * memory management. We do this to avoid relying on Java's finalizer thread to
035 * keep up and instead make every new native allocation first release any
036 * unreachable objects.
037 * </p><p>
038 * Most times these objects are managed behind the scenes when you
039 * call {@link RefCounted#delete()}.  But when we return
040 * {@link java.nio.ByteBuffer} objects, there is no equivalent of
041 * delete(), so this object can be used if you want to explicitly control
042 * when the {@link ByteBuffer}'s underlying native memory is freed.
043 * </p>
044 * 
045 */
046public final class JNIReference extends WeakReference<Object>
047{
048  private final AtomicLong mSwigCPtr = new AtomicLong(0);
049
050  // This memory manager will outlive the Java object we're referencing; that
051  // means this class will sometimes show up as a potential leak, but trust us
052  // here; ignore all the refs to here and see who else is holding the ref to
053  // the JNIMemoryAllocator and that's your likely leak culprit (if we didn't
054  // do this, then you'd get no indication of who is leaking your native object
055  // so stop complaining now).
056  private volatile JNIMemoryAllocator mMemAllocator;
057
058  private final boolean mIsFerryObject;
059
060  private final AtomicLong mJavaRefCount;
061  
062  // A static memory allocator that is only constructed once
063  private static final JNIMemoryAllocator sMemAllocator = new JNIMemoryAllocator();
064  private final boolean sUseStaticMemAllocator = true;
065  
066  // Only turn this to true if you need memory debugging on.  Given how
067  // hot this code is, remembering debug info is an unnecessary extra
068  // step.
069  private static volatile boolean mMemoryDebugging = false;
070  /* package */ static void setMemoryDebugging(boolean value) {
071    mMemoryDebugging = value;
072  }
073  /* package */ static boolean isMemoryDebugging() {
074    return mMemoryDebugging; 
075  }
076  private static class DebugInfo {
077    private final int mHashCode;
078    private final Class<? extends Object> mClass;
079    public DebugInfo(Object aObject)
080    {
081      mClass = aObject.getClass();
082      mHashCode = aObject.hashCode();
083    }
084    public Class<? extends Object> getObjectClass()
085    {
086      return mClass;
087    }
088    public int getObjectHashCode()
089    {
090      return mHashCode;
091    }
092  }
093  final private DebugInfo mDebugInfo;
094  private JNIReference(
095      final Object proxy,
096      final Object aReferent,
097      final long nativeVal,
098      final boolean isFerry,
099      final AtomicLong javaRefCount)
100  {
101    super(aReferent, JNIMemoryManager.getMgr().getQueue());
102    mIsFerryObject = isFerry;
103    mJavaRefCount = javaRefCount;
104    mSwigCPtr.set(nativeVal);
105    if (mMemoryDebugging) {
106      mDebugInfo = new DebugInfo(proxy);
107    } else {
108      mDebugInfo = null;
109    }
110    JNIMemoryManager.MemoryModel model = JNIMemoryManager.getMemoryModel();
111    if (model == JNIMemoryManager.MemoryModel.JAVA_DIRECT_BUFFERS ||
112        model == JNIMemoryManager.MemoryModel.NATIVE_BUFFERS)
113      // don't use our allocators
114      return;
115    if (mJavaRefCount.get() == 1 && 
116        FerryJNI.RefCounted_getCurrentNativeRefCount(nativeVal, null) == 1)
117    {
118      // it's only safe to set the allocator if you're the only reference
119      // holder. Otherwise
120      // we default to having a null allocator, and allocations are anonymous,
121      // but at least
122      // won't crash under heavy multi-threaded situations.
123      if (sUseStaticMemAllocator)
124        mMemAllocator = sMemAllocator;
125      else
126        mMemAllocator = new JNIMemoryAllocator();
127      // we are the only owner of this object; tell it we're the object it can
128      // allocate from
129      JNIMemoryAllocator.setAllocator(nativeVal, mMemAllocator);
130    }
131    else
132    {
133      // This creates a Strong reference to the allocator currently being used
134      // so that if the Java proxy object the native object depended upon goes
135      // away, the memory manager it was using stays around until this reference
136      // is killed
137      mMemAllocator = JNIMemoryAllocator.getAllocator(nativeVal);
138    }
139  }
140
141  /**
142   * Returns the {@link JNIMemoryManager} we're using.
143   * 
144   * @return the manager
145   */
146  static JNIMemoryManager getMgr()
147  {
148    return JNIMemoryManager.getMgr();
149  }
150
151  static JNIReference createReference(Object proxy, 
152      Object aReferent, long swigCPtr,
153      boolean isFerry, AtomicLong javaRefCount)
154  {
155    // Clear out any pending native objects
156    JNIMemoryManager.getMgr().gcInternal();
157
158    JNIReference ref = new JNIReference(
159        proxy, aReferent,
160        swigCPtr, isFerry, javaRefCount);
161    JNIMemoryManager.getMgr().addReference(ref);
162    //System.err.println("added  : "+ref+"; "+swigCPtr+" ("+isFerry+")");
163    return ref;
164  }
165  static JNIReference createReference(Object proxy,
166      Object aReferent, long swigCPtr,
167      AtomicLong javaRefCount)
168  {
169    return createReference(proxy, aReferent, swigCPtr, true, javaRefCount);
170  }
171  static JNIReference createNonFerryReference(
172      Object proxy, Object aReferent,
173      long swigCPtr, AtomicLong javaRefCount)
174  {
175    return createReference(proxy, aReferent, swigCPtr, false, javaRefCount);
176  }
177
178
179  /**
180   * Explicitly deletes the underlying native storage used by
181   * the object this object references.  The underlying native
182   * object is now no long valid, and attempts to use it could
183   * cause unspecified behavior.
184   * 
185   */
186  public void delete()
187  {
188    // acquire lock for minimum time
189    final long swigPtr = mSwigCPtr.getAndSet(0);
190    if (swigPtr != 0)
191    {
192      if (mJavaRefCount.decrementAndGet() == 0)
193      {
194        // log.debug("deleting: {}; {}", this, mSwigCPtr);
195        FerryJNI.RefCounted_release(swigPtr, null);
196      }
197      // Free the memory manager we use
198      mMemAllocator = null;
199    }
200
201  }
202
203  /**
204   * Returns true if this object derived from {@link RefCounted}.
205   * @return true if a ferry object.
206   */
207  boolean isFerryObject()
208  {
209    return mIsFerryObject;
210  }
211 
212  /**
213   * Returns true if the underlying reference has had {@link #delete()} called.
214   * @return True if {@link #delete()} was called.
215   */
216  boolean isDeleted()
217  {
218    return mSwigCPtr.get() == 0;
219  }
220  /**
221   * Creates a string representation of this reference.  If the underlying
222   * object this reference points to has been deleted, then "native" will
223   * be zero.  If {@link JNIMemoryManager#isMemoryDebugging()} is true,
224   * then the class and hashcode of the object this reference points to (or
225   * used to point to) is also printed.
226   * <p>
227   * If the reference still points to an actual object, we will also print
228   * the contents of that object.  It may return "null" in which case the
229   * underlying object is no longer reachable, but if native != 0, it means
230   * it has not yet been collected by Ferry.
231   * </p>
232   */
233  @Override
234  public String toString()
235  {
236    final StringBuilder builder = new StringBuilder();
237    builder.append(super.toString());
238    builder.append("[");
239    builder.append("native=").append(mSwigCPtr.get()).append(";");
240    if (mDebugInfo != null) {
241      builder.append("proxyClass=").append(mDebugInfo.getObjectClass().getCanonicalName()).append(";");
242      builder.append("hashCode=").append(mDebugInfo.getObjectHashCode()).append(";");
243    }
244    builder.append("object=[").append(get()).append("];");
245    builder.append("];");
246    
247    return builder.toString();
248  }
249}