/*
* Copyright (c) 2008, Matthias Mann
* Copyright (C) 2014 Zhang,Yuexiang (xfeep)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package nginx.clojure;
import java.io.IOException;
import java.io.Serializable;
/**
* <p>A Coroutine is used to run a CoroutineProto.</p>
* <p>It also provides a function to suspend a running Coroutine.</p>
*
* <p>A Coroutine can be serialized if it's not running and all involved
* classes and data types are also {@link Serializable}.</p>
*
* @author Matthias Mann
*/
public class Coroutine implements Runnable, Serializable {
/**
* Default stack size for the data stack.
* @see #Coroutine(de.matthiasmann.continuations.CoroutineProto, int)
*/
public static final int DEFAULT_STACK_SIZE = System.getProperty("nginx.clojure.coroutine.defaultStackSize") == null ? 256
: Integer.parseInt(System.getProperty("nginx.clojure.coroutine.defaultStackSize"));
private static final long serialVersionUID = 2783452871536981L;
public enum State {
/** The Coroutine has not yet been executed */
NEW,
/** The Coroutine is currently executing */
RUNNING,
/** The Coroutine has suspended it's execution */
SUSPENDED,
/** The Coroutine has finished it's run method
* @see CoroutineProto#coExecute()
*/
FINISHED
};
public interface FinishAwaredRunnable extends Runnable {
void onFinished(Coroutine c);
}
private Runnable proto;
private final Stack stack;
private final SuspendableConstructorUtilStack cstack;
private State state;
private int resumeCounter = 0;
private Object locals;
private Object inheritableLocals;
/**
* Suspend the currently running Coroutine on the calling thread.
*
* @throws de.matthiasmann.continuations.SuspendExecution This exception is used for control transfer - don't catch it !
* @throws java.lang.IllegalStateException If not called from a Coroutine
*/
public static void yield() throws SuspendExecution, IllegalStateException {
throw new Error("Calling function not instrumented");
}
/**
* DON'T call this, this method is used by wave tool for generate waving configuration file
* @throws SuspendExecution
* @throws IllegalStateException
*/
public static void _yieldp() throws SuspendExecution, IllegalStateException {
}
/**
* Creates a new Coroutine from the given CoroutineProto. A CoroutineProto
* can be used in several Coroutines at the same time - but then the normal
* multi threading rules apply to the member state.
*
* @param proto the CoroutineProto for the Coroutine.
*/
public Coroutine(Runnable proto) {
this(proto, DEFAULT_STACK_SIZE);
}
/**
* Creates a new Coroutine from the given CoroutineProto. A CoroutineProto
* can be used in several Coroutines at the same time - but then the normal
* multi threading rules apply to the member state.
*
* @param proto the CoroutineProto for the Coroutine.
* @param stackSize the initial stack size for the data stack
* @throws NullPointerException when proto is null
* @throws IllegalArgumentException when stackSize is <= 0
*/
public Coroutine(Runnable proto, int stackSize) {
this.proto = proto;
this.stack = new Stack(this, stackSize);
this.cstack = new SuspendableConstructorUtilStack(stackSize/8);
this.state = State.NEW;
Thread thread = Thread.currentThread();
Object currentLocals = HackUtils.getThreadLocals(Thread.currentThread());
this.locals = HackUtils.cloneThreadLocalMap(currentLocals);
try {
HackUtils.setThreadLocals(thread, this.locals);
Stack.setStack(this.stack);
SuspendableConstructorUtilStack.setStack(this.cstack);
}finally {
HackUtils.setThreadLocals(thread, currentLocals);
}
Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread());
if (inheritableLocals != null) {
this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals);
}
if(proto == null) {
throw new NullPointerException("proto");
}
assert isInstrumented(proto) : "Not instrumented";
}
public void reset() {
this.state = State.NEW;
Thread thread = Thread.currentThread();
Object currentLocals = HackUtils.getThreadLocals(Thread.currentThread());
this.locals = HackUtils.cloneThreadLocalMap(currentLocals);
try {
HackUtils.setThreadLocals(thread, this.locals);
Stack.setStack(this.stack);
SuspendableConstructorUtilStack.setStack(this.cstack);
}finally {
HackUtils.setThreadLocals(thread, currentLocals);
}
Object inheritableLocals = HackUtils.getInheritableThreadLocals(Thread.currentThread());
if (inheritableLocals != null) {
this.inheritableLocals = HackUtils.createInheritedMap(inheritableLocals);
}
}
public void reset(Runnable proto) {
this.proto = proto;
reset();
}
/**
* Returns the active Coroutine on this thread or NULL if no coroutine is running.
* @return the active Coroutine on this thread or NULL if no coroutine is running.
*/
public static Coroutine getActiveCoroutine() {
Stack s = Stack.getStack();
if(s != null) {
return s.co;
}
return null;
}
/**
* Returns the CoroutineProto that is used for this Coroutine
* @return The CoroutineProto that is used for this Coroutine
*/
public Runnable getProto() {
return proto;
}
/**
* <p>Returns the current state of this Coroutine. May be called by the Coroutine
* itself but should not be called by another thread.</p>
*
* <p>The Coroutine starts in the state NEW then changes to RUNNING. From
* RUNNING it may change to FINISHED or SUSPENDED. SUSPENDED can only change
* to RUNNING by calling run() again.</p>
*
* @return The current state of this Coroutine
* @see #run()
*/
public State getState() {
return state;
}
/**
* Runs the Coroutine until it is finished or suspended. This method must only
* be called when the Coroutine is in the states NEW or SUSPENDED. It is not
* multi threading safe.
*
* @throws java.lang.IllegalStateException if the Coroutine is currently running or already finished.
*/
public void run() throws IllegalStateException {
resume();
}
/**
* Runs the Coroutine until it is finished or suspended. This method must only
* be called when the Coroutine is in the states NEW or SUSPENDED. It is not
* multi threading safe.
*
* @throws java.lang.IllegalStateException if the Coroutine is currently running or already finished.
*/
public void resume() {
if(state != State.NEW && state != State.SUSPENDED) {
throw new IllegalStateException("Not new or suspended");
}
resumeCounter++;
State result = State.FINISHED;
// Stack oldStack = Stack.getStack();
// SuspendableConstructorUtilStack oldCStack = SuspendableConstructorUtilStack.getStack();
Thread thread = Thread.currentThread();
Object oldLocals = HackUtils.getThreadLocals(thread);
Object oldInheritableLocals = HackUtils.getInheritableThreadLocals(thread);
try {
HackUtils.setThreadLocals(thread, this.locals);
HackUtils.setInheritablehreadLocals(thread, this.inheritableLocals);
state = State.RUNNING;
// Stack.setStack(stack);
// SuspendableConstructorUtilStack.setStack(cstack);
try {
proto.run();
} catch (SuspendExecution ex) {
assert ex == SuspendExecution.instance;
result = State.SUSPENDED;
//stack.dump();
stack.resumeStack();
}
} finally {
if (result == State.FINISHED) {
//for reduce memory leak probability
//cstack.release was called by waved code so skip it here
stack.release();
inheritableLocals = null;
locals = null;
resumeCounter = 0;
if (proto instanceof FinishAwaredRunnable) {
((FinishAwaredRunnable) proto).onFinished(this);;
}
}
HackUtils.setThreadLocals(thread, oldLocals);
HackUtils.setInheritablehreadLocals(thread, oldInheritableLocals);
// Stack.setStack(oldStack);
// SuspendableConstructorUtilStack.setStack(oldCStack);
state = result;
}
}
/**
* DON'T call this, this method is used by wave tool for generate waving configuration file
*/
public void _resumep(){
if ( ++resumeCounter > 1) {
return;
}
if(state != State.NEW && state != State.SUSPENDED) {
throw new IllegalStateException("Not new or suspended");
}
State result = State.FINISHED;
Stack oldStack = Stack.getStack();
try {
state = State.RUNNING;
Stack.setStack(stack);
try {
proto.run();
} catch (SuspendExecution ex) {
assert ex == SuspendExecution.instance;
result = State.SUSPENDED;
//stack.dump();
stack.resumeStack();
}
} finally {
Stack.setStack(oldStack);
state = result;
}
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
if(state == State.RUNNING) {
throw new IllegalStateException("trying to serialize a running coroutine");
}
out.defaultWriteObject();
}
public int getResumeCounter() {
return resumeCounter;
}
public Stack getStack() {
return stack;
}
public SuspendableConstructorUtilStack getCStack() {
return cstack;
}
@SuppressWarnings("unchecked")
private boolean isInstrumented(Runnable proto) {
try {
Class clz = Class.forName("nginx.clojure.wave.AlreadyInstrumented");
return proto.getClass().isAnnotationPresent(clz);
} catch (ClassNotFoundException ex) {
return true; // can't check
} catch (Throwable ex) {
return true; // it's just a check - make sure we don't fail if something goes wrong
}
}
}