kilim.Fiber.java Source code

Java tutorial

Introduction

Here is the source code for kilim.Fiber.java

Source

/* 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.Field;

import me.jor.util.Log4jUtil;

import org.apache.commons.logging.Log;

/**
 * This class serves as a context to manage and store the continuation stack.
 * The actual capture of the closure is done in the Weaver-transformed code.
 */

public final class Fiber {
    private static final Log log = Log4jUtil.getLog(Fiber.class);
    //    public boolean debug = false;
    /**
     * The current frame's state (local vars  and elements of the operand stack 
     * that will be needed when the Fiber is resumed. It is always kept equal 
     * to stateStack[iStack] if iStack is in the (0..stateStack.length-1) range, 
     * and null otherwise. This is used by the generated code to avoid 
     * having to manipulate stateStack in the generated code, and to isolate
     * all stack manipulations to up() and down().
     */
    public State curState;

    /**
     * The "program counter", kept equal to stateStack[iStack].pc and is used to
     * jump to the appropriate place in the code while rewinding the code, and
     * also to inform the weaved code inside an exception handler which pausable
     * method (if at all) was being invoked when that exception was thrown. The
     * value 0 refers to normal operation; control transfers to the beginning of
     * the original (pre-weaved) code. A value of n indicates a direct jump into 
     * the nth pausable method (after restoring the appropriate state).
     * Accessed by generated code (hence public).
     */
    public int pc;

    /*
     * One State object for each activation frame in the call hierarchy.
     */
    private State[] stateStack = new State[10];

    /*
     * Index into stateStack and equal to depth of call hierarchy - 1
     */
    private int iStack = -1;

    boolean isPausing;

    boolean isDone;

    /*
     * The task to which this Fiber belongs
     */
    public Task task;

    /*
     * Special marker state used by pause
     */
    private static final State PAUSE_STATE = new State();

    /*
     * Status indicators returned by down()
     *
     * normal return, nothing to restore
     */
    public static final int NOT_PAUSING__NO_STATE = 0;

    /*
     * Normal return, have saved state to restore before resuming
     */
    public static final int NOT_PAUSING__HAS_STATE = 1;

    /*
     * Pausing, and need to save state before returning
     */
    public static final int PAUSING__NO_STATE = 2;

    /*
     * Pausing, and have saved state from an earlier invocation,
     * so nothing left to do.
     */
    public static final int PAUSING__HAS_STATE = 3;

    static {
        PAUSE_STATE.pc = 1;
    }

    public Fiber(Task t) {
        task = t;
    }

    public Task task() {
        return task;
    }

    public boolean isDone() {
        return isDone;
    }

    public static void pause() throws Pausable {
        throw new IllegalStateException("pause() called without weaving");
    }

    /*
     * The user calls pause(), but the weaver changes the
     * call to pause(Fiber), which alternates between
     * pausing and not pausing in successive invocations.
     * @param f
     */
    public static void pause(Fiber f) {
        f.togglePause();
    }

    /*
     * Indication from the caller that the invoked function has returned.
     * 
     * If it is a normal return, it needs to read the state into curState
     * and nullify it in stateStack. This accomplishes the equivalent of:
     *      move up
     *      get state()
     *      restore from state
     *      delete state()
        
     * If it is pausing, it merely needs to return a combined status
     * 
     * @return a combined status of PAUSING/NOT_PAUSING and HAS_STATE/NO_STATE.
     */

    public int up() {
        int d = iStack;
        iStack = --d;
        if (isPausing) {
            //            if (debug) System.out.println("\nup(pausing)" + this);;
            //            if (debug) ds();
            return (stateStack[d] == null) ? PAUSING__NO_STATE : PAUSING__HAS_STATE;
            // not setting curState because the generated code is only
            // interested in knowing whether we have state or not.
        } else {
            // move up to caller's level
            State[] stack = stateStack;
            State cs = curState = stack[d];
            if (cs == null) {
                pc = 0;
                //                if (debug) System.out.println("\nup(not pausing)" + this);;
                //                if (debug) ds();
                return NOT_PAUSING__NO_STATE;
            } else {
                stack[d] = null; // clean up
                pc = cs.pc;
                //                if (debug) System.out.println("\nup(not pausing)" + this);;
                //                if (debug) ds();
                return NOT_PAUSING__HAS_STATE;
            }
        }
    }

    public final Fiber begin() {
        return down();
    }

    /**
     * end() is the last up(). returns true if the fiber is not pausing.
     */
    public final boolean end() {
        assert iStack == 0 : "Reset: Expected iStack == 0, not " + iStack + "\n" + this;
        boolean isDone = !isPausing;

        if (isDone) {
            // clean up callee's state
            stateStack[0] = null;
        }
        // reset pausing for next round.
        isPausing = false;
        iStack = -1;
        //        if (debug) System.out.println("lastUp() " + this);
        //        if (debug) ds();
        return isDone;
    }

    /*
     * Called by the generated code to indicate that it is moving on to the next
     * pausable method in the hierarchy Adjust iStack, set curState and pc for
     * convenience
     * 
     * @return this Fiber.
     */
    public Fiber down() {
        int d = ++iStack;
        if (d >= stateStack.length) {
            //            System.out.println("size == " + d);
            ensureSize(d * 2);
            pc = 0;
            curState = null;
        } else {
            State s = stateStack[d];
            curState = s;
            pc = (s == null) ? 0 : s.pc;
        }
        //        if (debug) System.out.println("down:\n" + this);
        //        if (debug) ds();
        return this;
    }

    static void ds() {
        for (StackTraceElement ste : new Exception().getStackTrace()) {
            String cl = ste.getClassName();
            String meth = ste.getMethodName();
            if (cl.startsWith("kilim.Worker") || meth.equals("go") || meth.equals("ds"))
                continue;
            String line = ste.getLineNumber() < 0 ? "" : ":" + ste.getLineNumber();
            log.info('\t' + cl + '.' + ste.getMethodName() + '(' + ste.getFileName() + line + ')');
        }
    }

    /**
     * In the normal (non-exception) scheme of things, the iStack is incremented
     * by down() on the way down and decremented by a corresponding up() when returning 
     * or pausing. If, however, an exception is thrown, we lose track of where we
     * are in the hierarchy. We recalibrate iStack by creating a dummy exception
     * and comparing it to the stack depth of an exception taken earlier.
     * This is done in scheduler.getStackDepth();
     * A sample stack trace of the dummy exception looks as follows
     * <pre>
     *   at kilim.Fiber.upEx(Fiber.java:250)
     *   at kilim.test.ex.ExCatch.normalCatch(ExCatch.java)
     *   at kilim.test.ex.ExCatch.test(ExCatch.java)
     *   at kilim.test.ex.ExCatch.execute(ExCatch.java)
     *   at kilim.Task.runExecute(Task.java)
     *   at kilim.WorkerThread.run(WorkerThread.java:11)
     * </pre>
     * We have to figure out the stack depth (iStack) of the method
     * that caught the exception and called upEx ("normalCatch" here).
     * The call stack below runExecute may be owned by the scheduler, which
     * may permit more than one task to build up on the stack. For this reason,
     * we let the scheduler tell us the depth of upEx below the task's execute().
     * @return Fiber.pc (note: in contrast up() returns status)
     */
    public int upEx() {
        // compute new iStack. 
        int is = task.getStackDepth() - 2; // remove upEx and convert to 0-based index. 
        State cs = stateStack[is];

        for (int i = iStack; i >= is; i--) {
            stateStack[i] = null; // release state
        }

        iStack = is;
        curState = cs;
        return (cs == null) ? 0 : cs.pc;
    }

    /**
     * Called by the weaved code while rewinding the stack. If we are about to
     * call a virtual pausable method, we need an object reference on which to
     * call that method. The next state has that information in state.self
     */
    public Object getCallee() {
        assert stateStack[iStack] != PAUSE_STATE : "No callee: this state is the pause state";
        assert stateStack[iStack] != null : "Callee is null";
        return stateStack[iStack + 1].self;
    }

    private State[] ensureSize(int newsize) {
        //        System.out.println("ENSURE SIZE = " + newsize);
        State[] newStack = new State[newsize];
        System.arraycopy(stateStack, 0, newStack, 0, stateStack.length);
        stateStack = newStack;
        return newStack;
    }

    /**
     * Called by the generated code before pausing and unwinding its stack
     * frame.
     * 
     * @param state
     */
    public void setState(State state) {
        stateStack[iStack] = state;
        isPausing = true;
        //        System.out.println("setState[" + + iStack + "] = " + this);
    }

    public State getState() {
        return stateStack[iStack];
    }

    void togglePause() {
        // The client code would have called fiber.down()
        // before calling Task.pause. curStatus would be
        // upto date.

        if (curState == null) {
            setState(PAUSE_STATE);
        } else {
            assert curState == PAUSE_STATE : "togglePause: Expected PAUSE_STATE, instead got: iStack == " + iStack
                    + ", state = " + curState;
            stateStack[iStack] = null;
            isPausing = false;
        }
    }

    public String toString() {
        StringBuilder sb = new StringBuilder(40);
        sb.append("iStack = ").append(iStack).append(", pc = ").append(pc);
        if (isPausing) {
            sb.append(" pausing");
        }
        sb.append('\n');
        for (int i = 0; i < stateStack.length; i++) {
            State st = stateStack[i];
            if (st != null) {
                sb.append(st.getClass().getName()).append('[').append(i).append("]: ");
                stateToString(sb, stateStack[i]);
            }
        }
        return sb.toString();
    }

    public void wrongPC() {
        throw new IllegalStateException("Wrong pc: " + pc);
    }

    static private void stateToString(StringBuilder sb, State s) {
        if (s == PAUSE_STATE) {
            sb.append("PAUSE\n");
            return;
        }
        Field[] fs = s.getClass().getFields();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            sb.append(f.getName()).append(" = ");
            Object v;
            try {
                v = f.get(s);
            } catch (IllegalAccessException iae) {
                v = "?";
            }
            sb.append(' ').append(v).append(' ');
        }
        sb.append('\n');
    }

    void clearPausing() {
        isPausing = false;
    }
}