//
// Copyright (C) 2013 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.vm;
import java.util.ArrayList;
import java.util.List;
import cmu.conditional.Conditional;
import cmu.conditional.One;
import de.fosd.typechef.featureexpr.FeatureExpr;
import de.fosd.typechef.featureexpr.FeatureExprFactory;
/**
* @author Nastaran Shafiei <nastaran.shafiei@gmail.com>
*
* This class represents the finalizer thread in VM which is responsible for
* executing finalize() methods upon garbage collection of finalizable objects.
* By a finalizable object, we mean an object, the class of which overrides the
* Object.finalize() method. By default, we do not allow for processing finalizers,
* to enforce that one needs to set the property "vm.process_finalizers" to true.
*
* If "vm.process_finalizers" is set to true, during vm initialization we create
* a FinalizerThreadInfo object per process. ApplicationContext keeps a reference
* to FinalizerThreadInfo of the process. This thread is alive during the entire
* process execution. The finalizer thread is "always" waiting on an internal
* private lock. The JPF Thread object corresponding to the FinalizerThreadInfo
* is encapsulated by the model class gov.nasa.jpf.FinalizerThread. The thread
* encapsulated by FinalizerThread has a queue of objects called "finalizeQueue"
* which is kept at the SUT level. This queue is initially empty, and it is
* populated during the sweep() phase of the garbage collection. During sweep(),
* before removing unmark objects from the heap, any unmark finalizable object is
* marked and added to finalizeQueue.
*
* In VM.forward(), after each garbage collection, VM checks if finalizeQueue of
* the current process finalizer thread is not empty. If so, VM schedules the
* finalizer thread to execute next, i.e. finalizer thread stops waiting and its
* state becomes runnable. To accomplish that, VM replaces the next choiceGenerator
* with a new choice generator from which only finalizer thread can proceed.
*
* After finalizer thread processes the finalize() methods of all objects in
* finalizeQueue, the queue becomes empty and the thread waits on its internal lock
* again. After the process terminates, we still keep the finalizer thread to be
* processed after the last garbage collection involving the process. The runnable
* finalizer thread terminates itself when it has processed its finalizeQueue and
* there is no other alive thread in the process.
*/
public class FinalizerThreadInfo extends ThreadInfo {
static final String FINALIZER_NAME = "finalizer";
ChoiceGenerator<?> replacedCG;
protected FinalizerThreadInfo (VM vm, ApplicationContext appCtx, int id) {
super(vm, id, appCtx);
ci = appCtx.getSystemClassLoader().getResolvedClassInfo(null, "gov.nasa.jpf.FinalizerThread");
threadData.name = FINALIZER_NAME.toCharArray();
tempFinalizeQueue = new ArrayList<ElementInfo>();
}
protected void createFinalizerThreadObject (FeatureExpr ctx, SystemClassLoaderInfo sysCl){
Heap heap = getHeap();
ElementInfo eiThread = heap.newObject( ctx, ci, this);
objRef = eiThread.getObjectRef();
ElementInfo eiName = heap.newString(ctx, new One<>(FINALIZER_NAME), this);
int nameRef = eiName.getObjectRef();
eiThread.setReferenceField(ctx, "name", new One<>(nameRef));
// Since we create FinalizerThread upon VM initialization, they are assigned to the
// same group as the main thread
int grpRef = ThreadInfo.getCurrentThread().getThreadGroupRef();
eiThread.setReferenceField(ctx, "group", new One<>(grpRef));
eiThread.setIntField(ctx, "priority", One.valueOf(Thread.MAX_PRIORITY-2));
ClassInfo ciPermit = sysCl.getResolvedClassInfo(ctx, "java.lang.Thread$Permit");
ElementInfo eiPermit = heap.newObject( ctx, ciPermit, this);
eiPermit.setBooleanField(ctx, "blockPark", One.TRUE);
eiThread.setReferenceField(ctx, "permit", new One<>(eiPermit.getObjectRef()));
addToThreadGroup(ctx, getElementInfo(grpRef));
addId( objRef, id);
threadData.name = FINALIZER_NAME.toCharArray();
// start the thread by pushing Thread.run()
startFinalizerThread(ctx);
eiThread.setBooleanField(ctx, "done", One.FALSE);
ElementInfo finalizeQueue = getHeap().newArray(FeatureExprFactory.True(), "Ljava/lang/Object;", 0, this);
eiThread.setReferenceField(ctx, "finalizeQueue", new One<>(finalizeQueue.getObjectRef()));
// create an internal private lock used for lock-free wait
ElementInfo lock = getHeap().newObject(ctx, appCtx.getSystemClassLoader().objectClassInfo, this);
eiThread.setReferenceField(ctx, "semaphore", new One<>(lock.getObjectRef()));
// make it wait on the internal private lock until its finalizeQueue is populated. This way,
// we avoid scheduling points from including FinalizerThreads
waitOnSemaphore();
assert this.isWaiting();
}
/**
* Pushes a frame corresponding to Thread.run() into the finalizer thread stack to
* start the thread.
*/
protected void startFinalizerThread(FeatureExpr ctx) {
MethodInfo mi = ci.getMethod("run()V", false);
DirectCallStackFrame frame = mi.createDirectCallStackFrame(ctx, this, 0);
frame.setReferenceArgument(ctx, 0, objRef, frame);
pushFrame(frame);
}
public boolean hasQueuedFinalizers() {
ElementInfo queue = getFinalizeQueue();
return (queue!=null && queue.asReferenceArray().length>0);
}
public ElementInfo getFinalizeQueue() {
ElementInfo ei = vm.getModifiableElementInfo(objRef);
ElementInfo queue = null;
if(ei!=null) {
int queueRef = ei.getReferenceField("finalizeQueue").getValue();
queue = vm.getModifiableElementInfo(queueRef);
return queue;
}
return queue;
}
// all finalizable objects collected during garbage collection are temporarily added to this list
// when VM schedule the finalizer thread, it add all elements to FinalizerThread.finalizeQueue at
// the SUT level.
List<ElementInfo> tempFinalizeQueue;
/**
* This method is invoked by the sweep() phase of the garbage collection process (GenericHeap.sweep()).
* It adds a given finalizable object to the finalizeQueue array of gov.nasa.jpf.FinalizerThread.
*/
public void addToFinalizeQueue(ElementInfo ei) {
ei = ei.getModifiableInstance();
// make sure we process this object finalizer only once
ei.setFinalized();
tempFinalizeQueue.add(ei);
}
/**
* This method adds all finalizable objects observed during the last garbage collection
* to finalizeQueue of FinalizerThread corresponding to this thread
*/
void processNewFinalizables() {
if(!tempFinalizeQueue.isEmpty()) {
ElementInfo oldQueue = getFinalizeQueue();
Conditional<Integer>[] oldValues = oldQueue.asReferenceArray();
int len = oldValues.length;
int n = tempFinalizeQueue.size();
ElementInfo newQueue = getHeap().newArray(FeatureExprFactory.True(), "Ljava/lang/Object;", len+n, this);
Conditional<Integer>[] newValues = newQueue.asReferenceArray();
System.arraycopy(oldValues, 0, newValues, 0, len);
for(ElementInfo ei: tempFinalizeQueue) {
newValues[len++] = new One<>(ei.getObjectRef());
}
vm.getModifiableElementInfo(objRef).setReferenceField(FeatureExprFactory.True(), "finalizeQueue", new One<>(newQueue.getObjectRef()));
tempFinalizeQueue.clear();
assert hasQueuedFinalizers();
}
}
public boolean scheduleFinalizer() {
if(hasQueuedFinalizers() && !isRunnable()) {
replacedCG = vm.getNextChoiceGenerator();
vm.getSystemState().removeNextChoiceGenerator();
// NOTE - before we get here we have already made sure that nextCg is not Cascaded.
// we have to set nextCg to null before setting the nextCG, otherwise, the new CG is
// mistakenly considered as "Cascaded"
vm.getSystemState().nextCg = null;
ChoiceGenerator<ThreadInfo> cg = vm.getSystemState().getSchedulerFactory().createPreFinalizeCG(this);
vm.getSystemState().setMandatoryNextChoiceGenerator(cg, "Need to start FinalizerThread to process objects finalizers");
// stop waiting and process finalizers
notifyOnSemaphore();
assert this.isRunnable();
return true;
}
return false;
}
protected void waitOnSemaphore() {
int lockRef = vm.getElementInfo(objRef).getReferenceField("semaphore").getValue();
ElementInfo lock = vm.getModifiableElementInfo(lockRef);
lock.wait(this, 0, false);
}
protected void notifyOnSemaphore() {
int lockRef = vm.getElementInfo(objRef).getReferenceField("semaphore").getValue();
ElementInfo lock = vm.getModifiableElementInfo(lockRef);
lock.notifies(vm.getSystemState(), this, false);
}
@Override
public boolean isSystemThread() {
return true;
}
}