Improve detection and handling of orphaned coroutine threads.
This commit is contained in:
@@ -197,7 +197,7 @@ public class LuaClosure extends LuaFunction {
|
||||
DebugLib.debugSetupCall(varargs, stack);
|
||||
|
||||
// process instructions
|
||||
LuaThread.onCall( this );
|
||||
LuaThread.CallStack cs = LuaThread.onCall( this );
|
||||
try {
|
||||
while ( true ) {
|
||||
if (DebugLib.DEBUG_ENABLED)
|
||||
@@ -500,11 +500,10 @@ public class LuaClosure extends LuaFunction {
|
||||
}
|
||||
} catch ( LuaError le ) {
|
||||
throw le;
|
||||
} catch ( Throwable t ) {
|
||||
LuaError le = new LuaError(t);
|
||||
throw le;
|
||||
} catch ( Exception e ) {
|
||||
throw new LuaError(e);
|
||||
} finally {
|
||||
LuaThread.onReturn();
|
||||
cs.onReturn();
|
||||
if ( openups != null )
|
||||
for ( int u=openups.length; --u>=0; )
|
||||
if ( openups[u] != null )
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2007-2011 LuaJ. All rights reserved.
|
||||
* Copyright (c) 2007-2012 LuaJ. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -62,31 +62,32 @@ import org.luaj.vm2.lib.DebugLib;
|
||||
public class LuaThread extends LuaValue {
|
||||
|
||||
public static LuaValue s_metatable;
|
||||
|
||||
public static int coroutine_count = 0;
|
||||
|
||||
private static final int STATUS_SUSPENDED = 0;
|
||||
private static final int STATUS_RUNNING = 1;
|
||||
private static final int STATUS_NORMAL = 2;
|
||||
private static final int STATUS_DEAD = 3;
|
||||
private static final int STATUS_ERROR = 4;
|
||||
static long thread_orphan_check_interval = 30000;
|
||||
|
||||
private static final int STATUS_INITIAL = 0;
|
||||
private static final int STATUS_SUSPENDED = 1;
|
||||
private static final int STATUS_RUNNING = 2;
|
||||
private static final int STATUS_NORMAL = 3;
|
||||
private static final int STATUS_DEAD = 4;
|
||||
private static final String[] STATUS_NAMES = {
|
||||
"suspended",
|
||||
"suspended",
|
||||
"running",
|
||||
"normal",
|
||||
"dead",
|
||||
"error" };
|
||||
"dead",};
|
||||
|
||||
private int status = STATUS_SUSPENDED;
|
||||
|
||||
private JavaThread thread;
|
||||
private LuaValue env;
|
||||
private LuaValue func;
|
||||
private final State state;
|
||||
|
||||
/** Field to hold state of error condition during debug hook function calls. */
|
||||
public LuaValue err;
|
||||
|
||||
final CallStack callstack = new CallStack();
|
||||
|
||||
public static final int MAX_CALLSTACK = 256;
|
||||
public final LuaFunction[] callstack = new LuaFunction[MAX_CALLSTACK];
|
||||
public int calls = 0;
|
||||
|
||||
private static final LuaThread main_thread = new LuaThread();
|
||||
|
||||
@@ -101,7 +102,8 @@ public class LuaThread extends LuaValue {
|
||||
|
||||
/** Private constructor for main thread only */
|
||||
private LuaThread() {
|
||||
status = STATUS_RUNNING;
|
||||
state = new State(this, null);
|
||||
state.status = STATUS_RUNNING;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,10 +112,11 @@ public class LuaThread extends LuaValue {
|
||||
* @param env The environment to apply to the thread
|
||||
*/
|
||||
public LuaThread(LuaValue func, LuaValue env) {
|
||||
LuaValue.assert_(func != null, "function cannot be null");
|
||||
this.env = env;
|
||||
this.func = func;
|
||||
state = new State(this, func);
|
||||
}
|
||||
|
||||
|
||||
public int type() {
|
||||
return LuaValue.TTHREAD;
|
||||
}
|
||||
@@ -147,7 +150,7 @@ public class LuaThread extends LuaValue {
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return STATUS_NAMES[status];
|
||||
return STATUS_NAMES[state.status];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,33 +188,15 @@ public class LuaThread extends LuaValue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used at the beginning of a call
|
||||
* Callback used at the beginning of a call to prepare for possible getfenv/setfenv calls
|
||||
* @param function Function being called
|
||||
* @return CallStack which is used to signal the return or a tail-call recursion
|
||||
* @see DebugLib
|
||||
*/
|
||||
public static final void onCall(LuaFunction function) {
|
||||
running_thread.callstack[running_thread.calls++] = function;
|
||||
if (DebugLib.DEBUG_ENABLED)
|
||||
DebugLib.debugOnCall(running_thread, running_thread.calls, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used at the end of a call
|
||||
* @see DebugLib
|
||||
*/
|
||||
public static final void onReturn() {
|
||||
running_thread.callstack[--running_thread.calls] = null;
|
||||
if (DebugLib.DEBUG_ENABLED)
|
||||
DebugLib.debugOnReturn(running_thread, running_thread.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of calls in stack
|
||||
* @return number of calls in current call stack
|
||||
* @see DebugLib
|
||||
*/
|
||||
public static int getCallstackDepth() {
|
||||
return running_thread.calls;
|
||||
public static final CallStack onCall(LuaFunction function) {
|
||||
CallStack cs = running_thread.callstack;
|
||||
cs.onCall(function);
|
||||
return cs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,21 +205,30 @@ public class LuaThread extends LuaValue {
|
||||
* @return LuaFunction on the call stack, or null if outside of range of active stack
|
||||
*/
|
||||
public static final LuaFunction getCallstackFunction(int level) {
|
||||
return level>0 && level<=running_thread.calls?
|
||||
running_thread.callstack[running_thread.calls-level]:
|
||||
null;
|
||||
return running_thread.callstack.getFunction(level);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace the error function of the currently running thread.
|
||||
* @param errfunc the new error function to use.
|
||||
* @return the previous error function.
|
||||
*/
|
||||
public static LuaValue setErrorFunc(LuaValue errfunc) {
|
||||
LuaValue prev = running_thread.err;
|
||||
running_thread.err = errfunc;
|
||||
return prev;
|
||||
}
|
||||
|
||||
/** Yield the current thread with arguments
|
||||
*
|
||||
* @param args The arguments to send as return values to {@link #resume(Varargs)}
|
||||
* @return {@link Varargs} provided as arguments to {@link #resume(Varargs)}
|
||||
*/
|
||||
public static Varargs yield(Varargs args) {
|
||||
JavaThread t = running_thread.thread;
|
||||
if ( t == null )
|
||||
error("cannot yield main thread");
|
||||
return t.yield(args);
|
||||
State s = running_thread.state;
|
||||
if (s.function == null)
|
||||
throw new LuaError("cannot yield main thread");
|
||||
return s.lua_yield(args);
|
||||
}
|
||||
|
||||
/** Start or resume this thread
|
||||
@@ -243,124 +237,129 @@ public class LuaThread extends LuaValue {
|
||||
* @return {@link Varargs} provided as arguments to {@link #yield(Varargs)}
|
||||
*/
|
||||
public Varargs resume(Varargs args) {
|
||||
if ( status != STATUS_SUSPENDED )
|
||||
return varargsOf(FALSE, valueOf("cannot resume "+STATUS_NAMES[status]+" coroutine"));
|
||||
if ( thread == null )
|
||||
thread = new JavaThread(this,func);
|
||||
return thread.resume(this,args);
|
||||
if (this.state.status > STATUS_SUSPENDED)
|
||||
return LuaValue.varargsOf(LuaValue.FALSE,
|
||||
LuaValue.valueOf("cannot resume "+LuaThread.STATUS_NAMES[this.state.status]+" coroutine"));
|
||||
return state.lua_resume(this, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper class which contains the java stack used by this coroutine,
|
||||
* and which detects when the LuaThread has been collected and completes.
|
||||
*/
|
||||
private static final class JavaThread extends Thread {
|
||||
private final WeakReference ref;
|
||||
private final LuaValue func;
|
||||
private Varargs args;
|
||||
private boolean started;
|
||||
private static int count;
|
||||
private JavaThread(LuaThread lua_thread,LuaValue func) {
|
||||
this.ref = new WeakReference(lua_thread);
|
||||
this.func = func;
|
||||
this.setDaemon(true);
|
||||
this.setName("LuaThread-"+(++count));
|
||||
}
|
||||
public void run() {
|
||||
synchronized ( this ) {
|
||||
try {
|
||||
this.args = func.invoke(this.args);
|
||||
setStatus( STATUS_DEAD );
|
||||
} catch ( Throwable t ) {
|
||||
String msg = t.getMessage();
|
||||
this.args = valueOf(msg!=null? msg: t.toString());
|
||||
setStatus( STATUS_ERROR );
|
||||
} finally {
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
static class State implements Runnable {
|
||||
final WeakReference lua_thread;
|
||||
final LuaValue function;
|
||||
Varargs args = LuaValue.NONE;
|
||||
Varargs result = LuaValue.NONE;
|
||||
String error = null;
|
||||
int status = LuaThread.STATUS_INITIAL;
|
||||
|
||||
State(LuaThread lua_thread, LuaValue function) {
|
||||
this.lua_thread = new WeakReference(lua_thread);
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
private Varargs yield(Varargs args) {
|
||||
synchronized ( this ) {
|
||||
if ( getStatus() != STATUS_RUNNING )
|
||||
error(this+" not running");
|
||||
setStatus( STATUS_SUSPENDED );
|
||||
this.args = args;
|
||||
public synchronized void run() {
|
||||
try {
|
||||
Varargs a = this.args;
|
||||
this.args = LuaValue.NONE;
|
||||
this.result = function.invoke(a);
|
||||
} catch (Throwable t) {
|
||||
this.error = t.getMessage();
|
||||
} finally {
|
||||
this.status = LuaThread.STATUS_DEAD;
|
||||
this.notify();
|
||||
try {
|
||||
while ( getStatus() == STATUS_SUSPENDED )
|
||||
this.wait(GC_INTERVAL);
|
||||
if ( null == this.ref.get() )
|
||||
stop();
|
||||
setStatus( STATUS_RUNNING );
|
||||
return this.args;
|
||||
} catch ( InterruptedException e ) {
|
||||
setStatus( STATUS_DEAD );
|
||||
error( "thread interrupted" );
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatus(int status) {
|
||||
LuaThread lt = (LuaThread) ref.get();
|
||||
if ( lt != null )
|
||||
lt.status = status;
|
||||
}
|
||||
|
||||
private int getStatus() {
|
||||
LuaThread lt = (LuaThread) ref.get();
|
||||
return lt != null? lt.status: STATUS_DEAD;
|
||||
}
|
||||
|
||||
private Varargs resume(LuaThread lua_thread, Varargs args) {
|
||||
|
||||
synchronized ( this ) {
|
||||
|
||||
// set prior thread to normal status while we are running
|
||||
LuaThread prior = running_thread;
|
||||
try {
|
||||
// set our status to running
|
||||
prior.status = STATUS_NORMAL;
|
||||
running_thread = lua_thread;
|
||||
running_thread.status = STATUS_RUNNING;
|
||||
|
||||
// copy args in
|
||||
this.args = args;
|
||||
|
||||
// start thread if not started alread
|
||||
if ( ! this.started ) {
|
||||
this.started = true;
|
||||
this.start();
|
||||
}
|
||||
|
||||
// wait for thread to yield or finish
|
||||
synchronized Varargs lua_resume(LuaThread new_thread, Varargs args) {
|
||||
LuaThread previous_thread = LuaThread.running_thread;
|
||||
try {
|
||||
LuaThread.running_thread = new_thread;
|
||||
this.args = args;
|
||||
if (this.status == STATUS_INITIAL) {
|
||||
this.status = STATUS_RUNNING;
|
||||
new Thread(this, "Coroutine-"+(++coroutine_count)).start();
|
||||
} else {
|
||||
this.notify();
|
||||
this.wait();
|
||||
|
||||
// copy return values from yielding stack state
|
||||
if ( lua_thread.status == STATUS_ERROR ) {
|
||||
lua_thread.status = STATUS_DEAD;
|
||||
return varargsOf(FALSE, this.args);
|
||||
} else {
|
||||
return varargsOf(TRUE, this.args);
|
||||
}
|
||||
|
||||
} catch ( Throwable t ) {
|
||||
lua_thread.status = STATUS_DEAD;
|
||||
try {
|
||||
return varargsOf(FALSE, valueOf("thread: "+t));
|
||||
} finally {
|
||||
this.notify();
|
||||
}
|
||||
|
||||
} finally {
|
||||
// previous thread is now running again
|
||||
running_thread = prior;
|
||||
running_thread.status = STATUS_RUNNING;
|
||||
}
|
||||
previous_thread.state.status = STATUS_NORMAL;
|
||||
this.status = STATUS_RUNNING;
|
||||
this.wait();
|
||||
return (this.error != null?
|
||||
LuaValue.varargsOf(LuaValue.FALSE, LuaValue.valueOf(this.error)):
|
||||
LuaValue.varargsOf(LuaValue.TRUE, this.result));
|
||||
} catch (InterruptedException ie) {
|
||||
throw new OrphanedThread();
|
||||
} finally {
|
||||
running_thread = previous_thread;
|
||||
running_thread.state.status =STATUS_RUNNING;
|
||||
this.args = LuaValue.NONE;
|
||||
this.result = LuaValue.NONE;
|
||||
this.error = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized Varargs lua_yield(Varargs args) {
|
||||
try {
|
||||
this.result = args;
|
||||
this.status = STATUS_SUSPENDED;
|
||||
this.notify();
|
||||
do {
|
||||
this.wait(thread_orphan_check_interval);
|
||||
if (this.lua_thread.get() == null) {
|
||||
this.status = STATUS_DEAD;
|
||||
throw new OrphanedThread();
|
||||
}
|
||||
} while (this.status == STATUS_SUSPENDED);
|
||||
return this.args;
|
||||
} catch (InterruptedException ie) {
|
||||
this.status = STATUS_DEAD;
|
||||
throw new OrphanedThread();
|
||||
} finally {
|
||||
this.args = LuaValue.NONE;
|
||||
this.result = LuaValue.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class CallStack {
|
||||
final LuaFunction[] functions = new LuaFunction[MAX_CALLSTACK];
|
||||
int calls = 0;
|
||||
|
||||
/**
|
||||
* Method to indicate the start of a call
|
||||
* @see DebugLib
|
||||
*/
|
||||
final void onCall(LuaFunction function) {
|
||||
functions[calls++] = function;
|
||||
if (DebugLib.DEBUG_ENABLED)
|
||||
DebugLib.debugOnCall(running_thread, calls, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to signal the end of a call
|
||||
* @see DebugLib
|
||||
*/
|
||||
public final void onReturn() {
|
||||
if (DebugLib.DEBUG_ENABLED)
|
||||
DebugLib.debugOnReturn(running_thread, calls);
|
||||
functions[--calls] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of calls in stack
|
||||
* @return number of calls in current call stack
|
||||
* @see DebugLib
|
||||
*/
|
||||
public final int getCallstackDepth() {
|
||||
return calls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the function at a particular level of the stack.
|
||||
* @param level # of levels back from the top of the stack.
|
||||
* @return LuaFunction, or null if beyond the stack limits.
|
||||
*/
|
||||
LuaFunction getFunction(int level) {
|
||||
return level>0 && level<=calls? functions[calls-level]: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
src/core/org/luaj/vm2/OrphanedThread.java
Normal file
43
src/core/org/luaj/vm2/OrphanedThread.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012 Luaj.org. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
package org.luaj.vm2;
|
||||
|
||||
|
||||
/**
|
||||
* Error sublcass that indicates a lua thread that is no longer referenced has been detected.
|
||||
*
|
||||
* The java thread in which this is thrown should correspond to a LuaThread being used as a
|
||||
* coroutine that could not possibly be resumed again because there are no more references
|
||||
* to the LuaThread with which it is associated. Rather than locking up resources forever,
|
||||
* this error is thrown, and should fall through all the way to the thread's run() method.
|
||||
*
|
||||
* Java code mixed with the luaj vm should not catch this error because it may occur when
|
||||
* the coroutine is not running, so any processing done during error handling could break
|
||||
* the thread-safety of the application because other lua processing could be going on in
|
||||
* a different thread.
|
||||
*/
|
||||
public class OrphanedThread extends Error {
|
||||
|
||||
public OrphanedThread() {
|
||||
super("orphaned thread");
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,13 @@
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/package org.luaj.vm2.lib;
|
||||
******************************************************************************/
|
||||
package org.luaj.vm2.lib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import org.luaj.vm2.LoadState;
|
||||
import org.luaj.vm2.Lua;
|
||||
@@ -249,20 +251,20 @@ public class BaseLib extends OneArgFunction implements ResourceFinder {
|
||||
case 7: // "pcall", // (f, arg1, ...) -> status, result1, ...
|
||||
{
|
||||
LuaValue func = args.checkvalue(1);
|
||||
LuaThread.onCall(this);
|
||||
LuaThread.CallStack cs = LuaThread.onCall(this);
|
||||
try {
|
||||
return pcall(func,args.subargs(2),null);
|
||||
} finally {
|
||||
LuaThread.onReturn();
|
||||
cs.onReturn();
|
||||
}
|
||||
}
|
||||
case 8: // "xpcall", // (f, err) -> result1, ...
|
||||
{
|
||||
LuaThread.onCall(this);
|
||||
LuaThread.CallStack cs = LuaThread.onCall(this);
|
||||
try {
|
||||
return pcall(args.arg1(),NONE,args.checkvalue(2));
|
||||
} finally {
|
||||
LuaThread.onReturn();
|
||||
cs.onReturn();
|
||||
}
|
||||
}
|
||||
case 9: // "print", // (...) -> void
|
||||
@@ -358,13 +360,18 @@ public class BaseLib extends OneArgFunction implements ResourceFinder {
|
||||
|
||||
public static Varargs pcall(LuaValue func, Varargs args, LuaValue errfunc) {
|
||||
try {
|
||||
LuaThread thread = LuaThread.getRunning();
|
||||
LuaValue olderr = thread.err;
|
||||
try {
|
||||
thread.err = errfunc;
|
||||
if (errfunc == null) {
|
||||
return varargsOf(LuaValue.TRUE, func.invoke(args));
|
||||
} finally {
|
||||
thread.err = olderr;
|
||||
} else {
|
||||
LuaValue preverr = LuaThread.setErrorFunc(errfunc);
|
||||
WeakReference ref = new WeakReference(LuaThread.getRunning());
|
||||
try {
|
||||
return varargsOf(LuaValue.TRUE, func.invoke(args));
|
||||
} finally {
|
||||
LuaThread lt = (LuaThread) ref.get();
|
||||
if (lt != null)
|
||||
lt.err = preverr;
|
||||
}
|
||||
}
|
||||
} catch ( LuaError le ) {
|
||||
String m = le.getMessage();
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
******************************************************************************/
|
||||
package org.luaj.vm2.lib;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import org.luaj.vm2.Lua;
|
||||
import org.luaj.vm2.LuaBoolean;
|
||||
import org.luaj.vm2.LuaClosure;
|
||||
@@ -244,7 +246,7 @@ public class DebugLib extends VarArgFunction {
|
||||
|
||||
/** DebugState is associated with a Thread */
|
||||
static class DebugState {
|
||||
private final LuaThread thread;
|
||||
private final WeakReference thread_ref;
|
||||
private int debugCalls = 0;
|
||||
private DebugInfo[] debugInfo = new DebugInfo[LuaThread.MAX_CALLSTACK+1];
|
||||
private LuaValue hookfunc;
|
||||
@@ -252,7 +254,7 @@ public class DebugLib extends VarArgFunction {
|
||||
private int hookcount,hookcodes;
|
||||
private int line;
|
||||
DebugState(LuaThread thread) {
|
||||
this.thread = thread;
|
||||
this.thread_ref = new WeakReference(thread);
|
||||
}
|
||||
public DebugInfo nextInfo() {
|
||||
DebugInfo di = debugInfo[debugCalls];
|
||||
@@ -318,7 +320,8 @@ public class DebugLib extends VarArgFunction {
|
||||
return new DebugInfo(func);
|
||||
}
|
||||
public String tojstring() {
|
||||
return DebugLib.traceback(thread, 0);
|
||||
LuaThread thread = (LuaThread) thread_ref.get();
|
||||
return thread != null? DebugLib.traceback(thread, 0): "orphaned thread";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,11 +79,11 @@ abstract public class VarArgFunction extends LibFunction {
|
||||
* @param args the arguments to the function call.
|
||||
*/
|
||||
public Varargs invoke(Varargs args) {
|
||||
LuaThread.onCall(this);
|
||||
LuaThread.CallStack cs = LuaThread.onCall(this);
|
||||
try {
|
||||
return this.onInvoke(args).eval();
|
||||
} finally {
|
||||
LuaThread.onReturn();
|
||||
cs.onReturn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public class AllTests {
|
||||
vm.addTestSuite(MetatableTest.class);
|
||||
vm.addTestSuite(LuaOperationsTest.class);
|
||||
vm.addTestSuite(StringTest.class);
|
||||
vm.addTestSuite(LuaThreadTest.class);
|
||||
vm.addTestSuite(OrphanedThreadTest.class);
|
||||
suite.addTest(vm);
|
||||
|
||||
// table tests
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2011 Luaj.org. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
package org.luaj.vm2;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
|
||||
public class LuaThreadTest extends TestCase {
|
||||
|
||||
public void testMainThread() {
|
||||
assertEquals( true, LuaThread.isMainThread( LuaThread.getRunning()) );
|
||||
assertEquals( "running", LuaThread.getRunning().getStatus() );
|
||||
assertEquals( LuaValue.FALSE, LuaThread.getRunning().resume(LuaValue.NONE).arg1() );
|
||||
try {
|
||||
LuaThread.yield(LuaThread.yield(LuaValue.NONE));
|
||||
fail("did not throw lua error as expected");
|
||||
} catch ( LuaError le ) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testLuaThreadIsCollected() throws InterruptedException { System.out.println("testLuaThread - starting");
|
||||
int originalInterval = LuaThread.GC_INTERVAL;
|
||||
try {
|
||||
LuaThread.GC_INTERVAL = 75;
|
||||
TestRig rig = new TestRig();
|
||||
assertEquals( "resumed 1 times, arg=test-arg", rig.resumeOnce() );
|
||||
assertEquals( true, rig.isThreadReferenced() );
|
||||
assertEquals( true, rig.isFunctionReferenced() );
|
||||
assertEquals( true, rig.isArgReferenced() );
|
||||
collectGarbage();
|
||||
assertEquals( "resumed 2 times, arg=test-arg", rig.resumeOnce() );
|
||||
assertEquals( true, rig.isThreadReferenced() );
|
||||
assertEquals( true, rig.isFunctionReferenced() );
|
||||
assertEquals( true, rig.isArgReferenced() );
|
||||
Thread.sleep( 200 );
|
||||
collectGarbage();
|
||||
assertEquals( "resumed 3 times, arg=test-arg", rig.resumeOnce() );
|
||||
assertEquals( true, rig.isThreadReferenced() );
|
||||
assertEquals( true, rig.isFunctionReferenced() );
|
||||
assertEquals( true, rig.isArgReferenced() );
|
||||
|
||||
// check that references are collected
|
||||
// some time after lua thread is de-referenced
|
||||
rig.weakenReference();
|
||||
Thread.sleep( 200 );
|
||||
collectGarbage();
|
||||
assertEquals( false, rig.isThreadReferenced() );
|
||||
assertEquals( false, rig.isFunctionReferenced() );
|
||||
assertEquals( false, rig.isArgReferenced() );
|
||||
} finally {
|
||||
LuaThread.GC_INTERVAL = originalInterval;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static class TestRig {
|
||||
LuaThread luaThread;
|
||||
final WeakReference luaRef;
|
||||
final WeakReference funcRef;
|
||||
final WeakReference argRef;
|
||||
TestRig() {
|
||||
LuaValue a = new LuaUserdata( "test-arg" );
|
||||
LuaValue f = new TestFunction();
|
||||
luaThread = new LuaThread( f, new LuaTable() );
|
||||
luaRef = new WeakReference( luaThread );
|
||||
funcRef = new WeakReference( f );
|
||||
argRef = new WeakReference( a );
|
||||
}
|
||||
public String resumeOnce() {
|
||||
LuaThread t = (LuaThread) luaRef.get();
|
||||
LuaValue a = (LuaValue) argRef.get();
|
||||
return t==null? "no ref to lua thread":
|
||||
a==null? "no ref to arg":
|
||||
t.resume(a).arg(2).toString();
|
||||
}
|
||||
public void weakenReference() {
|
||||
luaThread = null;
|
||||
}
|
||||
public Object isThreadReferenced() {
|
||||
return null != luaRef.get();
|
||||
}
|
||||
public Object isFunctionReferenced() {
|
||||
return null != funcRef.get();
|
||||
}
|
||||
public Object isArgReferenced() {
|
||||
return null != argRef.get();
|
||||
}
|
||||
}
|
||||
|
||||
static class TestFunction extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
for ( int count=1; true; count++ ) {
|
||||
LuaValue r = LuaValue.valueOf("resumed "+count+" times, arg="+arg);
|
||||
Varargs v = LuaThread.yield( r );
|
||||
arg = v.arg1();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void collectGarbage() {
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
rt.gc();
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
rt.gc();
|
||||
Thread.sleep(20);
|
||||
} catch ( Exception e ) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
rt.gc();
|
||||
}
|
||||
}
|
||||
167
test/junit/org/luaj/vm2/OrphanedThreadTest.java
Normal file
167
test/junit/org/luaj/vm2/OrphanedThreadTest.java
Normal file
@@ -0,0 +1,167 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012 Luaj.org. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
package org.luaj.vm2;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.luaj.vm2.LoadState;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.Varargs;
|
||||
import org.luaj.vm2.compiler.LuaC;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.luaj.vm2.lib.jse.JsePlatform;
|
||||
|
||||
|
||||
public class OrphanedThreadTest extends TestCase {
|
||||
|
||||
LuaThread luathread;
|
||||
WeakReference luathr_ref;
|
||||
LuaValue function;
|
||||
WeakReference func_ref;
|
||||
LuaValue env;
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
LuaThread.thread_orphan_check_interval = 5;
|
||||
env = JsePlatform.standardGlobals();
|
||||
}
|
||||
|
||||
protected void tearDown() {
|
||||
LuaThread.thread_orphan_check_interval = 30000;
|
||||
}
|
||||
|
||||
public void testCollectOrphanedNormalThread() throws Exception {
|
||||
function = new NormalFunction();
|
||||
doTest(LuaValue.TRUE, LuaValue.ZERO);
|
||||
}
|
||||
|
||||
public void testCollectOrphanedEarlyCompletionThread() throws Exception {
|
||||
function = new EarlyCompletionFunction();
|
||||
doTest(LuaValue.TRUE, LuaValue.ZERO);
|
||||
}
|
||||
|
||||
public void testCollectOrphanedAbnormalThread() throws Exception {
|
||||
function = new AbnormalFunction();
|
||||
doTest(LuaValue.FALSE, LuaValue.valueOf("abnormal condition"));
|
||||
}
|
||||
|
||||
public void testCollectOrphanedClosureThread() throws Exception {
|
||||
String script =
|
||||
"print('in closure, arg is '..(...))\n" +
|
||||
"arg = coroutine.yield(1)\n" +
|
||||
"print('in closure.2, arg is '..arg)\n" +
|
||||
"arg = coroutine.yield(0)\n" +
|
||||
"print('leakage in closure.3, arg is '..arg)\n" +
|
||||
"return 'done'\n";
|
||||
LuaC.install();
|
||||
function = LoadState.load(new ByteArrayInputStream(script.getBytes()), "script", env);
|
||||
doTest(LuaValue.TRUE, LuaValue.ZERO);
|
||||
}
|
||||
|
||||
public void testCollectOrphanedPcallClosureThread() throws Exception {
|
||||
String script =
|
||||
"f = function(x)\n" +
|
||||
" print('in pcall-closure, arg is '..(x))\n" +
|
||||
" arg = coroutine.yield(1)\n" +
|
||||
" print('in pcall-closure.2, arg is '..arg)\n" +
|
||||
" arg = coroutine.yield(0)\n" +
|
||||
" print('leakage in pcall-closure.3, arg is '..arg)\n" +
|
||||
" return 'done'\n" +
|
||||
"end\n" +
|
||||
"print( 'pcall-closre.result:', pcall( f, ... ) )\n";
|
||||
LuaC.install();
|
||||
function = LoadState.load(new ByteArrayInputStream(script.getBytes()), "script", env);
|
||||
doTest(LuaValue.TRUE, LuaValue.ZERO);
|
||||
}
|
||||
|
||||
private void doTest(LuaValue status2, LuaValue value2) throws Exception {
|
||||
luathread = new LuaThread(function, env);
|
||||
luathr_ref = new WeakReference(luathread);
|
||||
func_ref = new WeakReference(function);
|
||||
assertNotNull(luathr_ref.get());
|
||||
|
||||
// resume two times
|
||||
Varargs a = luathread.resume(LuaValue.valueOf("foo"));
|
||||
assertEquals(LuaValue.TRUE, a.arg1());
|
||||
assertEquals(LuaValue.ONE, a.arg(2));
|
||||
a = luathread.resume(LuaValue.valueOf("bar"));
|
||||
assertEquals(status2, a.arg1());
|
||||
assertEquals(value2, a.arg(2));
|
||||
|
||||
// drop strong references
|
||||
luathread = null;
|
||||
function = null;
|
||||
|
||||
// gc
|
||||
for (int i=0; i<100 && (luathr_ref.get() != null || func_ref.get() != null); i++) {
|
||||
Runtime.getRuntime().gc();
|
||||
Thread.sleep(5);
|
||||
}
|
||||
|
||||
// check reference
|
||||
assertNull(luathr_ref.get());
|
||||
assertNull(func_ref.get());
|
||||
}
|
||||
|
||||
|
||||
static class NormalFunction extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
System.out.println("in normal.1, arg is "+arg);
|
||||
arg = LuaThread.yield(ONE).arg1();
|
||||
System.out.println("in normal.2, arg is "+arg);
|
||||
arg = LuaThread.yield(ZERO).arg1();
|
||||
System.out.println("in normal.3, arg is "+arg);
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static class EarlyCompletionFunction extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
System.out.println("in early.1, arg is "+arg);
|
||||
arg = LuaThread.yield(ONE).arg1();
|
||||
System.out.println("in early.2, arg is "+arg);
|
||||
return ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
static class AbnormalFunction extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
System.out.println("in abnormal.1, arg is "+arg);
|
||||
arg = LuaThread.yield(ONE).arg1();
|
||||
System.out.println("in abnormal.2, arg is "+arg);
|
||||
error("abnormal condition");
|
||||
return ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
static class ClosureFunction extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
System.out.println("in abnormal.1, arg is "+arg);
|
||||
arg = LuaThread.yield(ONE).arg1();
|
||||
System.out.println("in abnormal.2, arg is "+arg);
|
||||
error("abnormal condition");
|
||||
return ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user