diff --git a/README.html b/README.html
index 629ef0d3..a561f478 100644
--- a/README.html
+++ b/README.html
@@ -16,7 +16,7 @@
Getting Started with LuaJ
-James Roseborough, Ian Farmer, Version 2.0.2
+James Roseborough, Ian Farmer, Version 2.0.3
Copyright © 2009-2010 Luaj.org.
@@ -106,7 +106,7 @@ in comparison with the standard C distribution.
| 16.794 |
11.274 |
Java |
- java -cp luaj-jse-2.0.2.jar;bcel-5.2.jar lua -b fannkuch.lua 10 |
+ java -cp luaj-jse-2.0.3.jar;bcel-5.2.jar lua -b fannkuch.lua 10 |
|
|
@@ -116,7 +116,7 @@ in comparison with the standard C distribution.
16.701 |
13.789 |
|
- java -cp luaj-jse-2.0.2.jar lua -j fannkuch.lua 10 |
+ java -cp luaj-jse-2.0.3.jar lua -j fannkuch.lua 10 |
|
|
@@ -126,7 +126,7 @@ in comparison with the standard C distribution.
36.894 |
15.163 |
|
- java -cp luaj-jse-2.0.2.jar lua -n fannkuch.lua 10 |
+ java -cp luaj-jse-2.0.3.jar lua -n fannkuch.lua 10 |
| lua |
5.1.4 |
@@ -182,7 +182,7 @@ It is also faster than Java-lua implementations Jill, Kahlua, and Mochalua for a
From the main distribution directory line type:
- java -cp lib/luaj-jse-2.0.2.jar lua examples/lua/hello.lua
+ java -cp lib/luaj-jse-2.0.3.jar lua examples/lua/hello.lua
@@ -197,8 +197,8 @@ You should see the following output:
From the main distribution directory line type:
- java -cp lib/luaj-jse-2.0.2.jar luac examples/lua/hello.lua
- java -cp lib/luaj-jse-2.0.2.jar lua luac.out
+ java -cp lib/luaj-jse-2.0.3.jar luac examples/lua/hello.lua
+ java -cp lib/luaj-jse-2.0.3.jar lua luac.out
@@ -210,9 +210,9 @@ The compiled output "luac.out" is lua bytecode and should run and produce the sa
Luaj can compile to lua source code to Java source code:
- java -cp lib/luaj-jse-2.0.2.jar lua2java -s examples/lua -d . hello.lua
- javac -cp lib/luaj-jse-2.0.2.jar hello.java
- java -cp "lib/luaj-jse-2.0.2.jar;." lua -l hello
+ java -cp lib/luaj-jse-2.0.3.jar lua2java -s examples/lua -d . hello.lua
+ javac -cp lib/luaj-jse-2.0.3.jar hello.java
+ java -cp "lib/luaj-jse-2.0.3.jar;." lua -l hello
@@ -223,7 +223,7 @@ There are no additional dependencies for compiling or running source-to-source c
Lua scripts can also be run directly in this mode without precompiling using the lua command with the -j option when run in JDK 1.5 or higher:
- java -cp lib/luaj-jse-2.0.2.jar lua -j examples/lua/hello.lua
+ java -cp lib/luaj-jse-2.0.3.jar lua -j examples/lua/hello.lua
Compile lua bytecode to java bytecode
@@ -233,8 +233,8 @@ Luaj can compile lua sources or binaries directly to java bytecode if the bcel l
ant bcel-lib
- java -cp "lib/luaj-jse-2.0.2.jar;lib/bcel-5.2.jar" luajc -s examples/lua -d . hello.lua
- java -cp "lib/luaj-jse-2.0.2.jar;." lua -l hello
+ java -cp "lib/luaj-jse-2.0.3.jar;lib/bcel-5.2.jar" luajc -s examples/lua -d . hello.lua
+ java -cp "lib/luaj-jse-2.0.3.jar;." lua -l hello
@@ -245,7 +245,7 @@ but the compiled classes must be in the class path at runtime, unless runtime ji
Lua scripts can also be run directly in this mode without precompiling using the lua command with the -b option and providing the bcel library in the class path:
- java -cp "lib/luaj-jse-2.0.2.jar;lib/bcel-5.2.jar" lua -b examples/lua/hello.lua
+ java -cp "lib/luaj-jse-2.0.3.jar;lib/bcel-5.2.jar" lua -b examples/lua/hello.lua
@@ -270,7 +270,7 @@ A simple example may be found in
-You must include the library lib/luaj-jse-2.0.2.jar in your class path.
+You must include the library lib/luaj-jse-2.0.3.jar in your class path.
Run a script in a MIDlet
@@ -297,7 +297,7 @@ A simple example may be found in
-You must include the library lib/luaj-jme-2.0.2.jar in your midlet jar.
+You must include the library lib/luaj-jme-2.0.3.jar in your midlet jar.
An ant script to build and run the midlet is in
@@ -325,7 +325,7 @@ The standard use of JSR-223 scripting engines may be used:
All standard aspects of script engines including compiled statements should be supported.
-You must include the library lib/luaj-jse-2.0.2.jar in your class path.
+You must include the library lib/luaj-jse-2.0.3.jar in your class path.
A working example may be found in
@@ -337,7 +337,7 @@ To compile and run it using Java 1.6 or higher:
javac examples/jse/ScriptEngineSample.java
- java -cp "lib/luaj-jse-2.0.2.jar;examples/jse" ScriptEngineSample
+ java -cp "lib/luaj-jse-2.0.3.jar;examples/jse" ScriptEngineSample
Excluding the lua bytecode compiler
@@ -501,7 +501,7 @@ The following lua script will open a swiing frame on Java SE:
See a longer sample in examples/lua/swingapp.lua for details, or try running it using:
- java -cp lib/luaj-jse-2.0.2.jar lua examples/lua/swingapp.lua
+ java -cp lib/luaj-jse-2.0.3.jar lua examples/lua/swingapp.lua
@@ -729,8 +729,11 @@ and LuaForge:
Enhance javadoc, put it in distribution and on line
Major refactor of luajava type coercion logic, improve method selection.
Add lib/luaj-sources-2.0.2.jar for easier integration into an IDE such as Netbeans
+
| 2.0.3 |
+- Improve coroutine state logic including let unreferenced coroutines be garbage collected
|
-
+
+
Known Issues
- debug code may not be completely removed by some obfuscators
diff --git a/src/core/org/luaj/vm2/LuaThread.java b/src/core/org/luaj/vm2/LuaThread.java
index 97e87456..836dfade 100644
--- a/src/core/org/luaj/vm2/LuaThread.java
+++ b/src/core/org/luaj/vm2/LuaThread.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2007 LuaJ. All rights reserved.
+* Copyright (c) 2007-2011 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
@@ -21,6 +21,9 @@
******************************************************************************/
package org.luaj.vm2;
+
+import java.lang.ref.WeakReference;
+
import org.luaj.vm2.lib.DebugLib;
/**
@@ -56,7 +59,7 @@ import org.luaj.vm2.lib.DebugLib;
* @see JmePlatform
* @see CoroutineLib
*/
-public class LuaThread extends LuaValue implements Runnable {
+public class LuaThread extends LuaValue {
public static LuaValue s_metatable;
@@ -69,31 +72,36 @@ public class LuaThread extends LuaValue implements Runnable {
"suspended",
"running",
"normal",
- "dead" };
+ "dead",
+ "error" };
private int status = STATUS_SUSPENDED;
- private Thread thread;
+ private JavaThread thread;
private LuaValue env;
private LuaValue func;
- private Varargs args;
+
+ /** Field to hold state of error condition during debug hook function calls. */
public LuaValue err;
-
public static final int MAX_CALLSTACK = 256;
public final LuaFunction[] callstack = new LuaFunction[MAX_CALLSTACK];
public int calls = 0;
-
- private static final LuaThread mainthread = new LuaThread();
+
+ private static final LuaThread main_thread = new LuaThread();
// state of running thread including call stack
- private static LuaThread running_thread = mainthread;
+ private static LuaThread running_thread = main_thread;
- // thread-local used by DebugLib to store debugging state
+ /** Interval to check for LuaThread dereferencing. */
+ public static int GC_INTERVAL = 30000;
+
+ /** Thread-local used by DebugLib to store debugging state. */
public Object debugState;
-
- LuaThread() {
+ /** Private constructor for main thread only */
+ private LuaThread() {
+ status = STATUS_RUNNING;
}
/**
@@ -155,7 +163,7 @@ public class LuaThread extends LuaValue implements Runnable {
* @return true if this is the main thread
*/
public static boolean isMainThread(LuaThread r) {
- return r == mainthread;
+ return r == main_thread;
}
/**
@@ -216,101 +224,143 @@ public class LuaThread extends LuaValue implements Runnable {
running_thread.callstack[running_thread.calls-level]:
null;
}
-
- public void run() {
- synchronized ( this ) {
- try {
- this.args = func.invoke(this.args);
- status = STATUS_DEAD;
- } catch ( Throwable t ) {
- String msg = t.getMessage();
- this.args = valueOf(msg!=null? msg: t.toString());
- status = STATUS_ERROR;
- } finally {
- this.notify();
- }
- }
- }
- /** Yield this thread with arguments
+ /** 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 Varargs yield(Varargs args) {
- synchronized ( this ) {
- if ( status != STATUS_RUNNING )
- error(this+" not running");
- status = STATUS_SUSPENDED;
- this.args = args;
- this.notify();
- try {
- this.wait();
- status = STATUS_RUNNING;
- return this.args;
- } catch ( InterruptedException e ) {
- status = STATUS_DEAD;
- error( "thread interrupted" );
- return NONE;
- }
- }
+ public static Varargs yield(Varargs args) {
+ JavaThread t = running_thread.thread;
+ if ( t == null )
+ error("cannot yield main thread");
+ return t.yield(args);
}
-
+
/** Start or resume this thread
*
* @param args The arguments to send as return values to {@link #yield(Varargs)}
* @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);
+ }
- synchronized ( this ) {
- if ( status == STATUS_DEAD ) {
- return varargsOf(FALSE, valueOf("cannot resume dead coroutine"));
- }
-
- // 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 = this;
- this.status = STATUS_RUNNING;
-
- // copy args in
- this.args = args;
-
- // start the thread
- if ( thread == null ) {
- thread = new Thread(this);
- thread.start();
- }
-
- // run this vm until it yields
- this.notify();
- this.wait();
-
- // copy return values from yielding stack state
- if ( status == STATUS_ERROR ) {
- status = STATUS_DEAD;
- return varargsOf(FALSE, this.args);
- } else {
- return varargsOf(TRUE, this.args);
- }
-
- } catch ( Throwable t ) {
- status = STATUS_DEAD;
+ /**
+ * 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 {
- return varargsOf(FALSE, valueOf("thread: "+t));
+ 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();
}
-
- } finally {
- // previous thread is now running again
- running_thread = prior;
- prior.status = STATUS_RUNNING;
}
}
+ private Varargs yield(Varargs args) {
+ synchronized ( this ) {
+ if ( getStatus() != STATUS_RUNNING )
+ error(this+" not running");
+ setStatus( STATUS_SUSPENDED );
+ this.args = args;
+ 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
+ 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;
+ }
+ }
+ }
}
}
diff --git a/src/core/org/luaj/vm2/lib/CoroutineLib.java b/src/core/org/luaj/vm2/lib/CoroutineLib.java
index 428ba7fd..3d464a62 100644
--- a/src/core/org/luaj/vm2/lib/CoroutineLib.java
+++ b/src/core/org/luaj/vm2/lib/CoroutineLib.java
@@ -99,10 +99,7 @@ public class CoroutineLib extends VarArgFunction {
return valueOf( args.checkthread(1).getStatus() );
}
case YIELD: {
- final LuaThread r = LuaThread.getRunning();
- if ( LuaThread.isMainThread( r ) )
- error("main thread can't yield");
- return r.yield( args );
+ return LuaThread.yield( args );
}
case WRAP: {
final LuaValue func = args.checkfunction(1);
diff --git a/test/junit/org/luaj/vm2/AllTests.java b/test/junit/org/luaj/vm2/AllTests.java
index d4642840..290bdea4 100644
--- a/test/junit/org/luaj/vm2/AllTests.java
+++ b/test/junit/org/luaj/vm2/AllTests.java
@@ -47,6 +47,7 @@ public class AllTests {
vm.addTestSuite(MetatableTest.class);
vm.addTestSuite(LuaOperationsTest.class);
vm.addTestSuite(StringTest.class);
+ vm.addTestSuite(LuaThreadTest.class);
suite.addTest(vm);
// table tests
diff --git a/test/junit/org/luaj/vm2/LuaThreadTest.java b/test/junit/org/luaj/vm2/LuaThreadTest.java
new file mode 100644
index 00000000..bf8a37f4
--- /dev/null
+++ b/test/junit/org/luaj/vm2/LuaThreadTest.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * 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/lua/coroutinelib.lua b/test/lua/coroutinelib.lua
index 7b92dfca..31e3934e 100644
--- a/test/lua/coroutinelib.lua
+++ b/test/lua/coroutinelib.lua
@@ -95,3 +95,31 @@ step(111,222,333)
step()
step(111)
step(111,222,333)
+
+-- test loops in resume calls
+b = coroutine.create( function( arg )
+ while ( true ) do
+ print( ' b-resumed', arg, b == coroutine.running() )
+ print( ' b-b', coroutine.status(b) )
+ print( ' b-c', coroutine.status(c) )
+ print( ' b-resume-b',coroutine.resume( b, 'b-arg-for-b' ) )
+ print( ' b-resume-c',coroutine.resume( c, 'b-arg-for-c' ) )
+ arg = coroutine.yield( 'b-rslt' )
+ end
+end )
+c = coroutine.create( function( arg )
+ for i=1,3 do
+ print( ' c-resumed', arg, c == coroutine.running() )
+ print( ' c-b', coroutine.status(b) )
+ print( ' c-c', coroutine.status(c) )
+ print( ' c-resume-b',coroutine.resume( b, 'b-arg-for-b' ) )
+ print( ' c-resume-c',coroutine.resume( c, 'b-arg-for-c' ) )
+ arg = coroutine.yield( 'c-rslt' )
+ end
+end )
+for i=1,3 do
+ print( 'main-b', coroutine.status(b) )
+ print( 'main-c', coroutine.status(c) )
+ print( 'main-resume-b',coroutine.resume( b, 'main-arg-for-b' ) )
+ print( 'main-resume-c',coroutine.resume( c, 'main-arg-for-c' ) )
+end