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