/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.handle;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGPersistentHandle;
/**
* <p>
* An implementation of a live handle that tracks garbage collection activity by
* extending <code>PhantomReference</code>.
* </p>
*
* @author Borislav Iordanov
*
*/
public class PhantomHandle extends PhantomReference<Object> implements HGLiveHandle, Comparable<HGHandle>
{
private HGPersistentHandle persistentHandle;
private byte flags;
private static Field refField = null;
static
{
for (Class<? super PhantomReference<Object>> clazz = PhantomReference.class;
clazz != null && refField == null;
clazz = clazz.getSuperclass())
{
Field [] all = clazz.getDeclaredFields();
for (int i = 0; i < all.length; i++)
if (all[i].getName().equals("referent"))
{
refField = all[i];
refField.setAccessible(true);
break;
}
}
}
/**
* <strong>This is for internal use ONLY.</strong>
*
* <p>See comments in 'getRef' for information about this variable.</p>
*/
public static ThreadLocal<Boolean> returnEnqueued = new ThreadLocal<Boolean>();
public PhantomHandle(Object ref,
HGPersistentHandle persistentHandle,
byte flags,
ReferenceQueue<Object> refQueue)
{
super(ref, refQueue);
this.persistentHandle = persistentHandle;
this.flags = flags;
}
public byte getFlags()
{
return flags;
}
public HGPersistentHandle getPersistent()
{
return persistentHandle;
}
/**
* <p>
* A getter of the referent that uses reflection to access the field directly. Thus,
* the field is available even after it's finalized. Therefore, this method should
* only be called if it doesn't result in the referent becoming strongly reachable again.
* </p>
*/
public Object fetchRef()
{
try { return refField.get(this); } catch (Exception t) { throw new HGException(t); }
}
/**
* <p>
* A setter of the referent. This setter will block the current Thread while the reference
* is being enqueued by the grabage collector.
* </p>
*
* @param ref
*/
public void storeRef(Object ref)
{
while (isEnqueued())
try { synchronized (this) { wait(100); } } catch (InterruptedException ex) { }
try { refField.set(this, ref); } catch (Exception t) { throw new HGException(t); }
}
public Object getRef()
{
//
// Here, we want to return null when the object is about to be garbage
// collected. This will be indicated by the fact that the object will
// be enqueued in the ReferenceQueue for this PhantomReference.
//
// Obviously, we are doing something unorthodox here. The main danger
// and the first thing to examine in case of weird behavior with this
// is a situation where an atom gets removed from the 'atoms' WeakHashMap
// in the cache (hence it's being garbage collected) and 'getRef' is called
// after that but before the reference gets enqueued. Is that possible at all?
// It looks like the GC manages references and references queues in a high-priority
// thread. Also, when an object is going to be garbage collect its references are
// added to a "pending" list from where they are further enqueued. The 'isEnqueued'
// method actually checks for "pending or already enqueued" so we should be fine.
//
// However, there is a special case in which we don't want to return null: when
// we are in the phantom reference queue cleanup thread. Because cleanup may involve
// saving the atom which in turn might trigger a 'getRef' for another handle down
// in the reference queue, this may result in a deadlock within the phantom ref
// cleanup thread itself. For this we use the thread local variable 'returnEnqueued'
//
Object x = fetchRef();
if (isEnqueued())
{
Boolean f = returnEnqueued.get();
if (f != null && f.booleanValue())
return x;
x = null;
do
{
try { synchronized (this) { wait(100); } } catch (InterruptedException ex) { }
} while (isEnqueued());
}
return x;
}
public void accessed() { }
public final int hashCode()
{
return persistentHandle.hashCode();
}
public final boolean equals(Object other)
{
if (other == null || ! (other instanceof HGHandle))
return false;
else if (other instanceof HGLiveHandle)
return persistentHandle.equals(((HGLiveHandle)other).getPersistent());
else
return persistentHandle.equals((HGPersistentHandle)other);
}
public String toString()
{
return "phantomHandle(" + persistentHandle.toString() + ")";
}
public int compareTo(HGHandle h)
{
if (h instanceof HGPersistentHandle)
return this.persistentHandle.compareTo((HGPersistentHandle)h);
else
return this.persistentHandle.compareTo(((HGLiveHandle)h).getPersistent());
}
}