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);
// 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 )

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

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,
* 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();

View File

@@ -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";
}
}

View File

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

View File

@@ -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

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