/* * 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.rawthreads; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.mmtk.harness.Collector; 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 static org.mmtk.harness.scheduler.ThreadModel.State.*; import org.mmtk.utility.Log; /** * The deterministic thread scheduler. Java threads are used for all * program threads of execution, but only one thread is permitted * to execute at a time, the scheduler selecting the next thread at * each context switch. */ public final class RawThreadModel extends ThreadModel { private static boolean unitTest = false; /** * Enable unit-test mode */ public static void setUnitTest() { unitTest = true; } static { //Trace.enable(Item.SCHEDULER); } /** The global scheduler thread */ private final Thread scheduler = Thread.currentThread(); private static void trace(String message) { Trace.trace(Item.SCHEDULER, "%d: "+message, Thread.currentThread().getId()); } /** * The 'scheduler woken' flag. * * Unchecked invariant: at most one of schedulerIsAwake and (RawThread)isCurrent * are true at any given time. */ private boolean schedulerIsAwake = true; private RawThread current = null; final List<RawThread> collectors = new ArrayList<RawThread>(); private final List<RawThread> mutators = new ArrayList<RawThread>(); /** Unique mutator thread number - used to name thne mutator threads */ private volatile int mutatorId = 0; int nextMutatorId() { return ++mutatorId; } /** * Scheduling queues. A thread may be on exactly one of these queues, * or on a lock wait queue, or current. */ private final List<RawThread> runQueue = new LinkedList<RawThread>(); private final List<RawThread> rendezvousQueue = new LinkedList<RawThread>(); private final List<RawThread> gcWaitQueue = new LinkedList<RawThread>(); private final List<RawThread> collectorWaitQueue = new LinkedList<RawThread>(); /** * Remove a mutator from the pool when a mutator thread exits * @param m The mutator thread */ void removeMutator(MutatorThread m) { synchronized(scheduler) { mutators.remove(m); Trace.trace(Item.SCHEDULER, "%d: mutator removed, %d mutators remaining",m.getId(),mutators.size()); wakeScheduler(); } } /** * Remove a collector from the pool when a collector thread exits. * Only used for unit testing, since collectors are usually permanent * @param c The collector thread */ void removeCollector(CollectorThread c) { synchronized(scheduler) { collectors.remove(c); wakeScheduler(); } } void setCurrent(RawThread current) { this.current = current; } static MMTkThread getCurrent() { return (MMTkThread)Thread.currentThread(); } /** * @see org.mmtk.harness.scheduler.ThreadModel#currentLog() */ @Override public Log currentLog() { return current.getLog(); } /** * @see org.mmtk.harness.scheduler.ThreadModel#currentMutator() */ @Override public Mutator currentMutator() { return ((MutatorThread)current).env; } /** * @see org.mmtk.harness.scheduler.ThreadModel#exitGC() */ @Override public void exitGC() { Trace.trace(Item.SCHEDULER, "%d: exiting GC", current.getId()); setState(END_GC); } /** * @see org.mmtk.harness.scheduler.ThreadModel#currentCollector() */ @Override public Collector currentCollector() { return ((CollectorThread)current).c; } private int currentRendezvous = -1; /** * The number of mutators waiting for a collection to proceed. */ protected int mutatorsWaitingForGC; /** * The number of mutators currently executing in the system. */ protected int activeMutators; /** * @see org.mmtk.harness.scheduler.ThreadModel#rendezvous(int) */ @Override public int rendezvous(int where) { Trace.trace(Item.SCHEDULER, "%d: rendezvous(%d)", current.getId(), where); if (currentRendezvous == -1) { currentRendezvous = where; } else { assert currentRendezvous == where; } current.setOrdinal(rendezvousQueue.size()+1); if (rendezvousQueue.size() == collectors.size()-1) { makeRunnable(rendezvousQueue); currentRendezvous = -1; } else { yield(rendezvousQueue); } Trace.trace(Item.SCHEDULER, "%d: rendezvous(%d) complete: ordinal = %d", current.getId(), where,current.getOrdinal()); return current.getOrdinal(); } private final Map<String,List<RawThread>> rendezvousQueues = new HashMap<String,List<RawThread>>(); /** * @see org.mmtk.harness.scheduler.ThreadModel#mutatorRendezvous(java.lang.String, int) */ @Override public int mutatorRendezvous(String where, int expected) { String barrierName = "Barrier-"+where; Trace.trace(Item.SCHEDULER, "%s: rendezvous(%s)", current.getId(), barrierName); List<RawThread> queue = rendezvousQueues.get(barrierName); if (queue == null) { queue = new ArrayList<RawThread>(expected); rendezvousQueues.put(barrierName, queue); } current.setOrdinal(queue.size()+1); if (queue.size() == expected-1) { makeRunnable(queue,false); rendezvousQueues.put(barrierName, null); } else { yield(queue); } Trace.trace(Item.SCHEDULER, "%d: rendezvous(%s) complete: ordinal = %d", current.getId(), barrierName,current.getOrdinal()); return current.getOrdinal(); } /** * Create a collector thread */ @Override public void scheduleCollector() { RawThread c = new CollectorThread(this); Trace.trace(Item.SCHEDULER, "%d: creating new collector, id=%d", Thread.currentThread().getId(), c.getId()); c.start(); } /** * Create a collector thread for specific code (eg unit test) */ @Override public Thread scheduleCollector(Schedulable method) { RawThread c = new CollectorContextThread(this,method); Trace.trace(Item.SCHEDULER, "%d: creating new collector, id=%d", Thread.currentThread().getId(), c.getId()); c.start(); return c; } /** * Create a mutator thread */ @Override public void scheduleMutator(Schedulable method) { MutatorThread m = new MutatorThread(method, RawThreadModel.this); synchronized(scheduler) { Trace.trace(Item.SCHEDULER, "%d: creating new mutator, id=%d", Thread.currentThread().getId(), m.getId()); m.setName("Mutator-"+mutators.size()); mutators.add(m); if (!isState(MUTATOR)) { Trace.trace(Item.SCHEDULER, "%d: Adding to GC wait queue", Thread.currentThread().getId()); gcWaitQueue.add(m); } else { Trace.trace(Item.SCHEDULER, "%d: Adding to run queue", Thread.currentThread().getId()); runQueue.add(m); assert runQueue.size() <= Math.max(mutators.size(),collectors.size()); } m.start(); Trace.trace(Item.SCHEDULER, "%d: mutator started", Thread.currentThread().getId()); } } @Override protected void initCollectors() { Trace.trace(Item.SCHEDULER, "%d: Initializing collectors", Thread.currentThread().getId()); setState(BEGIN_GC); assert Thread.currentThread() == scheduler; makeRunnable(collectors,false); schedule(); assert collectorWaitQueue.size() == collectors.size(); setState(MUTATOR); Trace.trace(Item.SCHEDULER, "%d: Collector threads initialized", Thread.currentThread().getId()); } @Override public void triggerGC(int why) { Trace.trace(Item.SCHEDULER, "%d: Triggering GC", Thread.currentThread().getId()); synchronized(scheduler) { triggerReason = why; setState(BEGIN_GC); } } /** * A GC has been requested. A Mutator thread calls this when it sees the request, * and wishes to wait for the GC. In the raw-threads case, we yield to the * collectorWaitQueue. * * @see org.mmtk.harness.scheduler.ThreadModel#waitForGCStart() */ @Override public void waitForGCStart() { trace("Yielding to collector wait queue"); yield(collectorWaitQueue); } /** * Mutator waits for a GC */ @Override public void waitForGC() { Trace.trace(Item.SCHEDULER, "%d: Yielding to GC wait queue", Thread.currentThread().getId()); yield(gcWaitQueue); } /** @see org.mmtk.harness.scheduler.ThreadModel#yield() */ @Override public void yield() { if (isRunning()) { if (current.yieldPolicy()) { Trace.trace(Item.YIELD, "%d: Yieldpoint", Thread.currentThread().getId()); yield(runQueue); } else { Trace.trace(Item.YIELD, "%d: Yieldpoint - not taken", Thread.currentThread().getId()); } } } /** * Yield, placing the current thread on a specific queue * @param queue */ void yield(List<RawThread> queue) { assert current != null; queue.add(current); Trace.trace(Item.SCHEDULER,"%d: Yielded onto queue with %d members",Thread.currentThread().getId(),queue.size()); assert queue.size() <= Math.max(mutators.size(),collectors.size()) : "yielded to queue size "+queue.size()+" where there are "+mutators.size()+" m and "+collectors.size()+"c"; current.yieldThread(); } /** * Thread-model specific lock factory */ @Override public org.mmtk.harness.scheduler.Lock newLock(String name) { return new RawLock(this,name); } /** * Schedule gc-context unit tests */ @Override public void scheduleGcThreads() { /* Advance the GC threads to the collector wait queue */ initCollectors(); /* Make them all runnable, as though we had entered a GC */ makeRunnable(collectorWaitQueue); /* Transition to GC state */ setState(GC); /* And run to completion */ schedule(); } /** * The actual scheduler */ @Override public void schedule() { startRunning(); assert Thread.currentThread() == scheduler; Trace.trace(Item.SCHEDULER, "%d: scheduler begin", scheduler.getId()); /** * The scheduler runs until there are no threads to schedule. */ while (!runQueue.isEmpty()) { synchronized(scheduler) { assert runQueue.size() <= Math.max(mutators.size(),collectors.size()) : "Run queue is unreasonably long, queue="+runQueue.size()+ ", m="+mutators.size()+", c="+collectors.size(); if (runQueue.size() > 0) { runQueue.remove(0).resumeThread(); Trace.trace(Item.SCHEDULER, "%d: scheduler sleeping, runqueue=%d", scheduler.getId(), runQueue.size()); schedWait(); Trace.trace(Item.SCHEDULER, "%d: scheduler resuming, state %s, runqueue=%d", scheduler.getId(),getState(), runQueue.size()); } switch (getState()) { case MUTATOR: /* If there are available mutators, at least one of them must be runnable */ assert mutators.isEmpty() || !runQueue.isEmpty() : "mutators.isEmpty()="+mutators.isEmpty()+", runQueue.isEmpty()="+runQueue.isEmpty(); break; case BEGIN_GC: if (runQueue.isEmpty()) { setState(GC); Trace.trace(Item.SCHEDULER, "%d: Changing to state GC - scheduling %d GC threads", scheduler.getId(),collectorWaitQueue.size()); makeRunnable(collectorWaitQueue); } break; case END_GC: if (runQueue.isEmpty()) { assert collectorWaitQueue.size() == collectors.size(); setState(MUTATOR); Trace.trace(Item.SCHEDULER, "%d: Changing to state MUTATOR - scheduling %d mutator threads", scheduler.getId(),gcWaitQueue.size()); makeRunnable(gcWaitQueue); } break; case GC: assert ! unitTest && (mutators.size() == 0 || !runQueue.isEmpty()) : "Runqueue cannot be empty while GC is in progress"; break; } } } Trace.trace(Item.SCHEDULER, "%d: scheduler end", scheduler.getId()); stopRunning(); } void makeRunnable(List<RawThread> threads, boolean clear) { assert runQueue.size() <= Math.max(mutators.size(),collectors.size()); runQueue.addAll(threads); if (clear) { threads.clear(); } } void makeRunnable(List<RawThread> threads) { makeRunnable(threads,true); } void wakeScheduler() { Trace.trace(Item.SCHEDULER, "%d: waking scheduler", Thread.currentThread().getId()); synchronized(scheduler) { schedulerIsAwake = true; scheduler.notify(); } } /** * Wait for a scheduler wake-up. The caller *must* hold the scheduler monitor. */ private void schedWait() { schedulerIsAwake = false; assert Thread.currentThread() == scheduler; while (!schedulerIsAwake) { try { scheduler.wait(1); } catch (InterruptedException e) { } } } @Override public boolean noThreadsInGC() { return isState(MUTATOR); } @Override public boolean gcTriggered() { return isState(BEGIN_GC); } }