// You can redistribute this software and/or modify it under the terms of
// the Ozone Core License version 1 published by ozone-db.org.
//
// The original code and portions created by SMB are
// Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
//
// $Id: GarbageCollector.java,v 1.3 2002/07/26 12:29:22 per_nyfelt Exp $
package org.ozoneDB.core;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import org.ozoneDB.data.SimpleArrayList;
import org.ozoneDB.io.stream.NullOutputStream;
import org.ozoneDB.DxLib.DxIterator;
import org.ozoneDB.core.DbRemote.CommandThread;
import org.ozoneDB.OzoneProxy;
import org.ozoneDB.OzoneCompatible;
import org.ozoneDB.OzoneCompatibleOrProxy;
import org.ozoneDB.ObjectNotFoundExc;
import org.ozoneDB.TransactionExc;
import org.ozoneDB.OzoneRemoteExc;
import org.ozoneDB.OzoneInternalExc;
/**
Marks reachable objects and sweeps unreachable objects.
<DIV>
This implementation has at least following deficiencies:
<UL>
<LI>
Method invocation observation is limited to the direct method parameters and the return value.
A correct implementation should observe not only method parameters and return values but
all objects which are reachable from these objects.
</LI>
<LI>
Currently, the implementation is not scaleable, because the list
{@link #surelyReachableObjectsWhichHaveToBeMarkedAsSuch} may grow to the count of objects.
Possible solutions are:
<UL>
<LI>
Export some contents of this stack to disk. This seems to be highly efficient, because
the "hot spot" of the corresponding file always would be the end of the file.
</LI>
<LI>
Simply forget entries if they are too much. Work through the whole list until it is empty.
Once the list is empty, walk through all objects to refill this list and continue.
This approach has some appeals, but seems to be element of O(n^2) for large databases because
there would be n*c1 walks trough 1/2*n objects each walk.
</LI>
</UL>
</LI>
</UL>
</DIV>
@author Xu�n Baldauf (<A HREF="http://www.medium.net/">Medium.net</A)>
*/
public class GarbageCollector extends ServerComponent implements Runnable {
/**
Our database environment
*/
// protected Env env;
/**
The thread which actually does the work.
*/
protected Thread garbageCollectionThread;
/**
The list of {@link Transaction}s, which have to be completed before the GarbageCollector
may start. It may not start earlier because the rollback of those transaction may make
objects live which were believed to be dead.
*/
protected HashSet transactionsRequiredToComplete;
/**
The number which represents the current garbage collection run.
This number is the number mentioned in {@link org.ozoneDB.core.wizardStore.WizardObjectContainer#garbageCollectionLevel}.
There are tree sets of database objects
<UL>
<LI>probablyReachable: These objects may or may not be reachable. Their garbageCollectionLevel is below the currentGarbageCollectionLevel</LI>
<LI>surelyReachable: These objects have been reached during the walk. Their garbageCollectionLevel is currentGarbageCollectionLevel</LI>
<LI>doneReachable: These object have been processed for members. All their members are surelyReachable or better. Their garbageCollectionLevel is currentGarbageCollectionLevel+1</LI>
</UL>
*/
protected int currentGarbageCollectionLevel;
/**
The garbageCollectionLevel which objects have if they belong to the doneReachable set.
This is {@link #currentGarbageCollectionLevel}+1
*/
protected int doneReachableGarbageCollectionLevel;
/**
The current phase this GarbageCollector is in. Access only within synchronization to this object.
*/
protected int phase = PHASE_IDLE;
/** Doing nothing */
protected final static int PHASE_IDLE = 0;
/** A run has been initiated */
protected final static int PHASE_RUN_INITIATED = 1;
/** Waiting for the old transactions to complete */
protected final static int PHASE_WAITING_FOR_OLD_TRANSACTIONS_TO_COMPLETE = 2;
/** We may start, all old transactions are complete */
protected final static int PHASE_READY_TO_START = 3;
/** We are started, we are marking all reachable objects */
protected final static int PHASE_MARKING = 4;
/** Waiting for the old transactions to complete */
protected final static int PHASE_WAITING_FOR_NEW_TRANSACTIONS_TO_COMPLETE = 5;
/** We are started, we are marking all reachable objects */
protected final static int PHASE_MARKING2 = 6;
/** We are started, we are sweeping all unreachable objects */
protected final static int PHASE_SWEEPING = 7;
/** Somebody requested to finish prematurely... */
// protected final static int PHASE_KILL = -1;
/**
The current transaction of this garbageCollector.
*/
protected Transaction transaction;
/**
The count of actions which were done within this garbageCollector.
*/
protected int actionsWithinTransactionCount = 0;
/**
A stack of ObjectIDs of objects, which are surely reachable, but still
were not marked as such.
Access only during synchronization on it.
*/
// protected DxListDeque surelyReachableObjectsWhichHaveToBeMarkedAsSuch;
protected SimpleArrayList surelyReachableObjectsWhichHaveToBeMarkedAsSuch;
/**
The list of ObjectIDs of objects which are surelyReachable and already were tried to
be processed but where locking the objects failed. The processing the contents of this
list should be retried some times later.
To minimize overhead, access is only allowed during synchronization to {@link #surelyReachableObjectsWhichHaveToBeMarkedAsSuch}
*/
protected LinkedList surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented;
/**
This is the lock that everybody has to be synchronized to in order to
be able to access any garbageCollectionLevel in any object within this database.
*/
protected Object garbageCollectionLevelsLock = new Object();
/**
Wether we should stop running as soon as possible. Synchronization is not required,
because it does not matter wether we receive this signal soon or later.
*/
protected boolean kill = false;
/**
Creates a new garbage collector.
*/
protected GarbageCollector(Env env) {
super(env);
// this.env = env;
setCurrentGarbageCollectionLevel(env.getState().intProperty(env.getState().GARBAGE_COLLECTION_LEVEL,0));
// surelyReachableObjectsWhichHaveToBeMarkedAsSuch = new DxListDeque;
surelyReachableObjectsWhichHaveToBeMarkedAsSuch = new SimpleArrayList(10000); // We expect at least 10000 entries in a busy database.
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented = new LinkedList();
transactionsRequiredToComplete = new HashSet();
}
protected void setCurrentGarbageCollectionLevel(int to) {
synchronized (garbageCollectionLevelsLock) {
this.currentGarbageCollectionLevel = to;
this.doneReachableGarbageCollectionLevel = this.currentGarbageCollectionLevel+1;
}
}
public void startup() {
env.getLogWriter().newEntry( this, "startup...", env.getLogWriter().DEBUG3);
}
public void shutdown() {
kill = true;
}
public void save() {
}
/**
Starts the garbage collection process.
*/
public void start() {
// env.getLogWriter().newEntry(this,"start()",env.getLogWriter().INFO);
synchronized (this) {
if (phase==PHASE_IDLE) {
if (garbageCollectionThread==null) {
setPhase(PHASE_RUN_INITIATED);
garbageCollectionThread = env.getInvokeServer().newThread(this);
garbageCollectionThread.start();
} else {
// We're already started
}
} else {
// We're already started
}
}
}
public void addTransactionRequiredToComplete(Object ta) {
transactionsRequiredToComplete.add(ta);
}
public void removeTransactionRequiredToComplete(Transaction ta) {
if (!transactionsRequiredToComplete.isEmpty()) {
transactionsRequiredToComplete.remove(ta);
checkForEndOfWaitForCurrentTransactionsToCompletePhase();
}
}
protected void checkForEndOfWaitForCurrentTransactionsToCompletePhase() {
if (transactionsRequiredToComplete.isEmpty()) {
notifyEndOfWaitForCurrentTransactionsToCompletePhase();
}
}
/**
Informs this GarbageCollector, the the pre-phase is done, there are no transactions that
are older than this garbage collector run.
*/
protected void notifyEndOfWaitForCurrentTransactionsToCompletePhase() {
synchronized (this) {
/*
if (phase==PHASE_WAITING_FOR_OLD_TRANSACTIONS_TO_COMPLETE) {
phase = PHASE_READY_TO_START;
}
*/
setPhase(phase+1);
notify();
}
}
protected void incCurrentGarbageCollectorLevel() {
setCurrentGarbageCollectionLevel(currentGarbageCollectionLevel+4);
env.getState().setIntProperty(env.getState().GARBAGE_COLLECTION_LEVEL,currentGarbageCollectionLevel);
setChanged();
}
/**
The main method for garbage collection.
*/
public void run() {
env.getLogWriter().newEntry(this,"garbage collection start...",env.getLogWriter().INFO);
try {
incCurrentGarbageCollectorLevel();
setPhase(PHASE_WAITING_FOR_OLD_TRANSACTIONS_TO_COMPLETE);
env.getTransactionManager().startGarbageCollectionWaitForCurrentTransactionsToCompletePhase(this);
waitForPhase(PHASE_READY_TO_START);
renewTransactionIfRequired();
addRootSetElementsToSurelyReachableSet();
setPhase(PHASE_MARKING);
processSurelyReachableObjectsWhichHaveToBeMarkedAsSuch();
if (kill) {
return;
}
setPhase(PHASE_WAITING_FOR_NEW_TRANSACTIONS_TO_COMPLETE);
env.getTransactionManager().startGarbageCollectionWaitForCurrentTransactionsToCompletePhase(this);
waitForPhase(PHASE_MARKING2);
processSurelyReachableObjectsWhichHaveToBeMarkedAsSuch();
if (kill) {
return;
}
setPhase(PHASE_SWEEPING);
sweepUnreachableObjects();
} catch (Throwable e) {
env.getLogWriter().newEntry(this,"GC caught: ",e,env.getLogWriter().ERROR);
} finally {
env.getLogWriter().newEntry(this,"garbage collection end...",env.getLogWriter().INFO);
garbageCollectionThread = null;
setPhase(PHASE_IDLE);
try {
if (transaction!=null) { // We are crashing with an open transaction, let's abort it..
internalAbortTransaction(transaction);
// internalFinishTransaction(transaction);
env.getTransactionManager().deleteTransaction();
transaction = null;
}
} catch (ClassNotFoundException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
} catch (IOException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
}
}
}
protected void notifyAboutTransactionActionAndRenewTransactionIfRequired() throws IOException,ClassNotFoundException,TransactionExc {
renewTransactionIfRequired();
actionsWithinTransactionCount++;
}
/**
Checks wether the current transaction has to be committed (because it accumulated too much changes)
and if so, commits it and closes it. Afterwards (or if there has not been a current transaction already),
it creates a new current transaction.
*/
protected void renewTransactionIfRequired() throws IOException,ClassNotFoundException,TransactionExc {
if (actionsWithinTransactionCount>=100) { // We renew if we had 100 or more actions within this transaction.
completeTransaction();
env.getLogWriter().newEntry(this,"toBeProcessedCount="+surelyReachableObjectsWhichHaveToBeMarkedAsSuch.size()+".",env.getLogWriter().DEBUG1);
}
if (transaction==null) {
transaction = env.getTransactionManager().newTransaction(env.getUserManager().getGarbageCollectorUser());
}
}
/**
Completes the current transaction and releases it.
*/
protected void completeTransaction() throws IOException,ClassNotFoundException {
if (transaction!=null) {
internalCompleteTransaction(transaction);
actionsWithinTransactionCount = 0;
env.getTransactionManager().deleteTransaction();
transaction = null;
}
}
protected void internalCompleteTransaction(Transaction transaction) throws IOException,ClassNotFoundException {
boolean allright = false;
try {
transaction.prepareCommit();
allright = true;
} finally {
if (!allright) {
internalAbortTransaction(transaction);
}
}
internalFinishTransaction(transaction);
}
protected void internalFinishTransaction(Transaction transaction) throws IOException,ClassNotFoundException {
TransactionManager transactionManager = transaction.getManager();
try {
transactionManager.beginExclusion();
transaction.commit();
} finally {
transactionManager.endExclusion();
transactionManager.notifyWaitingTransactions();
}
}
protected void internalAbortTransaction(Transaction transaction) throws IOException,ClassNotFoundException {
TransactionManager transactionManager = transaction.getManager();
try {
transactionManager.beginExclusion();
transaction.abort(null);
} finally {
transactionManager.endExclusion();
transactionManager.notifyWaitingTransactions();
}
}
protected synchronized void setPhase(int to) {
phase = to;
env.getLogWriter().newEntry(this,"setPhase("+to+")",env.getLogWriter().DEBUG2);
}
protected synchronized void waitForPhase(int newPhase) throws InterruptedException {
while (phase!=newPhase) {
wait();
}
}
protected void addRootSetElementsToSurelyReachableSet() {
startFilterDatabaseObjectReferencesAtExternalDatabaseGates();
addAllNamedObjectsToSurelyReachableSet();
}
protected void startFilterDatabaseObjectReferencesAtExternalDatabaseGates() {
env.getInvokeServer().startFilterDatabaseObjectReferencesExports(this);
env.getLocalClientTracker().startFilterDatabaseObjectReferencesExports(this);
}
public void notifyDatabaseObjectIsExported(ObjectID id) {
notifyDatabaseObjectIsAboutToBeExported(id);
}
/**
This method is called by DbInvokeClient to notify this garbageCollector
that there is a reference which is to be exported to a client.
*/
public void notifyDatabaseObjectIsAboutToBeExported(ObjectID id) {
// These object referenced belong, because they are used, to the surelyReachable set.
ensureSurelyReachable(id);
}
/**
This method walks through all named objects and adds each of them to the surelyReachable set.
*/
protected void addAllNamedObjectsToSurelyReachableSet() {
env.getStoreManager().reportNamedObjectsToGarbageCollector();
}
/**
This method is called by StoreManager to indicate that the object
with the given id is a named object.
*/
public void notifyNamedObject(ObjectID id) {
ensureSurelyReachable(id);
}
/**
This method is called by StoreManager in the event an unnamed object receives a name.
This is the case if an object which was formerly unnamed gets a name.
*/
public void notifyNewObjectName(ObjectContainer objectContainer) {
ensureSurelyReachable(objectContainer);
}
/**
This method is called by StoreManager in the event an object is freshly created.
*/
public void notifyNewObjectContainer(/*Transaction transaction,*/ObjectContainer objectContainer) {
ensureDoneReachable(/*transaction,*/objectContainer);
}
/**
Calling this method makes sure that the object which is referenced by the given ID is
at least surely reachable.
*/
protected void ensureSurelyReachable(ObjectID id) {
synchronized (this) {
if (isRunning()) {
addObjectIDToSurelyReachableObjectsWhichHaveToBeMarkedAsSuch(id);
}
}
}
/**
This method may be called both from transaction threads and from GarbageCollector-Threads.
*/
protected void addObjectIDToSurelyReachableObjectsWhichHaveToBeMarkedAsSuch(ObjectID id) {
synchronized (surelyReachableObjectsWhichHaveToBeMarkedAsSuch) {
// env.getLogWriter().newEntry(this,"addObjectID(): adding "+id+".",new Exception(),env.getLogWriter().DEBUG3);
surelyReachableObjectsWhichHaveToBeMarkedAsSuch.push(id);
}
}
/**
Calling this method makes sure that the object which is referenced by the given ID is
at least surely reachable. This method is called by non-GarbageCollector-Threads.
*/
protected void ensureSurelyReachable(ObjectContainer objectContainer) {
// This is not possible because processing has to be done within a GarbageCollector transaction and not within any other transaction.
/*
objectContainer.pin();
processObjectContainerWhichWantsToBeSurelyReachableAndUnpin(objectContainer);
*/
addObjectIDToSurelyReachableObjectsWhichHaveToBeMarkedAsSuch(objectContainer.id());
}
/**
Returns wether this GarbageCollector is running. This method
must be called during synchronization on this GarbageCollector.
*/
protected boolean isRunning() {
return phase!=PHASE_IDLE;
}
/**
This is the "main()" method of the mark phase.
*/
protected void processSurelyReachableObjectsWhichHaveToBeMarkedAsSuch() {
ObjectID id;
int surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize = -1;
retryLoop:
for (;;) {
for (;;) {
if (kill) {
break retryLoop;
}
synchronized (surelyReachableObjectsWhichHaveToBeMarkedAsSuch) {
id = (ObjectID) surelyReachableObjectsWhichHaveToBeMarkedAsSuch.pop();
}
if (id==null) {
break;
}
try {
/*
ObjectContainer container = env.getStoreManager().containerForIDAndPin(null,id);
processObjectContainerWhichWantsToBeSurelyReachableAndUnpin(container);
*/
// We should acquire the container via our transaction
notifyAboutTransactionActionAndRenewTransactionIfRequired();
// env.getLogWriter().newEntry(this,"process(): processing "+id+".",env.getLogWriter().DEBUG3);
ObjectContainer container = transaction.acquireObjectAndPin(id,Lock.LEVEL_READ);
processObjectContainerWhichWantsToBeSurelyReachableAndUnpin(transaction,container);
// } catch (ObjNotFoundException e) {
} catch (ObjectNotFoundExc e) {
} catch (ClassNotFoundException e) {
} catch (IOException e) {
// It does not matter if the object is gone in the meantime. This is the best case for a garbage collector :-)
} catch (TransactionExc e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
// FIXME. What do we in this case?
}
}
boolean waitRecommended;
synchronized (surelyReachableObjectsWhichHaveToBeMarkedAsSuch) {
waitRecommended = surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize==surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.size();
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize = surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.size();
while (!surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.isEmpty()) {
surelyReachableObjectsWhichHaveToBeMarkedAsSuch.push(surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.getFirst());
}
}
if (surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize>0) {
if (waitRecommended) {
try {
try {
completeTransaction(); // We should finish our transaction so that other transactions may complete.
} catch (ClassNotFoundException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
} catch (IOException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
}
// We're waiting less if there are more lock-contented objects, but at least 100ms
Thread.sleep(100+2000/(surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContentedSize));
} catch (InterruptedException e) {
}
}
} else {
break;
}
}
try {
completeTransaction();
} catch (ClassNotFoundException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
} catch (IOException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
}
// Pheew. We're done. Now sweep the unreachable objects.
}
/**
Internally ensures that the given object is at least surelyReachable.
@return
<0 if this object has been updated. If this is the case, this object has to join a transaction which will complete successfully.
=0 if this object has not been updated, it is surelyReachable
>0 if this object has not been updated, it is processedReachable
*/
protected int internalEnsureSurelyReachable(ObjectContainer objectContainer) {
synchronized (garbageCollectionLevelsLock) {
return objectContainer.ensureGarbageCollectionLevel(currentGarbageCollectionLevel);
}
}
/**
Ensures that the given object is doneReachable.
*/
protected void ensureDoneReachable(/*Transaction transaction,*/ObjectContainer objectContainer) {
internalEnsureDoneReachable(objectContainer);
}
/**
Internally ensures that the given object is doneReachable.
*/
protected void internalEnsureDoneReachable(ObjectContainer objectContainer) {
synchronized (garbageCollectionLevelsLock) {
objectContainer.ensureGarbageCollectionLevel(doneReachableGarbageCollectionLevel);
}
}
/**
Processes an ObjectContainer which possibly has not already been registered as surely reachable.
// This method may be called by any CommandThread.
This method may be called only by the garbage collector thread.
*/
protected void processObjectContainerWhichWantsToBeSurelyReachableAndUnpin(Transaction transaction,ObjectContainer objectContainer) {
try {
int difference = internalEnsureSurelyReachable(objectContainer);
// env.getLogWriter().newEntry(this,"processObjectContainerWhichWantsToBeSurelyReachableAndUnpin("+objectContainer+"), difference="+difference+".",env.getLogWriter().DEBUG3);
if (difference<=0) {
processReferencesByThisObject(transaction,objectContainer);
}
} finally {
objectContainer.unpin();
}
}
/**
Determines all references which are "contained" within the given object,
adds them to the list of {@link #surelyReachableObjectsWhichHaveToBeMarkedAsSuch}
and then marks the given object as doneReachable.
This method may be called by any CommandThread.
*/
protected void processReferencesByThisObject(Transaction transaction,ObjectContainer objectContainer) {
/*
CommandThread thread = (CommandThread) Thread.currentThread();
Transaction transaction = thread.transaction();
*/
/*
We really only need READ locks, because only read and do not write the object.
But with READ locks, there may be transactions which have invoked a method on
this object. We do not want to read the object in this state, because there
may be references temporarily taken out of the object but onto the stack
which we would miss.
That is why we must ensure that nobody has invoked the object currently.
This should be - in theory - be possible by checking WizardObjectContainer.invokeCount,
but this is not an option in practice because accessing invokeCount is not
synchronized. This may lead to the situation that this thread does not see
an invokeCount!=1 while the other thread which invoked the object already has
set invokeCount to !=1.
Additionally, checking invokeCount would not suffice because a transaction thread
may invoke the object during our proxy identification.
That is why we must use an exclusive lock, which is a write lock.
*/
// int previousLockLevel = objectContainer.lock().tryAcquire(transaction,Lock.LEVEL_READ);
int previousLockLevel = objectContainer.lock().tryAcquire(transaction,Lock.LEVEL_WRITE);
if (previousLockLevel!=Lock.NOT_ACQUIRED) {
try {
env.getStoreManager().updateLockLevel(transaction,objectContainer);
GarbageCollectorProxyObjectIdentificationObjectOutputStream identificator = new GarbageCollectorProxyObjectIdentificationObjectOutputStream();
identificator.identifyProxys(objectContainer);
internalEnsureDoneReachable(objectContainer);
} catch (IOException e) {
// only supported from JDK1.4 on
// throw new RuntimeException("Caught during serialization for proxy object identification: ",e);
throw new RuntimeException("Caught during serialization for proxy object identification: "+e);
} finally {
// The lock is released on transaction commit.
// objectContainer.lock().release(transaction);
}
} else {
deferProcessingOfObjectContainerDueToLockContention(objectContainer);
}
}
/**
Defers the processing of the given objectContainer because locking was not possible at current time.
Because objectContainers may be modified in the meantime
(in the case they are unloaded and loaded again, we would hold an old copy of objectContainer which
does not reflect the changes made to the new objectContainer), it's not wise to queue the ObjectContainer.
Instead, we have to queue the ObjectID of that ObjectContainer.
*/
protected void deferProcessingOfObjectContainerDueToLockContention(ObjectContainer objectContainer) {
synchronized (surelyReachableObjectsWhichHaveToBeMarkedAsSuch) {
surelyReachableObjectsWhichShouldHaveBeenProcessedButWereLockContented.add(objectContainer.id());
}
}
/**
This method is called by Transactions to indicate that a method of the object callee
will be called by the transaction.
This method will always be called, even if the GarbageCollector is not startet.
*/
public void interceptInvocationPre(Transaction transaction,ObjectContainer callee,Object[] args) {
/*
This looks dangerous but is effective.
The method isRunning() is called without the lock on this GarbageCollector.
This is safe because this GarbageCollector only really starts to run after all transactions which
existed during the initiation have completed. All new transactions see the right value of
isRunning because there is a synchronization on creation of such a transaction.
The other possibility is that old transactions see this too early. But in this case, all
what can happen is that the callStack is empty and therefore the caller is null.
The speed gain is that method invocation may be not disturbed at all on HotSpot VMs, where
calls to this method may be fully optimized away as long as isRunning() is constant.
*/
if (isRunning()) {
SimpleArrayList callStack = transaction.getCallStack();
try {
if (args!=null) {
ObjectContainer caller = (ObjectContainer) callStack.peek();
if (caller!=null) {
/*
There is a design decision which heavily affects performance:
- Do we first check for the possibility of an OzoneProxy as parameter or
- Do we first check for the cross of the border of the different sets of database objects?
If we use true here, the first option is used.
If we use false here, the first option is used.
*/
if (false) {
/*
This spaghetti code is for speed.
- If we find an OzoneProxy, we have to look deeper and have to synchronize
- If we do not find an OzoneProxy, we do not have to look deeper and to synchronized
*/
checkForPossibleOzoneProxys:
for (;;) {
for (int i = args.length-1;i>=0;i--) {
if (args[i] instanceof OzoneProxy) {
break checkForPossibleOzoneProxys;
}
}
return;
}
}
boolean possibleProxyBorderCross = false;
synchronized (garbageCollectionLevelsLock) {
if (callee.getGarbageCollectionLevel()==doneReachableGarbageCollectionLevel) { // The callee belongs to the doneReachable set
if (caller.getGarbageCollectionLevel()!=doneReachableGarbageCollectionLevel) { // The caller does not belong to the doneReachable set
// The caller may transport object references to the doneReachable set where they are not checked. The referenced objects could falsely be identified as unreachable.
possibleProxyBorderCross = true;
}
}
}
if (possibleProxyBorderCross) {
for (int i = args.length-1;i>=0;i--) {
checkForProxyBorderCross(args[i]);
}
}
}
}
} finally {
callStack.push(callee);
}
}
}
/**
This method is called by Transactions to indicate that a method of the object callee
was called by the transaction.
This method will always be called, even if the GarbageCollector is not startet.
*/
public void interceptInvocationPost(Transaction transaction,ObjectContainer callee,Object result) {
/*
This looks dangerous but is effective.
The method isRunning() is called without the lock on this GarbageCollector.
This is safe because this GarbageCollector only really starts to run after all transactions which
existed during the initiation have completed. All new transactions see the right value of
isRunning because there is a synchronization on creation of such a transaction.
The other possibility is that old transactions see this too early. But in this case, all
what can happen is that the callStack is empty and therefore the caller is null.
The speed gain is that method invocation may be not disturbed at all on HotSpot VMs, where
calls to this method may be fully optimized away as long as isRunning() is constant.
*/
if (isRunning()) {
SimpleArrayList callStack = transaction.getCallStack();
if (result!=null) {
ObjectContainer caller = (ObjectContainer) callStack.peek();
if (caller!=null) {
if (result instanceof OzoneCompatibleOrProxy) {
// An indicator that this is a border cross proxy.
boolean possibleProxyBorderCross = false;
synchronized (garbageCollectionLevelsLock) {
if (caller.getGarbageCollectionLevel()==doneReachableGarbageCollectionLevel) { // The caller belongs to the doneReachable set
if (callee.getGarbageCollectionLevel()!=doneReachableGarbageCollectionLevel) { // The callee does not belong to the doneReachable set
// The caller may receive object references to the doneReachable set where they are not checked. The referenced objects could falsely be identified as unreachable.
possibleProxyBorderCross = true;
}
}
}
if (possibleProxyBorderCross) {
if (result instanceof OzoneCompatible) {
checkForProxyBorderCross((OzoneCompatible) result);
} else { // Must be OzoneProxy
checkForProxyBorderCross((OzoneProxy) result);
}
}
}
}
}
callStack.pop();
}
}
/**
This method checks wether the given object or the object graph reachable from this given object
contains OzoneProxys. If so, the database objects referenced by these proxies are considered
surelyReachable.
*/
protected void checkForProxyBorderCross(Object o) {
if (o instanceof OzoneProxy) {
checkForProxyBorderCross((OzoneProxy) o);
}
}
/**
This method checks wether the given object or the object graph reachable from this given object
contains OzoneProxys. If so, the database objects referenced by these proxies are considered
surelyReachable.
*/
protected void checkForProxyBorderCross(OzoneProxy o) {
ObjectID id = o.remoteID();
addObjectIDToSurelyReachableObjectsWhichHaveToBeMarkedAsSuch(id);
}
/**
This method checks wether the given object or the object graph reachable from this given object
contains OzoneProxys. If so, the database objects referenced by these proxies are considered
surelyReachable.
*/
protected void checkForProxyBorderCross(OzoneCompatible o) {
// We should not process this object immediately because it is likely that this object itself is currently called.
ensureSurelyReachable(o.container().id());
}
/**
ObjectOutputStream which servers as an intra ObjectContainer object-graph-walker to
detect all OzoneProxy instances an OzoneObject does refer.
Every detected OzoneProxy object is registered as to be marked as surely reachable.
*/
public class GarbageCollectorProxyObjectIdentificationObjectOutputStream extends ObjectOutputStream {
/*
Maybe one GarbageCollectorProxyObjectIdentificationObjectOutputStream per GarbageCollector
is sufficient, but there is state maintained in the ObjectOutputStream and
flushing state during time another thread is using the ObjectOutputStream as well
produces situationes which are unexpected by the developers of ObjectOutputStream.
So this is currently not considered.
*/
protected GarbageCollectorProxyObjectIdentificationObjectOutputStream() throws IOException {
super(NullOutputStream.getDefault());
}
public void notifyOzoneProxyEncountered(OzoneProxy encounteredProxy) {
addObjectIDToSurelyReachableObjectsWhichHaveToBeMarkedAsSuch(encounteredProxy.remoteID());
}
protected void identifyProxys(ObjectContainer objectContainer) throws IOException {
writeObject(objectContainer.target());
}
}
/**
Sweeps all objects which are considered unreachable.
*/
protected void sweepUnreachableObjects() {
env.getLogWriter().newEntry(this,"sweepUnreachableObjects(): starting to sweep...",env.getLogWriter().DEBUG3);
DxIterator i = env.getStoreManager().objectIDIterator();
while (i.next()!=null) {
if (kill) {
break;
}
ObjectID id = (ObjectID) i.key();
try {
notifyAboutTransactionActionAndRenewTransactionIfRequired();
ObjectContainer container = transaction.acquireObjectAndPin(id,Lock.LEVEL_READ);
try {
synchronized (garbageCollectionLevelsLock) {
// env.getLogWriter().newEntry(this,"sweepUnreachableObjects(): processing "+id+": container.getGarbageCollectionLevel()="+container.getGarbageCollectionLevel()+", currentGarbageCollectionLevel="+currentGarbageCollectionLevel+".",env.getLogWriter().DEBUG3);
if (container.getGarbageCollectionLevel()-currentGarbageCollectionLevel<0) {
env.getLogWriter().newEntry(this,"sweepUnreachableObjects(): deleting "+id,env.getLogWriter().DEBUG3);
transaction.deleteObject(id);
}
}
} finally {
transaction.releaseObjectAndUnpin(container);
}
} catch (ObjectNotFoundExc e) {
env.getLogWriter().newEntry(this,"caught while sweeping: ",e,env.getLogWriter().DEBUG3);
} catch (ClassNotFoundException e) {
env.getLogWriter().newEntry(this,"caught while sweeping: ",e,env.getLogWriter().DEBUG3);
} catch (IOException e) {
// It does not matter if the object is gone in the meantime. This is the best case for a garbage collector :-)
env.getLogWriter().newEntry(this,"caught while sweeping: ",e,env.getLogWriter().DEBUG3);
} catch (TransactionExc e) {
// FIXME. What do we in this case?
env.getLogWriter().newEntry(this,"caught while sweeping: ",e,env.getLogWriter().ERROR);
} catch (OzoneInternalExc e) {
env.getLogWriter().newEntry(this,"caught while sweeping: ",e,env.getLogWriter().ERROR);
} catch (OzoneRemoteExc e) {
env.getLogWriter().newEntry(this,"caught while sweeping: ",e,env.getLogWriter().ERROR);
}
}
try {
completeTransaction();
} catch (ClassNotFoundException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
} catch (IOException e) {
env.getLogWriter().newEntry(this,"caught: ",e,env.getLogWriter().ERROR);
}
}
}