package nachos.threads;
import nachos.machine.*;
import java.util.Random;
/**
* A Tester for the PriorityScheduler class
*/
public class PrioritySchedulerTest {
/**
* PingPongWorker class, which implements a thread runs in an infinite loop
* and randomly changes its own or its partner thread's priority.
*/
private static class PingPongWorker implements Runnable {
/** Constructor
*/
PingPongWorker(String name, char marker) {
this.name = name;
this.marker = marker;
this.partner = null;
this.amIDone = false;
}
/** setPartner(): set the partner thread for the
* thread running this PingPongWorker.
*/
public void setPartner(KThread partner) {
this.partner = partner;
}
/** terminate(): tell this PingPongWorker to terminate.
*/
public void terminate() {
this.amIDone = true;
}
/** run() method for the PingPongWorker thread.
*/
public void run() {
System.out.println("** "+name+" begins, priority="+ KThread.currentThread().getPriority());
/* Allocate a random number generator */
Random rng = new Random();
/* Loop until somebody calls terminate() */
while(amIDone == false) {
/* "Sleep" for some amount of time */
long wakeTime = Machine.timer().getTime() + 50;
while (wakeTime > Machine.timer().getTime()) {
KThread.yield();
}
/* Print some character to show some output */
System.out.print(this.marker);
/* Roll a dice */
int dice = rng.nextInt(100);
if ((dice % 20 == 0) || (dice % 20 == 1)) {
/* 2 times out of 20 do a priority change */
/* Pick a priority change target with 50% probability */
KThread target;
if (dice % 20 == 0)
target = KThread.currentThread();
else
target = partner;
/* Pick a new random priority, different from the target's current one */
int oldPriority = target.getPriority();
int newPriority = rng.nextInt(6);
while (newPriority == oldPriority) {
newPriority = rng.nextInt(6);
}
/* Update the target's priority */
System.out.println("");
System.out.println(target.getName()+"'s priority changed from "+
oldPriority+" to "+newPriority);
target.setPriority(newPriority);
}
}
System.out.println("** "+name+" exits.");
}
private String name; // The worker's name
private KThread partner; // The worker's partner thread
private char marker; // The character to be printed at each iteration
private boolean amIDone; // true if the worker must terminate
}
/* runPingPongTest(): runs the ping-pong test for the PriorityScheduler class
*/
private static void runPingPongTest() {
System.out.println("#### Ping-Pong test starts ####\n");
System.out.println(" The output will be sequences of '+' and '-' symbols, with\n"+
" notification of priority changes. Visual inspection of this\n"+
" output tells you whether the test is successful or not. When\n"+
" both threads have the same priority, they should print '+' and\n"+
" '-' in an interleaved manner. Otherwise, only the thread with\n"+
" the highest priority (i.e., smalled numerical priority value)\n"+
" should print its marker. The thread printing '+' is called 'Plus'\n"+
" and the thread printing '-' is called 'Minus'. Initially both\n"+
" both threads have the same priority.\n");
/* Create two ping-pong workers in threads with default priority */
PingPongWorker pingWorker = new PingPongWorker("Plus",'+');
PingPongWorker pongWorker = new PingPongWorker("Minus",'-');
KThread ping = new KThread(pingWorker);
ping.setName("Plus");
KThread pong = new KThread(pongWorker);
pong.setName("Minus");
/* Establish ping-pong partnership */
pingWorker.setPartner(pong);
pongWorker.setPartner(ping);
/* Start the two threads */
ping.fork();
pong.fork();
/* Wait for a moderate lapse of time */
ThreadedKernel.alarm.waitUntil(50000);
/* Terminate the threads */
pingWorker.terminate();
pongWorker.terminate();
/* Wait for their termination */
ping.join();
pong.join();
System.out.println("#### Ping-Pong test ends ####\n");
}
/**
* A NamedLock class, which allows locks to be identified
* by a name, which comes in handy for test output
*/
private static class NamedLock extends Lock {
/* Constructor
*/
public NamedLock(String name) {
super();
this.name = name;
}
/* getName()
*/
public String getName() {
return this.name;
}
/* setName()
*/
public void setName(String name) {
this.name = name;
}
private String name;
}
/**
* PriorityDonationWorker class, which implements a thread that runs in
* an infinite loop, with a given priority, that may go through the loop
* an arbitrary number of times or only once, that attempts to lock and
* unlock a set of (named) locks.
*/
private static class PriorityDonationWorker implements Runnable {
/* Constructor */
PriorityDonationWorker(String name, boolean once,
int priority, NamedLock locks[]) {
this.name = name;
this.once = once;
this.priority = priority;
this.locks = locks;
this.amIDone = false;
}
/** terminate()
*/
public void terminate() {
this.amIDone = true;
}
/** getName()
*/
public String getName() {
return this.name;
}
/** run() method
*/
public void run() {
System.out.println("** "+name+" begins");
/* Create a random number generator */
Random rng = new Random();
/* Setting the prescribed priority */
KThread.currentThread().setPriority(this.priority);
/* Loop until some other thread has called terminate() */
while(amIDone == false) {
/* Acquiring locks I am supposed to acquire */
for (int i=0; i < locks.length; i++) {
System.out.println(this.name+" trying to acquire "+
locks[i].getName());
System.out.println(this.name+" priority="+ this.priority);
locks[i].acquire();
System.out.println(this.name+" has acquired "+
locks[i].getName());
KThread.yield();
}
/* "Sleep" for a while */
long wakeTime = Machine.timer().getTime() + 20000;
while (wakeTime > Machine.timer().getTime()) {
KThread.yield();
}
/* Releasing the locks I have acquired, in reversed order */
for (int i=locks.length-1; i >= 0; i--) {
System.out.println(this.name+" about to release "+
locks[i].getName());
locks[i].release();
System.out.println(this.name+" has released "+
locks[i].getName());
KThread.yield();
}
/* Am I a thread that runs just once? If yes, then break */
if (once) {
break;
}
}
System.out.println("** "+name+" exits");
}
private String name; // Name of the worker
private boolean once; // true if worker should not loop
private int priority; // Prescribed priority
private NamedLock locks[]; // Array of locks to lock and unlock
private boolean amIDone; // true if the worker must terminate
}
/* runPriorityDonationTest1(): A simple priority donation test with one lock.
*/
private static void runPriorityDonationTest1() {
System.out.println("#### Priority Donation test #1 ####");
System.out.println(" This test succeeds if there is no deadlock. Note that due\n"+
" to randomness, the test may succeed many times and then fails.\n"+
" So you want to run it many, many times\n");
/* Create an array with only lock lock */
NamedLock[] locks = new NamedLock[1];
locks[0] = new NamedLock("lock0");
/* Create a Mid-priority thread that runs forever and doesn't
deal with any locks */
PriorityDonationWorker workerMi =
new PriorityDonationWorker("M-Priority",
false,6,new NamedLock[0]);
/* Create a Low-priority thread that runs forever and deals
with all locks */
PriorityDonationWorker workerLo =
new PriorityDonationWorker("L-Priority",
false,7,locks);
/* Create a Hi-priority thread that runs once and deals
with all locks */
PriorityDonationWorker workerHi =
new PriorityDonationWorker("H-Priority",
true,2,locks);
/* Create and name all threads */
KThread threadMi = new KThread(workerMi);
threadMi.setName(workerMi.getName());
KThread threadLo = new KThread(workerLo);
threadLo.setName(workerLo.getName());
KThread threadHi = new KThread(workerHi);
threadHi.setName(workerHi.getName());;
/* Fork the Low-priority thread */
threadLo.fork();
ThreadedKernel.alarm.waitUntil(500);
/* Fork the Mid-priority thread */
threadMi.fork();
ThreadedKernel.alarm.waitUntil(500);
/* Fork the Hi-priority thread */
threadHi.fork();
/* Waiting for the Hi-priority thread
* If priority Donation is not implemented (correctly),
this will deadlock as the Lo worker never gets to run */
threadHi.join();
/* Wait thread termination */
workerMi.terminate();
threadMi.join();
workerLo.terminate();
threadLo.join();
System.out.println("#### Priority Donation test #1 ends ####\n");
}
/* runPriorityDonationTest2(): A more complicated priority donation test with two locks.
*/
private static void runPriorityDonationTest2() {
System.out.println("#### Priority Donation test #2 ####");
System.out.println(" This test succeeds if there is no deadlock. Note that due\n"+
" to randomness, the test may succeed many times and then fails.\n"+
" So you want to run it many, many times\n");
/* Create an array of two named locks */
NamedLock allLocks[] = new NamedLock[2];
allLocks[0] = new NamedLock("lock1");
allLocks[1] = new NamedLock("lock2");
/* Create an array with only the first lock */
NamedLock firstLock[] = new NamedLock[1];
firstLock[0] = allLocks[0];
/* Create an array with only the second lock */
NamedLock secondLock[] = new NamedLock[1];
secondLock[0] = allLocks[1];
/* Create an array with no lock */
NamedLock noLocks[] = new NamedLock[0];
/* Create a Mid-priority worker, runs forever, priority 6, no locks */
PriorityDonationWorker workerMi =
new PriorityDonationWorker("Mid-Priority",
false,6,noLocks);
/* Create a Low-priority worker, runs forever, priority 7, all locks */
PriorityDonationWorker workerLo =
new PriorityDonationWorker("Low-Priority",
false,7,allLocks);
/* Create a Higher-priority worker, runs once, priority 2, first lock */
PriorityDonationWorker workerHigher =
new PriorityDonationWorker("Higher-Priority",
true,2,firstLock);
/* Create a Highest-priority worker, runs once, priority 1, second lock */
PriorityDonationWorker workerHighest =
new PriorityDonationWorker("Highest-Priority",
true,1,secondLock);
/* Create and name threads */
KThread threadMi = new KThread(workerMi);
threadMi.setName(workerMi.getName());
KThread threadLo = new KThread(workerLo);
threadLo.setName(workerLo.getName());
KThread threadHigher = new KThread(workerHigher);
threadHigher.setName(workerHigher.getName());
KThread threadHighest = new KThread(workerHighest);
threadHighest.setName(workerHighest.getName());
/* Fork the Low-priority thread */
threadLo.fork();
ThreadedKernel.alarm.waitUntil(500);
/* Fork the Mid-priority thread */
threadMi.fork();
ThreadedKernel.alarm.waitUntil(500);
/* Fork the Higher- and Highest-priority thread */
threadHigher.fork();
threadHighest.fork();
/* Wait for the Higher- and Highest-priority threads
* If priority Donation is not implemented (correctly),
this will deadlock as the Lo worker never gets to run */
threadHighest.join();
threadHigher.join();
/* Terminate Mid-priority thread */
workerMi.terminate();
threadMi.join();
/* Terminate Lo-priority thread */
workerLo.terminate();
threadLo.join();
System.out.println("#### Priority Donation test #2 ends ####\n");
}
/* getSomeLocks(): Given an input array of locks and selects a few at random,
* which are returned at an array.
*/
private static NamedLock[] getSomeLocks(NamedLock locks[]) {
NamedLock[] someLocks;
boolean[] selected;
int counter,numSelected;
Random rng = new Random();
/* Make a pass through the locks and mark some for selection */
selected = new boolean[locks.length];
numSelected = 0;
for (int i=0; i < locks.length; i++) {
selected[i] = (rng.nextInt(1000) % 2 == 0);
if (selected[i]) {
numSelected++;
}
}
/* Put the selected locks in an array */
someLocks = new NamedLock[numSelected];
counter = 0;
for (int i=0; i < locks.length; i++) {
if (selected[i]) {
someLocks[counter++] = locks[i];
}
}
return someLocks;
}
/* runPriorityDonationTest3(): A complex random test with many high-priority
* threads that all care about different locks.
*
*/
private static void runPriorityDonationTest3() {
int testSize = 10; // number of high-priority threads and of locks
System.out.println("#### Priority Donation test #3 ####");
System.out.println(" This test succeeds if there is no deadlock. Note that due\n"+
" to randomness, the test may succeed many times and then fails.\n"+
" So you want to run it many, many times\n");
/* Create a random number generator */
Random rng = new Random();
/* Create an array of locks */
NamedLock allLocks[] = new NamedLock[testSize];
for (int i=0; i < testSize; i++) {
allLocks[i] = new NamedLock("lock"+i);
}
/* Create a Mid-priority worker, runs forever, priority 6, no locks */
NamedLock noLocks[] = new NamedLock[0];
PriorityDonationWorker workerMi =
new PriorityDonationWorker("Mid-Priority",
false,6,noLocks);
/* Create a Mid-priority worker, runs forever, priority 7, all locks */
PriorityDonationWorker workerLo =
new PriorityDonationWorker("Low-Priority",
false,7,allLocks);
/* Creating a bunch of high priority workers, each dealing
* with a random subset of the locks */
PriorityDonationWorker workerHi[] =
new PriorityDonationWorker[testSize];
for (int i=0; i < testSize; i++) {
int priority = rng.nextInt(5);
workerHi[i] = new PriorityDonationWorker(
"High-Priority-"+i+"-"+priority,
true, priority,
getSomeLocks(allLocks));
}
/* Create and name all threads */
KThread threadMi = new KThread(workerMi);
threadMi.setName("Mid-Priority");
KThread threadLo = new KThread(workerLo);
threadLo.setName("Lo-Priority");
KThread[] threadHi = new KThread[testSize];
for (int i=0; i < testSize; i++) {
threadHi[i] = new KThread(workerHi[i]);
threadHi[i].setName(workerHi[i].getName());
}
/* Fork the Lo-priority thread */
threadLo.fork();
ThreadedKernel.alarm.waitUntil(50*testSize);
/* Fork the Mid-priority thread */
threadMi.fork();
ThreadedKernel.alarm.waitUntil(500);
/* Fork all Hi-priority thread */
for (int i=0; i < testSize; i++) {
threadHi[i].fork();
}
/* Wait for the Hi-Priority threads
* If priority Donation is not implemented (correctly),
this will deadlock as the Lo worker never gets to run */
for (int i=0; i < testSize; i++) {
threadHi[i].join();
}
/* Wait for the Mid- and Low-priority threads */
workerMi.terminate();
threadMi.join();
workerLo.terminate();
threadLo.join();
System.out.println("#### Priority Donation test #3 ends ####\n");
}
/**
* Tests whether this module is working.
*/
public static void runTest() {
System.out.println("######################################");
System.out.println("## PriorityScheduler testing begins ##");
System.out.println("######################################\n");
/* A simple ping-pong test */
//runPingPongTest();
/* Simplest priority donation test */
//runPriorityDonationTest1();
/* More sophisticated donation test */
runPriorityDonationTest2();
/* Complex donation test */
runPriorityDonationTest3();
System.out.println("####################################");
System.out.println("## PriorityScheduler testing ends ##");
System.out.println("####################################\n");
}
}