/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.mmtk.harness.scheduler.javathreads; import static org.mmtk.harness.scheduler.ThreadModel.State.BLOCKED; import static org.mmtk.harness.scheduler.ThreadModel.State.BLOCKING; import static org.mmtk.harness.scheduler.ThreadModel.State.MUTATOR; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.mmtk.harness.Mutator; import org.mmtk.harness.lang.Trace; import org.mmtk.harness.lang.Trace.Item; import org.mmtk.harness.scheduler.MMTkThread; import org.mmtk.harness.scheduler.Schedulable; import org.mmtk.harness.scheduler.ThreadModel; import org.mmtk.plan.CollectorContext; import org.mmtk.utility.Log; public final class JavaThreadModel extends ThreadModel { static { //Trace.enable(Item.SCHEDULER); } /** * Collector threads scheduled through #scheduleCollector(Schedulable) */ private final Set<CollectorThread> collectorThreads = Collections.newSetFromMap(new ConcurrentHashMap<CollectorThread, Boolean>()); /** * Mutator threads scheduled through scheduleMutator(Schedulable) */ private final Set<MutatorThread> mutatorThreads = Collections.newSetFromMap(new ConcurrentHashMap<MutatorThread, Boolean>()); @Override public void scheduleMutator(Schedulable code) { Trace.trace(Item.SCHEDULER, "Scheduling new mutator"); MutatorThread t = new MutatorThread(this,code); mutatorThreads.add(t); if (isRunning()) t.start(); } @Override protected void scheduleCollector(CollectorContext context) { Trace.trace(Item.SCHEDULER, "Scheduling new collector"); CollectorThread t = new CollectorThread(this,context); collectorThreads.add(t); context.initCollector(collectorThreads.size()); t.start(); } /** * Create a new non-daemon collector thread * * Used for scheduling unit tests in collector context */ @Override public Thread scheduleCollectorContext(CollectorContext context) { Trace.trace(Item.SCHEDULER, "Scheduling new collector"); CollectorThread t = new CollectorThread(this,context,false); collectorThreads.add(t); context.initCollector(collectorThreads.size()); return t; } void removeCollector(CollectorThread c) { collectorThreads.remove(c); Trace.trace(Item.SCHEDULER, "removeCollector: remaining collectors = %d",collectorThreads.size()); if (collectorThreads.size() == 0) { /* This only happens when running unit tests */ resumeAllMutators(); } } private MMTkThread currentMMTkThread() { return ((MMTkThread)Thread.currentThread()); } @Override public void yield() { if (isRunning()) { if (currentMMTkThread().yieldPolicy()) { Thread.yield(); } } } @Override public Log currentLog() { return currentMMTkThread().getLog(); } @Override public Mutator currentMutator() { assert Thread.currentThread() instanceof MutatorThread : "Current thread is not a Mutator"; return ((MutatorThread)Thread.currentThread()).env; } /** * Perform the delicate operation of joining the pool of active mutators. * <p> * If there isn't currently a GC in progress ({@code !allWaitingForGC()}), increment * the active mutator count and return. If a GC has been initiated, we will * join it at the next GC-safe point. * <p> * Otherwise, we 'quietly' join the active GC, by incrementing the count of * waiting mutators, and calling {@code waitForGC(false)} (there has already been a 'last' * mutator). Once the GC has completed, we remove ourselves from the GC, * and increment the activeMutators. */ void joinMutatorPool() { synchronized (count) { if (!allWaitingForGC()) { incActiveMutators(); return; } mutatorsBlocked++; incActiveMutators(); } waitForGC(false,false); synchronized (count) { mutatorsBlocked--; } } private void incActiveMutators() { activeMutators++; count.notify(); } private void decActiveMutators() { activeMutators--; if (activeMutators == 0) count.notify(); } /** * Perform the delicate operation of leaving the mutator pool. * <p> * If there's a GC scheduled, and we are the last thread to join, * join the GC (because we are required to trigger it) and then exit. * Otherwise, just decrement the mutator count and leave. */ void leaveMutatorPool(MutatorThread m) { Trace.trace(Item.SCHEDULER, "%d Leaving mutator pool", Thread.currentThread().getId()); synchronized (count) { boolean lastToGC = (mutatorsBlocked == (activeMutators - 1)); if (activeMutators == 1 || !lastToGC) { decActiveMutators(); Trace.trace(Item.SCHEDULER, "%d Leaving mutator pool: Removing self from thread pool", Thread.currentThread().getId()); mutatorThreads.remove(m); return; } mutatorsBlocked++; } Trace.trace(Item.SCHEDULER, "%d Leaving mutator pool: checking for GC", Thread.currentThread().getId()); waitForGC(true,false); synchronized (count) { mutatorsBlocked--; decActiveMutators(); } Trace.trace(Item.SCHEDULER, "%d Leaving mutator pool: Removing self from thread pool", Thread.currentThread().getId()); mutatorThreads.remove(m); Trace.trace(Item.SCHEDULER, "%d Leaving mutator pool: done", Thread.currentThread().getId()); } /** Synchronisation object used for GC triggering */ private final Object trigger = new Object(); /** * Wait for a GC to complete * @param last True if this thread is the last to join */ private void waitForGC(boolean last, boolean blockAnyway) { Trace.trace(Item.SCHEDULER, "%d waitForGC in, state=%s, mutatorsBlocked=%d", Thread.currentThread().getId(),getState(),mutatorsBlocked); synchronized (trigger) { /* Catch mutators who are waiting for a GC that hasn't necessarily started yet */ while (blockAnyway && isState(MUTATOR)) { Trace.trace(Item.SCHEDULER, "%d current state is MUTATOR", Thread.currentThread().getId()); try { trigger.wait(); } catch (InterruptedException ie) { } } /* Change state from BLOCKING to BLOCKED when the last thread arrives */ if (last && isState(BLOCKING)) { setState(BLOCKED); trigger.notifyAll(); } /* Wait to be resumed */ while (isState(BLOCKED) || isState(BLOCKING)) { try { trigger.wait(); } catch (InterruptedException ie) { } } } Trace.trace(Item.SCHEDULER, "%d waitForGC out", Thread.currentThread().getId()); } /** * Mutator thread calls this if it wants to block until the next GC is complete */ @Override public void waitForGC() { boolean allWaiting; synchronized (count) { mutatorsBlocked++; allWaiting = allWaitingForGC(); } waitForGC(allWaiting,true); synchronized (count) { mutatorsBlocked--; } } /** * Check whether all mutators are in GC - MUST HOLD THE 'count' MONITOR * @return true if all mutators are waiting for GC */ private boolean allWaitingForGC() { return (activeMutators > 0) && (mutatorsBlocked == activeMutators); } /** * Trigger a collection for the given reason */ @Override public void stopAllMutators() { Trace.trace(Item.SCHEDULER, "stopAllMutators"); synchronized (trigger) { setState(BLOCKING); trigger.notifyAll(); } waitForGCStart(); Trace.trace(Item.SCHEDULER, "stopAllMutators - done"); } /** * Resume all the blocked mutator threads */ @Override public void resumeAllMutators() { synchronized (trigger) { setState(MUTATOR); trigger.notifyAll(); } } protected void waitForGCStart() { synchronized (trigger) { while (!isState(BLOCKED)) { try { trigger.wait(); } catch (InterruptedException ie) { } } Trace.trace(Item.SCHEDULER, "GC has started - returning to caller"); } } /** Object used for synchronizing mutatorsWaitingForGC and activeMutators */ private static final Object count = new Object(); /** The number of mutators waiting for a collection to proceed. */ protected int mutatorsBlocked; /** The number of mutators currently executing in the system. */ protected int activeMutators; /** Thread access to current collector */ private static final ThreadLocal<CollectorContext> collectorThreadLocal = new ThreadLocal<CollectorContext>(); @Override public int mutatorRendezvous(String where, int expected) { synchronized (count) { mutatorsBlocked++; } int ordinal = Rendezvous.rendezvous(where,expected); synchronized (count) { mutatorsBlocked--; } return ordinal; } @Override public CollectorContext currentCollector() { return collectorThreadLocal.get(); } void setCurrentCollector(CollectorContext c) { collectorThreadLocal.set(c); } @Override public JavaLock newLock(String name) { return new org.mmtk.harness.scheduler.javathreads.JavaLock(name); } /** * Wait for the mutator threads to exit. */ @Override public void schedule() { startRunning(); /* Start the mutator threads */ for (Thread t : mutatorThreads) { t.start(); } /* Wait for the mutators to start */ while (mutatorThreads.size() > activeMutators) { synchronized (count) { try { count.wait(); } catch (InterruptedException e) { } Trace.trace(Item.SCHEDULER,"Active mutators = " + activeMutators + ", mutatorThreads = " + mutatorThreads.size()); } } /* Wait for the mutators to exit */ while (activeMutators > 0) { synchronized (count) { try { count.wait(); } catch (InterruptedException e) { } Trace.trace(Item.SCHEDULER,"Active mutators = " + activeMutators); } } } @Override public void scheduleGcThreads() { /* Start the mutator threads */ for (Thread t : collectorThreads) { t.start(); } synchronized (trigger) { startRunning(); setState(BLOCKED); trigger.notifyAll(); while (!isState(MUTATOR)) { try { trigger.wait(); } catch (InterruptedException e) { } } stopRunning(); } } @Override public boolean gcTriggered() { return !isState(MUTATOR); } @Override public boolean isMutator() { return Thread.currentThread() instanceof MutatorThread; } @Override public boolean isCollector() { return Thread.currentThread() instanceof CollectorThread; } @Override protected JavaMonitor newMonitor(String name) { return new JavaMonitor(name); } }