LuaTeX io.lines 和打开文件描述符的数量

LuaTeX io.lines 和打开文件描述符的数量

(从一个更复杂的问题开始,但在写这个问题时进行调试将其缩小到下面的内容。)

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-escapelualatex -shell-escape-shell-escapetexlua luatexfilelimit.lualuatex 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

相关内容