NativeCleaner.java

package com.surrealdb;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

/**
 * Frees native objects whose Java owner was garbage collected without being
 * closed. This is a small Java 8 compatible stand-in for
 * {@code java.lang.ref.Cleaner}: each {@link Native} registers a
 * {@link Handle}, a phantom reference that owns the native pointer and knows
 * how to free it without touching the (by then unreachable) owner.
 *
 * <p>
 * The pointer is cleared with an atomic {@code getAndSet(0)} by whichever comes
 * first: an explicit close, a hand-over to the native side ({@code take()}), or
 * the cleanup after GC. So these can race without freeing the pointer twice or
 * handing it over twice.
 *
 * <p>
 * Like the JDK cleaner, live handles are kept in an intrusive doubly linked
 * list, so registering and releasing a handle allocates nothing beyond the
 * handle itself.
 */
final class NativeCleaner {

	/** Frees the native object behind a pointer. Must not capture its owner. */
	interface Deleter {
		void delete(long ptr);
	}

	/** Owns one native pointer on behalf of a {@link Native}. */
	static final class Handle extends PhantomReference<Native> {

		private static final AtomicLongFieldUpdater<Handle> PTR = AtomicLongFieldUpdater.newUpdater(Handle.class,
				"ptr");

		private volatile long ptr;

		private final Deleter deleter;

		// Links in the list of live handles, guarded by LIVE. A handle that is not in
		// the list points to itself.
		private Handle prev = this;

		private Handle next = this;

		private Handle(Native owner, long ptr, Deleter deleter) {
			super(owner, QUEUE);
			this.ptr = ptr;
			this.deleter = deleter;
		}

		/** The list head: an empty handle that is never enqueued. */
		private Handle() {
			super(null, null);
			this.deleter = null;
		}

		long ptr() {
			return ptr;
		}

		/** Frees the native object, at most once. */
		void release() {
			final long p = PTR.getAndSet(this, 0);
			forget();
			if (p != 0) {
				deleter.delete(p);
			}
		}

		/**
		 * Gives up ownership and returns the pointer, for a native call that takes the
		 * object over. Returns 0 if it was already released or taken.
		 */
		long take() {
			final long p = PTR.getAndSet(this, 0);
			forget();
			return p;
		}

		private void forget() {
			synchronized (LIVE) {
				if (next != this) {
					next.prev = prev;
					prev.next = next;
					prev = this;
					next = this;
				}
			}
			clear();
		}
	}

	private static final ReferenceQueue<Native> QUEUE = new ReferenceQueue<>();

	// Keeps every handle strongly reachable until it has been released, so the
	// phantom reference is not collected before its owner is.
	private static final Handle LIVE = new Handle();

	private static final MethodHandle REACHABILITY_FENCE = findReachabilityFence();

	static {
		final Thread thread = new Thread(NativeCleaner::drain, "surrealdb-native-cleaner");
		thread.setDaemon(true);
		thread.start();
	}

	private NativeCleaner() {
	}

	static Handle register(Native owner, long ptr, Deleter deleter) {
		final Handle handle = new Handle(owner, ptr, deleter);
		try {
			if (ptr != 0) {
				synchronized (LIVE) {
					handle.prev = LIVE;
					handle.next = LIVE.next;
					LIVE.next.prev = handle;
					LIVE.next = handle;
				}
			}
		} finally {
			// If the owner became unreachable before the handle was added, the cleaner
			// could release it first and the add would then keep it forever.
			reachabilityFence(owner);
		}
		return handle;
	}

	/**
	 * Keeps {@code o} strongly reachable up to this call, so that its native object
	 * cannot be freed by the cleaner while a native call that received its pointer
	 * is still running. Uses {@code Reference.reachabilityFence} on Java 9+, and a
	 * synchronized block on Java 8.
	 */
	static void reachabilityFence(java.lang.Object o) {
		if (REACHABILITY_FENCE != null) {
			try {
				REACHABILITY_FENCE.invokeExact(o);
				return;
			} catch (Throwable t) {
				// Fall through to the Java 8 approach.
			}
		}
		synchronized (o) {
			// An empty synchronized block on o keeps it reachable up to here.
		}
	}

	private static MethodHandle findReachabilityFence() {
		try {
			return MethodHandles.lookup().findStatic(Reference.class, "reachabilityFence",
					MethodType.methodType(void.class, java.lang.Object.class));
		} catch (ReflectiveOperationException | RuntimeException e) {
			return null;
		}
	}

	private static void drain() {
		while (true) {
			try {
				((Handle) QUEUE.remove()).release();
			} catch (InterruptedException e) {
				// Daemon thread: keep draining.
			} catch (Throwable t) {
				// A failing deleter must not stop the cleaner.
			}
		}
	}
}