(从一个更复杂的问题开始,但在写这个问题时进行调试将其缩小到下面的内容。)
luatexfilelimit.lua
考虑一个包含以下内容的文件:
-- Create the file (once)
local filename = 'luatexfilelimit.txt'
local command = string.format('echo "hello world" > "%s"', filename)
os.execute(command)
-- Print its contents (many times)
for i = 1, 3000000 do
print(string.format('Attempt %d', i))
for line in io.lines(filename) do
print(line)
end
end
并且.tex
文件仅包含以下内容:
\directlua{dofile('luatexfilelimit.lua')}
现在,当我使用或运行此.tex
文件时(实际上,如果文件已经创建,我们甚至可以删除,我们甚至可以调用或),很快我就会收到此错误:luatex -shell-escape
lualatex -shell-escape
-shell-escape
texlua luatexfilelimit.lua
luatex luatexfilelimit.lua
Attempt 249
hello world
Attempt 250
hello world
Attempt 251
latexfilelimit.lua:9: attempt to call a nil value
stack traceback:
latexfilelimit.lua:9: in main chunk
[C]: in function 'dofile'
[\directlua]:1: in main chunk.
l.1 \directlua{dofile('latexfilelimit.lua')}
?
似乎io.lines
返回了nil
,即使文件存在。当我将 shell 中打开文件描述符数量的限制更改为 时ulimit -n 100
,我相应地得到:
Attempt 94
hello world
Attempt 95
latexfilelimit.lua:9: attempt to call a nil value
stack traceback:
latexfilelimit.lua:9: in main chunk
[C]: in function 'dofile'
[\directlua]:1: in main chunk.
l.1 \directlua{dofile('latexfilelimit.lua')}
?
但是当我使用 Lua 解释器 ( ) 运行 Lua 文件时,lua luatexfilelimit.lua
它可以很好地处理大量的迭代。对我来说,这表明在 LuaTeX 中,它以某种方式io.lines
没有关闭文件,根据文档。
这是正确的吗?这是 LuaTeX 的一个错误吗?
(在 macOS 上通过 TeX Live 2017 使用 LuaTeX 进行测试。)
编辑:感谢 Henri Menke 的回答,他指出问题似乎出在 LuaTeX 的垃圾收集器上,collectgarbage()
在循环内添加(甚至collectgarbage('step')
几次)似乎可以解决问题。但令人惊讶的是为什么垃圾收集器甚至参与其中。请参阅以下内容:
collectgarbage('stop')
for i = 1, 123456 do
print(string.format('Step %d: memory %.2f KB', i, collectgarbage('count')))
for line in io.lines('luatexfilelimit.txt') do print(line) end
end
这里第一行完全关闭了垃圾收集器。然而,上面的文件在使用 运行时lua
,可以毫无问题地完成大量迭代。这也符合我对 文档的理解io.lines
,文档说它在到达末尾时会自动关闭文件(不需要 GC)。但是当使用texlua
或运行时luatex
,上面的代码不起作用。另外,考虑这个(糟糕的)代码,它打开文件但不关闭它们:
collectgarbage('stop')
for i = 1, 123456 do
local f = io.open('luatexfilelimit.txt')
print(string.format('Step %d: memory %.2f KB', i, collectgarbage('count')))
for line in io.lines('luatexfilelimit.txt') do print(line) end
end
当使用 Lua 解释器运行时,错误消息实际上是有帮助的:
lua: luatexfilelimit.lua:5: cannot open file 'luatexfilelimit.txt' (Too many open files)
stack traceback:
[C]: in function 'lines'
luatexfilelimit.lua:5: in main chunk
[C]: in ?
而在 LuaTeX 中,io.lines
似乎默默地返回 nil,没有其他信息。因此,LuaTeX 中的问题不仅仅是垃圾收集器速度慢;我怀疑 LuaTeX 实际上已经以io.lines
某种方式改变了行为,以至于它既不能正确关闭文件(而是依靠 GC),也不能正确抛出错误。
答案1
我不知道为什么,但出于某种原因,文件句柄不会自动被垃圾收集。如果你collectgarbage("collect")
在循环中运行,它会起作用。通过os.execute
和写入文件echo
依赖于平台且效率低下。我用一种更规范的方法代替了。
-- Create the file (once)
local filename = 'luatexfilelimit.txt'
local file = io.open(filename, "w")
file:write("hello world\n")
file:close()
-- Print its contents (many times)
for i = 1, 3000000 do
print(string.format('Attempt %d', i))
for line in io.lines(filename) do
print(line)
end
collectgarbage("collect")
end
它还可以与显式文件句柄(明确关闭)一起使用。
-- Create the file (once)
local filename = 'luatexfilelimit.txt'
local file = io.open(filename, "w")
file:write("hello world\n")
file:close()
-- Print its contents (many times)
for i = 1, 3000000 do
print(string.format('Attempt %d', i))
local file = io.open(filename, "r")
for line in file:lines() do
print(line)
end
file:close()
end