Fixed issue: #55
This commit is contained in:
19
README.md
19
README.md
@@ -1023,7 +1023,10 @@ and at <a href="http://luaj.sourceforge.net/api/2.0/index.html">http://luaj.sour
|
||||
<li>debug code may not be completely removed by some obfuscators
|
||||
<li>tail calls are not tracked in debug information
|
||||
<li>mixing different versions of luaj in the same java vm is not supported
|
||||
<li>LuaJ runs on the host VM garbage collector, so object lifetime, weak reference timing, and finalization behavior are not identical to native Lua
|
||||
<li>the <code>__gc</code> metamethod is not supported as a reliable Lua finalization mechanism
|
||||
<li>values associated with weak keys may linger longer than expected
|
||||
<li>cascading weak-table collection can require multiple host GC cycles
|
||||
<li>behavior of luaj when a SecurityManager is used has not been fully characterized
|
||||
<li>negative zero is treated as identical to integer value zero throughout luaj
|
||||
<li>lua compiled into java bytecode using luajc cannot use string.dump() or xpcall()
|
||||
@@ -1031,6 +1034,22 @@ and at <a href="http://luaj.sourceforge.net/api/2.0/index.html">http://luaj.sour
|
||||
<li>shared metatables for string, bool, etc are shared across Globals instances in the same class loader
|
||||
<li>orphaned threads will not be collected unless garbage collection is run and sufficient time elapses
|
||||
</ul>
|
||||
<h3>Garbage Collection And Resources</h3>
|
||||
LuaJ does not implement the same garbage collector semantics as native Lua. Garbage collection is delegated to the host JVM or CLDC runtime, so <code>collectgarbage()</code> is only a hint and should not be used as a resource-management primitive.
|
||||
<p>
|
||||
In particular:
|
||||
<ul>
|
||||
<li>Do not rely on garbage collection to close files, sockets, database handles, or other resources.
|
||||
<li>Always call <code>close()</code> explicitly on files and iterators that own files.
|
||||
<li><code>io.lines(filename)</code> opens a file implicitly. If iteration is abandoned early, that file may remain open until explicitly collected by the host runtime.
|
||||
<li>Prefer <code>local f = assert(io.open(...))</code> together with <code>f:lines()</code> and an explicit <code>f:close()</code> when deterministic cleanup matters.
|
||||
<li>For implicit line iterators that need deterministic early cleanup, use <code>io.linesx(filename)</code> and call <code>iterator:close()</code>.
|
||||
<li><code>file:linesx()</code> provides the same closable iterator API for already-open files.
|
||||
<li>On Windows, leaked file handles can prevent rename or delete operations until the process exits.
|
||||
<li>On JME/CLDC, finalization support may be absent, so explicit close is mandatory.
|
||||
</ul>
|
||||
<p>
|
||||
Short-lived locals may also remain reachable longer than expected because host stack/register reuse is implementation-dependent. If prompt reclamation matters, isolate temporary allocations in functions and clear large references explicitly instead of assuming block exit is enough.
|
||||
<h3>File Character Encoding</h3>
|
||||
Source files can be considered encoded in UTF-8 or ISO-8859-1 and results should be as expected,
|
||||
with literal string contianing quoted characters compiling to the same byte sequences as the input.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator.class
Normal file
BIN
core/src/main/java/org/luaj/vm2/libs/IoLib$LinesIterator.class
Normal file
Binary file not shown.
Binary file not shown.
@@ -81,6 +81,10 @@ public class IoLib extends TwoArgFunction {
|
||||
abstract public void write( LuaString string ) throws IOException;
|
||||
abstract public void flush() throws IOException;
|
||||
abstract public boolean isstdfile();
|
||||
/**
|
||||
* Close the underlying resource explicitly.
|
||||
* Callers must not rely on garbage collection or finalization for cleanup.
|
||||
*/
|
||||
abstract public void close() throws IOException;
|
||||
abstract public boolean isclosed();
|
||||
// returns new position
|
||||
@@ -118,18 +122,6 @@ public class IoLib extends TwoArgFunction {
|
||||
public String tojstring() {
|
||||
return "file: " + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
public void finalize() throws Throwable {
|
||||
try {
|
||||
if (!isclosed()) {
|
||||
try {
|
||||
close();
|
||||
} catch (IOException ignore) {}
|
||||
}
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Enumerated value representing stdin */
|
||||
@@ -222,12 +214,15 @@ public class IoLib extends TwoArgFunction {
|
||||
|
||||
private static final int IO_INDEX = 18;
|
||||
private static final int LINES_ITER = 19;
|
||||
private static final int IO_LINESX = 20;
|
||||
private static final int FILE_LINESX = 21;
|
||||
|
||||
public static final String[] IO_NAMES = {
|
||||
"close",
|
||||
"flush",
|
||||
"input",
|
||||
"lines",
|
||||
"linesx",
|
||||
"open",
|
||||
"output",
|
||||
"popen",
|
||||
@@ -241,6 +236,7 @@ public class IoLib extends TwoArgFunction {
|
||||
"close",
|
||||
"flush",
|
||||
"lines",
|
||||
"linesx",
|
||||
"read",
|
||||
"seek",
|
||||
"setvbuf",
|
||||
@@ -316,6 +312,7 @@ public class IoLib extends TwoArgFunction {
|
||||
case IO_POPEN: return iolib._io_popen(args.checkjstring(1),args.optjstring(2,"r"));
|
||||
case IO_OPEN: return iolib._io_open(args.checkjstring(1), args.optjstring(2,"r"));
|
||||
case IO_LINES: return iolib._io_lines(args);
|
||||
case IO_LINESX: return iolib._io_linesx(args);
|
||||
case IO_READ: return iolib._io_read(args);
|
||||
case IO_WRITE: return iolib._io_write(args);
|
||||
|
||||
@@ -323,6 +320,7 @@ public class IoLib extends TwoArgFunction {
|
||||
case FILE_FLUSH: return iolib._file_flush(args.arg1());
|
||||
case FILE_SETVBUF: return iolib._file_setvbuf(args.arg1(),args.checkjstring(2),args.optint(3,8192));
|
||||
case FILE_LINES: return iolib._file_lines(args);
|
||||
case FILE_LINESX: return iolib._file_linesx(args);
|
||||
case FILE_READ: return iolib._file_read(args.arg1(),args.subargs(2));
|
||||
case FILE_SEEK: return iolib._file_seek(args.arg1(),args.optjstring(2,"cur"),args.optint(3,0));
|
||||
case FILE_WRITE: return iolib._file_write(args.arg1(),args.subargs(2));
|
||||
@@ -332,6 +330,12 @@ public class IoLib extends TwoArgFunction {
|
||||
}
|
||||
} catch ( IOException ioe ) {
|
||||
if (opcode == LINES_ITER) {
|
||||
if (toclose && f != null && !f.isclosed()) {
|
||||
try {
|
||||
f.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
String s = ioe.getMessage();
|
||||
error(s != null ? s : ioe.toString());
|
||||
}
|
||||
@@ -407,6 +411,14 @@ public class IoLib extends TwoArgFunction {
|
||||
return lines(infile, filename != null, args.subargs(2));
|
||||
}
|
||||
|
||||
// io.linesx(filename, ...) -> closable iterator
|
||||
public Varargs _io_linesx(Varargs args) {
|
||||
String filename = args.optjstring(1, null);
|
||||
File infile = filename==null? input(): ioopenfile(FTYPE_NAMED, filename,"r");
|
||||
checkopen(infile);
|
||||
return linesx(infile, filename != null, args.subargs(2));
|
||||
}
|
||||
|
||||
// io.read(...) -> (...)
|
||||
public Varargs _io_read(Varargs args) throws IOException {
|
||||
checkopen(input());
|
||||
@@ -447,6 +459,11 @@ public class IoLib extends TwoArgFunction {
|
||||
return lines(checkfile(args.arg1()), false, args.subargs(2));
|
||||
}
|
||||
|
||||
// file:linesx(...) -> closable iterator
|
||||
public Varargs _file_linesx(Varargs args) {
|
||||
return linesx(checkfile(args.arg1()), false, args.subargs(2));
|
||||
}
|
||||
|
||||
// file:read(...) -> (...)
|
||||
public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException {
|
||||
return ioread(checkfile(file),subargs);
|
||||
@@ -481,7 +498,7 @@ public class IoLib extends TwoArgFunction {
|
||||
if ( f == null ) argerror(1, "not a file: " + file);
|
||||
if ( f.isclosed() ) error("file is already closed");
|
||||
Varargs ret = ioread(f, args);
|
||||
if (toclose && ret.isnil(1) && f.eof()) f.close();
|
||||
if (toclose && ret.isnil(1) && !f.isclosed()) f.close();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -532,6 +549,84 @@ public class IoLib extends TwoArgFunction {
|
||||
}
|
||||
}
|
||||
|
||||
private Varargs linesx(final File f, boolean toclose, Varargs args) {
|
||||
try {
|
||||
return new LinesIterator(f, toclose, args);
|
||||
} catch ( Exception e ) {
|
||||
return error("linesx: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
private final class LinesIterator extends LuaTable {
|
||||
private final File file;
|
||||
private final boolean toclose;
|
||||
private final Varargs args;
|
||||
private boolean closed;
|
||||
|
||||
LinesIterator(File file, boolean toclose, Varargs args) {
|
||||
this.file = file;
|
||||
this.toclose = toclose;
|
||||
this.args = args.dealias();
|
||||
set("next", new next());
|
||||
set("close", new close());
|
||||
LuaTable mt = new LuaTable();
|
||||
mt.set(CALL, new invoke());
|
||||
setmetatable(mt);
|
||||
}
|
||||
|
||||
private Varargs nextLine() {
|
||||
if (closed || file.isclosed()) {
|
||||
return NIL;
|
||||
}
|
||||
try {
|
||||
Varargs ret = ioread(file, args);
|
||||
if (toclose && ret.isnil(1) && !file.isclosed()) {
|
||||
file.close();
|
||||
closed = true;
|
||||
}
|
||||
return ret;
|
||||
} catch (IOException ioe) {
|
||||
if (!file.isclosed()) {
|
||||
try {
|
||||
file.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
closed = true;
|
||||
String s = ioe.getMessage();
|
||||
error(s != null ? s : ioe.toString());
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private final class invoke extends VarArgFunction {
|
||||
public Varargs invoke(Varargs args) {
|
||||
return nextLine();
|
||||
}
|
||||
}
|
||||
|
||||
private final class next extends VarArgFunction {
|
||||
public Varargs invoke(Varargs args) {
|
||||
return nextLine();
|
||||
}
|
||||
}
|
||||
|
||||
private final class close extends OneArgFunction {
|
||||
public LuaValue call(LuaValue self) {
|
||||
if (!closed && !file.isclosed()) {
|
||||
try {
|
||||
file.close();
|
||||
} catch (IOException ioe) {
|
||||
String s = ioe.getMessage();
|
||||
error(s != null ? s : ioe.toString());
|
||||
}
|
||||
}
|
||||
closed = true;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Varargs iowrite(File f, Varargs args) throws IOException {
|
||||
for ( int i=1, n=args.narg(); i<=n; i++ )
|
||||
f.write( args.checkstring(i) );
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -438,6 +440,46 @@ public class FragmentsTest extends TestSuite {
|
||||
}
|
||||
}
|
||||
|
||||
public void testIoLinesClosesImplicitFileAtEnd() throws Exception {
|
||||
File file = writeTempFile("lines-close", "a\nb\n");
|
||||
try {
|
||||
Globals globals = JsePlatform.standardGlobals();
|
||||
Varargs iterTriplet = globals.get("io").get("lines").invoke(LuaValue.varargsOf(new LuaValue[] {
|
||||
LuaValue.valueOf(file.getAbsolutePath())
|
||||
}));
|
||||
LuaValue iter = iterTriplet.arg1();
|
||||
assertEquals(LuaValue.valueOf("a"), iter.call());
|
||||
assertEquals(LuaValue.valueOf("b"), iter.call());
|
||||
assertEquals(LuaValue.NIL, iter.call());
|
||||
|
||||
Field f = iter.getClass().getDeclaredField("f");
|
||||
f.setAccessible(true);
|
||||
Object fileHandle = f.get(iter);
|
||||
Method isclosed = fileHandle.getClass().getDeclaredMethod("isclosed");
|
||||
isclosed.setAccessible(true);
|
||||
assertEquals(Boolean.TRUE, isclosed.invoke(fileHandle));
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testIoLinesxCanBeClosedEarly() throws Exception {
|
||||
File file = writeTempFile("linesx-close", "a\nb\n");
|
||||
try {
|
||||
Globals globals = JsePlatform.standardGlobals();
|
||||
Varargs result = globals.load(
|
||||
"local it = io.linesx(" + quote(file.getAbsolutePath()) + ")\n" +
|
||||
"local first = it()\n" +
|
||||
"it:close()\n" +
|
||||
"return first, it()\n",
|
||||
"linesx.lua").invoke();
|
||||
assertEquals(LuaValue.valueOf("a"), result.arg1());
|
||||
assertEquals(LuaValue.NIL, result.arg(2));
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public void testTableMove() {
|
||||
runFragment(
|
||||
LuaValue.varargsOf(new LuaValue[] {
|
||||
|
||||
Reference in New Issue
Block a user