/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; /** * A base class for tasks. A task is a lightweight thread (it contains its * own stack in the form of a fiber). A concrete subclass of Task must * provide a pausable execute method. * */ public abstract class Task implements EventSubscriber { public volatile Thread currentThread = null; static PauseReason yieldReason = new YieldReason(); /** * Task id, automatically generated */ public final int id; static final AtomicInteger idSource = new AtomicInteger(); /** * The stack manager in charge of rewinding and unwinding * the stack when Task.pause() is called. */ protected Fiber fiber; /** * The reason for pausing (duh) and performs the role of a await * condition in CCS. This object is responsible for resuming * the task. * @see kilim.PauseReason */ protected PauseReason pauseReason; /** * running = true when it is put on the schdulers run Q (by Task.resume()). * The Task.runExecute() method is called at some point; 'running' remains * true until the end of runExecute (where it is reset), at which point a * fresh decision is made whether the task needs to continue running. */ protected boolean running = false; protected boolean done = false; /** * The thread in which to resume this task. Ideally, we shouldn't have any * preferences, but using locks in pausable methods will require the task * to be pinned to a thread. * @see kilim.ReentrantLock */ volatile WorkerThread preferredResumeThread; /** * @see Task#preferredResumeThread */ int numActivePins; /** * @see #informOnExit(Mailbox) */ private LinkedList<Mailbox<ExitMsg>> exitMBs; /** * The object responsible for handing this task to a thread * when the task is runnable. */ protected Scheduler scheduler; public Object exitResult = "OK"; // TODO: move into a separate timer service or into the schduler. public final static Timer timer = new Timer(true); public Task() { id = idSource.incrementAndGet(); fiber = new Fiber(this); } public int id() { return id; } public synchronized Task setScheduler(Scheduler s) { // if (running) { // throw new AssertionError("Attempt to change task's scheduler while it is running"); // } scheduler = s; return this; } public synchronized Scheduler getScheduler() { return scheduler; } public void resumeOnScheduler(Scheduler s) throws Pausable { if (scheduler == s) return; scheduler = s; Task.yield(); } /** * Used to start the task; the task doesn't resume on its own. Custom * schedulers must be set (@see #setScheduler(Scheduler)) before * start() is called. * @return */ public Task start() { if (scheduler == null) { setScheduler(Scheduler.getDefaultScheduler()); } resume(); return this; } /** * The generated code calls Fiber.upEx, which in turn calls * this to find out out where the current method is w.r.t * the closest _runExecute method. * @return the number of stack frames above _runExecute(), not including * this method */ public int getStackDepth() { StackTraceElement[] stes; stes = new Exception().getStackTrace(); int len = stes.length; for (int i = 0; i < len; i++) { StackTraceElement ste = stes[i]; if (ste.getMethodName().equals("_runExecute")){ // discounting WorkerThread.run, Task._runExecute, and Scheduler.getStackDepth return i - 1; } } throw new AssertionError("Expected task to be run by WorkerThread"); } public void onEvent(EventPublisher ep, Event e) { resume(); } /** * Add itself to scheduler if it is neither already running nor done. * @return True if it scheduled itself. */ public boolean resume() { if (scheduler == null) return false; boolean doSchedule = false; // We don't check pauseReason while resuming (to verify whether // it is worth returning to a pause state. The code at the top of stack // will be doing that anyway. synchronized(this) { if (done || running) return false; running = doSchedule = true; } if (doSchedule) { scheduler.schedule(this); } return doSchedule; } public void informOnExit(Mailbox<ExitMsg> exit) { if (isDone()) { exit.putnb(new ExitMsg(this, exitResult)); return; } synchronized (this) { if (exitMBs == null) exitMBs = new LinkedList<Mailbox<ExitMsg>>(); exitMBs.add(exit); } } /** * This is a placeholder that doesn't do anything useful. * Weave replaces the call in the bytecode from * invokestateic Task.getCurrentTask * to * load fiber * getfield task */ public static Task getCurrentTask() throws Pausable {return null;} /** * Analogous to System.exit, except an Object can * be used as the exit value */ public static void exit(Object aExitValue) throws Pausable { } public static void exit(Object aExitValue, Fiber f) { assert f.pc == 0 : "f.pc != 0"; f.task.setPauseReason(new TaskDoneReason(aExitValue)); f.togglePause(); } /** * Exit the task with a throwable indicating an error condition. The value * is conveyed through the exit mailslot (see informOnExit). * All exceptions trapped by the task scheduler also set the error result. */ public static void errorExit(Throwable ex) throws Pausable { } public static void errorExit(Throwable ex, Fiber f) { assert f.pc == 0 : "fc.pc != 0"; f.task.setPauseReason(new TaskDoneReason(ex)); f.togglePause(); } public static void errNotWoven() { System.err.println("############################################################"); System.err.println("Task has either not been woven or the classpath is incorrect"); System.err.println("############################################################"); Thread.dumpStack(); System.exit(0); } public static void errNotWoven(Task t) { System.err.println("############################################################"); System.err.println("Task " + t.getClass() + " has either not been woven or the classpath is incorrect"); System.err.println("############################################################"); Thread.dumpStack(); System.exit(0); } static class ArgState extends kilim.State { Object mthd; Object obj; Object[] fargs; } /** * Invoke a pausable method via reflection. Equivalent to Method.invoke(). * * @param mthd: The method to be invoked. (Implementation note: the corresponding woven method is invoked instead). * @param target: The object on which the method is invoked. Can be null if the method is static. * @param args: Arguments to the method * @return * @throws Pausable * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ public static Object invoke(Method mthd, Object target, Object ... args) throws Pausable, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Fiber f = getCurrentTask().fiber; Object[] fargs; if (f.pc == 0) { mthd = getWovenMethod(mthd); // Normal invocation. if (args == null) { fargs = new Object[1]; } else { fargs = new Object[args.length + 1]; // for fiber System.arraycopy(args, 0, fargs, 0, args.length); } fargs[fargs.length-1] = f; } else { // Resuming from a previous yield ArgState as = (ArgState)f.getState(); mthd = (Method)as.mthd; target = as.obj; fargs = as.fargs; } f.down(); Object ret = mthd.invoke(target, fargs); switch (f.up()) { case Fiber.NOT_PAUSING__NO_STATE: case Fiber.NOT_PAUSING__HAS_STATE: return ret; case Fiber.PAUSING__NO_STATE : ArgState as = new ArgState(); as.obj = target; as.fargs = fargs; as.pc = 1; as.mthd = mthd; f.setState(as); return null; case Fiber.PAUSING__HAS_STATE: return null; } throw new IllegalAccessException("Internal Error"); } // Given a method corresp. to "f(int)", return the equivalent woven method for "f(int, kilim.Fiber)" private static Method getWovenMethod(Method m) { Class<?>[] ptypes = m.getParameterTypes(); if (!(ptypes.length > 0 && ptypes[ptypes.length-1].getName().equals("kilim.Fiber"))) { // The last param is not "Fiber", so m is not woven. // Get the woven method corresponding to m(..., Fiber) boolean found = false; LOOP: for (Method wm: m.getDeclaringClass().getDeclaredMethods()) { if (wm != m && wm.getName().equals(m.getName()) ) { // names match. Check if the wm has the exact parameter types as m, plus a fiber. Class<?>[] wptypes = wm.getParameterTypes(); if (wptypes.length != ptypes.length + 1 || !(wptypes[wptypes.length-1].getName().equals("kilim.Fiber"))) continue LOOP; for (int i = 0; i < ptypes.length; i++) { if (ptypes[i] != wptypes[i]) continue LOOP; } m = wm; found = true; break; } } if (!found) { throw new IllegalArgumentException("Found no pausable method corresponding to supplied method: " +m); } } return m; } /** * @param millis * to sleep. Like thread.sleep, except it doesn't throw an interrupt, and it * doesn't hog the java thread. */ public static void sleep(final long millis) throws Pausable { // create a temp mailbox, and wait on it. final Mailbox<Integer> sleepmb = new Mailbox<Integer>(1); // TODO: will need a better mechanism for monitoring later on. timer.schedule(new TimerTask() { public void run() { sleepmb.putnb(0); } }, millis); sleepmb.get(); // block until a message posted } /** * Yield cooperatively to the next task waiting to use the thread. */ public static void yield() throws Pausable {errNotWoven();} public static void yield(Fiber f) { if (f.pc == 0) { f.task.setPauseReason(yieldReason); } else { f.task.setPauseReason(null); } f.togglePause(); f.task.checkKill(); } /** * Ask the current task to pause with a reason object, that is * responsible for resuming the task when the reason (for pausing) * is not valid any more. * @param pauseReason the reason */ public static void pause(PauseReason pauseReason) throws Pausable {errNotWoven();} public static void pause(PauseReason pauseReason, Fiber f) { if (f.pc == 0) { f.task.setPauseReason(pauseReason); } else { f.task.setPauseReason(null); } f.togglePause(); f.task.checkKill(); } /* * This is the fiber counterpart to the execute() method * that allows us to detec when a subclass has not been woven. * * If the subclass has not been woven, it won't have an * execute method of the following form, and this method * will be called instead. */ public void execute() throws Pausable, Exception { errNotWoven(this); } public void execute(Fiber f) throws Exception { errNotWoven(this); } public String toString() { return "" + id + "(running=" + running + ",pr=" + pauseReason+")"; } public String dump() { synchronized(this) { return "" + id + "(running=" + running + ", pr=" + pauseReason + ")"; } } public void pinToThread() { numActivePins++; } public void unpinFromThread() { numActivePins--; } final protected void setPauseReason(PauseReason pr) { pauseReason = pr; } public final PauseReason getPauseReason() { return pauseReason; } public synchronized boolean isDone() { return done; } /** * Called by WorkerThread, it is the wrapper that performs pre and post * execute processing (in addition to calling the execute(fiber) method * of the task. */ public void _runExecute(WorkerThread thread) throws NotPausable { Fiber f = fiber; boolean isDone = false; try { currentThread = Thread.currentThread(); assert (preferredResumeThread == null || preferredResumeThread == thread) : "Resumed " + id + " in incorrect thread. "; // start execute. fiber is wound to the beginning. execute(f.begin()); // execute() done. Check fiber if it is pausing and reset it. isDone = f.end() || (pauseReason instanceof TaskDoneReason); assert (pauseReason == null && isDone) || (pauseReason != null && !isDone) : "pauseReason:" + pauseReason + ",isDone =" + isDone; } catch (Throwable th) { th.printStackTrace(); // Definitely done setPauseReason(new TaskDoneReason(th)); isDone = true; } if (isDone) { done = true; // inform on exit if (numActivePins > 0) { throw new AssertionError("Task ended but has active locks"); } if (exitMBs != null) { if (pauseReason instanceof TaskDoneReason) { exitResult = ((TaskDoneReason)pauseReason).exitObj; } ExitMsg msg = new ExitMsg(this, exitResult); for (Mailbox<ExitMsg> exitMB: exitMBs) { exitMB.putnb(msg); } } preferredResumeThread = null; } else { if (thread != null) { // it is null for generators if (numActivePins > 0) { preferredResumeThread = thread; } else { assert numActivePins == 0: "numActivePins == " + numActivePins; preferredResumeThread = null; } } PauseReason pr = this.pauseReason; synchronized (this) { running = false; currentThread = null; } // The task has been in "running" mode until now, and may have missed // notifications to the pauseReason object (that is, it would have // resisted calls to resume(). If the pauseReason is not valid any // more, we'll resume. if (!pr.isValid(this)) { // NOTE: At this point, another event could trigger resumption before the following resume() can kick in. Additionally, // it is possible that the task could process all pending events, so the following call to resume() may be spurious. // Cell/Mailbox's get/put watch out for spurious resumptions. resume(); } } } public ExitMsg joinb() { Mailbox<ExitMsg> mb = new Mailbox<ExitMsg>(); informOnExit(mb); return mb.getb(); } public ExitMsg join() throws Pausable { Mailbox<ExitMsg> mb = new Mailbox<ExitMsg>(); informOnExit(mb); return mb.get(); } @Override public boolean equals(Object obj) { return obj == this; } @Override public int hashCode() { return id; } public void checkKill() { } }