Improve detection and handling of orphaned coroutine threads.

This commit is contained in:
James Roseborough
2012-01-21 05:26:41 +00:00
parent 61514aa02c
commit 26ed1ef392
9 changed files with 398 additions and 314 deletions

View File

@@ -197,7 +197,7 @@ public class LuaClosure extends LuaFunction {
DebugLib.debugSetupCall(varargs, stack); DebugLib.debugSetupCall(varargs, stack);
// process instructions // process instructions
LuaThread.onCall( this ); LuaThread.CallStack cs = LuaThread.onCall( this );
try { try {
while ( true ) { while ( true ) {
if (DebugLib.DEBUG_ENABLED) if (DebugLib.DEBUG_ENABLED)
@@ -500,11 +500,10 @@ public class LuaClosure extends LuaFunction {
} }
} catch ( LuaError le ) { } catch ( LuaError le ) {
throw le; throw le;
} catch ( Throwable t ) { } catch ( Exception e ) {
LuaError le = new LuaError(t); throw new LuaError(e);
throw le;
} finally { } finally {
LuaThread.onReturn(); cs.onReturn();
if ( openups != null ) if ( openups != null )
for ( int u=openups.length; --u>=0; ) for ( int u=openups.length; --u>=0; )
if ( openups[u] != null ) if ( openups[u] != null )

View File

@@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * 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 class LuaThread extends LuaValue {
public static LuaValue s_metatable; public static LuaValue s_metatable;
public static int coroutine_count = 0;
private static final int STATUS_SUSPENDED = 0; static long thread_orphan_check_interval = 30000;
private static final int STATUS_RUNNING = 1;
private static final int STATUS_NORMAL = 2; private static final int STATUS_INITIAL = 0;
private static final int STATUS_DEAD = 3; private static final int STATUS_SUSPENDED = 1;
private static final int STATUS_ERROR = 4; 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 = { private static final String[] STATUS_NAMES = {
"suspended",
"suspended", "suspended",
"running", "running",
"normal", "normal",
"dead", "dead",};
"error" };
private int status = STATUS_SUSPENDED;
private JavaThread thread;
private LuaValue env; private LuaValue env;
private LuaValue func; private final State state;
/** Field to hold state of error condition during debug hook function calls. */ /** Field to hold state of error condition during debug hook function calls. */
public LuaValue err; public LuaValue err;
final CallStack callstack = new CallStack();
public static final int MAX_CALLSTACK = 256; 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(); private static final LuaThread main_thread = new LuaThread();
@@ -101,7 +102,8 @@ public class LuaThread extends LuaValue {
/** Private constructor for main thread only */ /** Private constructor for main thread only */
private LuaThread() { 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 * @param env The environment to apply to the thread
*/ */
public LuaThread(LuaValue func, LuaValue env) { public LuaThread(LuaValue func, LuaValue env) {
LuaValue.assert_(func != null, "function cannot be null");
this.env = env; this.env = env;
this.func = func; state = new State(this, func);
} }
public int type() { public int type() {
return LuaValue.TTHREAD; return LuaValue.TTHREAD;
} }
@@ -147,7 +150,7 @@ public class LuaThread extends LuaValue {
} }
public String getStatus() { 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 * @param function Function being called
* @return CallStack which is used to signal the return or a tail-call recursion
* @see DebugLib * @see DebugLib
*/ */
public static final void onCall(LuaFunction function) { public static final CallStack onCall(LuaFunction function) {
running_thread.callstack[running_thread.calls++] = function; CallStack cs = running_thread.callstack;
if (DebugLib.DEBUG_ENABLED) cs.onCall(function);
DebugLib.debugOnCall(running_thread, running_thread.calls, function); return cs;
}
/**
* 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;
} }
/** /**
@@ -220,21 +205,30 @@ public class LuaThread extends LuaValue {
* @return LuaFunction on the call stack, or null if outside of range of active stack * @return LuaFunction on the call stack, or null if outside of range of active stack
*/ */
public static final LuaFunction getCallstackFunction(int level) { public static final LuaFunction getCallstackFunction(int level) {
return level>0 && level<=running_thread.calls? return running_thread.callstack.getFunction(level);
running_thread.callstack[running_thread.calls-level]:
null;
} }
/**
* 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 /** Yield the current thread with arguments
* *
* @param args The arguments to send as return values to {@link #resume(Varargs)} * @param args The arguments to send as return values to {@link #resume(Varargs)}
* @return {@link Varargs} provided as arguments to {@link #resume(Varargs)} * @return {@link Varargs} provided as arguments to {@link #resume(Varargs)}
*/ */
public static Varargs yield(Varargs args) { public static Varargs yield(Varargs args) {
JavaThread t = running_thread.thread; State s = running_thread.state;
if ( t == null ) if (s.function == null)
error("cannot yield main thread"); throw new LuaError("cannot yield main thread");
return t.yield(args); return s.lua_yield(args);
} }
/** Start or resume this thread /** Start or resume this thread
@@ -243,124 +237,129 @@ public class LuaThread extends LuaValue {
* @return {@link Varargs} provided as arguments to {@link #yield(Varargs)} * @return {@link Varargs} provided as arguments to {@link #yield(Varargs)}
*/ */
public Varargs resume(Varargs args) { public Varargs resume(Varargs args) {
if ( status != STATUS_SUSPENDED ) if (this.state.status > STATUS_SUSPENDED)
return varargsOf(FALSE, valueOf("cannot resume "+STATUS_NAMES[status]+" coroutine")); return LuaValue.varargsOf(LuaValue.FALSE,
if ( thread == null ) LuaValue.valueOf("cannot resume "+LuaThread.STATUS_NAMES[this.state.status]+" coroutine"));
thread = new JavaThread(this,func); return state.lua_resume(this, args);
return thread.resume(this,args);
} }
/** static class State implements Runnable {
* Private helper class which contains the java stack used by this coroutine, final WeakReference lua_thread;
* and which detects when the LuaThread has been collected and completes. final LuaValue function;
*/ Varargs args = LuaValue.NONE;
private static final class JavaThread extends Thread { Varargs result = LuaValue.NONE;
private final WeakReference ref; String error = null;
private final LuaValue func; int status = LuaThread.STATUS_INITIAL;
private Varargs args;
private boolean started; State(LuaThread lua_thread, LuaValue function) {
private static int count; this.lua_thread = new WeakReference(lua_thread);
private JavaThread(LuaThread lua_thread,LuaValue func) { this.function = function;
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();
}
}
} }
private Varargs yield(Varargs args) { public synchronized void run() {
synchronized ( this ) { try {
if ( getStatus() != STATUS_RUNNING ) Varargs a = this.args;
error(this+" not running"); this.args = LuaValue.NONE;
setStatus( STATUS_SUSPENDED ); this.result = function.invoke(a);
this.args = args; } catch (Throwable t) {
this.error = t.getMessage();
} finally {
this.status = LuaThread.STATUS_DEAD;
this.notify(); 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 Varargs lua_resume(LuaThread new_thread, Varargs args) {
LuaThread previous_thread = LuaThread.running_thread;
synchronized ( this ) { try {
LuaThread.running_thread = new_thread;
// set prior thread to normal status while we are running this.args = args;
LuaThread prior = running_thread; if (this.status == STATUS_INITIAL) {
try { this.status = STATUS_RUNNING;
// set our status to running new Thread(this, "Coroutine-"+(++coroutine_count)).start();
prior.status = STATUS_NORMAL; } else {
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
this.notify(); 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;
}
} }
} }

View 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");
}
}

View File

@@ -18,11 +18,13 @@
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * 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 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
******************************************************************************/package org.luaj.vm2.lib; ******************************************************************************/
package org.luaj.vm2.lib;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.ref.WeakReference;
import org.luaj.vm2.LoadState; import org.luaj.vm2.LoadState;
import org.luaj.vm2.Lua; import org.luaj.vm2.Lua;
@@ -249,20 +251,20 @@ public class BaseLib extends OneArgFunction implements ResourceFinder {
case 7: // "pcall", // (f, arg1, ...) -> status, result1, ... case 7: // "pcall", // (f, arg1, ...) -> status, result1, ...
{ {
LuaValue func = args.checkvalue(1); LuaValue func = args.checkvalue(1);
LuaThread.onCall(this); LuaThread.CallStack cs = LuaThread.onCall(this);
try { try {
return pcall(func,args.subargs(2),null); return pcall(func,args.subargs(2),null);
} finally { } finally {
LuaThread.onReturn(); cs.onReturn();
} }
} }
case 8: // "xpcall", // (f, err) -> result1, ... case 8: // "xpcall", // (f, err) -> result1, ...
{ {
LuaThread.onCall(this); LuaThread.CallStack cs = LuaThread.onCall(this);
try { try {
return pcall(args.arg1(),NONE,args.checkvalue(2)); return pcall(args.arg1(),NONE,args.checkvalue(2));
} finally { } finally {
LuaThread.onReturn(); cs.onReturn();
} }
} }
case 9: // "print", // (...) -> void 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) { public static Varargs pcall(LuaValue func, Varargs args, LuaValue errfunc) {
try { try {
LuaThread thread = LuaThread.getRunning(); if (errfunc == null) {
LuaValue olderr = thread.err;
try {
thread.err = errfunc;
return varargsOf(LuaValue.TRUE, func.invoke(args)); return varargsOf(LuaValue.TRUE, func.invoke(args));
} finally { } else {
thread.err = olderr; 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 ) { } catch ( LuaError le ) {
String m = le.getMessage(); String m = le.getMessage();

View File

@@ -21,6 +21,8 @@
******************************************************************************/ ******************************************************************************/
package org.luaj.vm2.lib; package org.luaj.vm2.lib;
import java.lang.ref.WeakReference;
import org.luaj.vm2.Lua; import org.luaj.vm2.Lua;
import org.luaj.vm2.LuaBoolean; import org.luaj.vm2.LuaBoolean;
import org.luaj.vm2.LuaClosure; import org.luaj.vm2.LuaClosure;
@@ -244,7 +246,7 @@ public class DebugLib extends VarArgFunction {
/** DebugState is associated with a Thread */ /** DebugState is associated with a Thread */
static class DebugState { static class DebugState {
private final LuaThread thread; private final WeakReference thread_ref;
private int debugCalls = 0; private int debugCalls = 0;
private DebugInfo[] debugInfo = new DebugInfo[LuaThread.MAX_CALLSTACK+1]; private DebugInfo[] debugInfo = new DebugInfo[LuaThread.MAX_CALLSTACK+1];
private LuaValue hookfunc; private LuaValue hookfunc;
@@ -252,7 +254,7 @@ public class DebugLib extends VarArgFunction {
private int hookcount,hookcodes; private int hookcount,hookcodes;
private int line; private int line;
DebugState(LuaThread thread) { DebugState(LuaThread thread) {
this.thread = thread; this.thread_ref = new WeakReference(thread);
} }
public DebugInfo nextInfo() { public DebugInfo nextInfo() {
DebugInfo di = debugInfo[debugCalls]; DebugInfo di = debugInfo[debugCalls];
@@ -318,7 +320,8 @@ public class DebugLib extends VarArgFunction {
return new DebugInfo(func); return new DebugInfo(func);
} }
public String tojstring() { public String tojstring() {
return DebugLib.traceback(thread, 0); LuaThread thread = (LuaThread) thread_ref.get();
return thread != null? DebugLib.traceback(thread, 0): "orphaned thread";
} }
} }

View File

@@ -79,11 +79,11 @@ abstract public class VarArgFunction extends LibFunction {
* @param args the arguments to the function call. * @param args the arguments to the function call.
*/ */
public Varargs invoke(Varargs args) { public Varargs invoke(Varargs args) {
LuaThread.onCall(this); LuaThread.CallStack cs = LuaThread.onCall(this);
try { try {
return this.onInvoke(args).eval(); return this.onInvoke(args).eval();
} finally { } finally {
LuaThread.onReturn(); cs.onReturn();
} }
} }

View File

@@ -47,7 +47,7 @@ public class AllTests {
vm.addTestSuite(MetatableTest.class); vm.addTestSuite(MetatableTest.class);
vm.addTestSuite(LuaOperationsTest.class); vm.addTestSuite(LuaOperationsTest.class);
vm.addTestSuite(StringTest.class); vm.addTestSuite(StringTest.class);
vm.addTestSuite(LuaThreadTest.class); vm.addTestSuite(OrphanedThreadTest.class);
suite.addTest(vm); suite.addTest(vm);
// table tests // table tests

View File

@@ -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();
}
}

View 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;
}
}
}