diff --git a/src/core/org/luaj/vm2/LuaClosure.java b/src/core/org/luaj/vm2/LuaClosure.java index 7b9cdc63..8deb56b4 100644 --- a/src/core/org/luaj/vm2/LuaClosure.java +++ b/src/core/org/luaj/vm2/LuaClosure.java @@ -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 ) diff --git a/src/core/org/luaj/vm2/LuaThread.java b/src/core/org/luaj/vm2/LuaThread.java index 836dfade..a6f25508 100644 --- a/src/core/org/luaj/vm2/LuaThread.java +++ b/src/core/org/luaj/vm2/LuaThread.java @@ -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; + } } } diff --git a/src/core/org/luaj/vm2/OrphanedThread.java b/src/core/org/luaj/vm2/OrphanedThread.java new file mode 100644 index 00000000..08840d04 --- /dev/null +++ b/src/core/org/luaj/vm2/OrphanedThread.java @@ -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"); + } +} diff --git a/src/core/org/luaj/vm2/lib/BaseLib.java b/src/core/org/luaj/vm2/lib/BaseLib.java index bd570039..b2172529 100644 --- a/src/core/org/luaj/vm2/lib/BaseLib.java +++ b/src/core/org/luaj/vm2/lib/BaseLib.java @@ -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(); diff --git a/src/core/org/luaj/vm2/lib/DebugLib.java b/src/core/org/luaj/vm2/lib/DebugLib.java index 281438e5..ca4bfb7d 100644 --- a/src/core/org/luaj/vm2/lib/DebugLib.java +++ b/src/core/org/luaj/vm2/lib/DebugLib.java @@ -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"; } } diff --git a/src/core/org/luaj/vm2/lib/VarArgFunction.java b/src/core/org/luaj/vm2/lib/VarArgFunction.java index 7888f724..9fb07af1 100644 --- a/src/core/org/luaj/vm2/lib/VarArgFunction.java +++ b/src/core/org/luaj/vm2/lib/VarArgFunction.java @@ -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(); } } diff --git a/test/junit/org/luaj/vm2/AllTests.java b/test/junit/org/luaj/vm2/AllTests.java index 290bdea4..aef67d9f 100644 --- a/test/junit/org/luaj/vm2/AllTests.java +++ b/test/junit/org/luaj/vm2/AllTests.java @@ -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 diff --git a/test/junit/org/luaj/vm2/LuaThreadTest.java b/test/junit/org/luaj/vm2/LuaThreadTest.java deleted file mode 100644 index bf8a37f4..00000000 --- a/test/junit/org/luaj/vm2/LuaThreadTest.java +++ /dev/null @@ -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(); - } -} diff --git a/test/junit/org/luaj/vm2/OrphanedThreadTest.java b/test/junit/org/luaj/vm2/OrphanedThreadTest.java new file mode 100644 index 00000000..94244239 --- /dev/null +++ b/test/junit/org/luaj/vm2/OrphanedThreadTest.java @@ -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; + } + } +}