From ebd9c3c4ec2cfe2f08b4f700dcc5bcb2a8b4b847 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Thu, 16 Nov 2023 17:50:24 +0300 Subject: improved interruption (SIGINT, CTRL+C) support * Just like the original LuaJIT interpreter, this will use a hook to automatically raise an error from Lua code. * Unfortunately, as long as Jit compilation is enabled, this cannot reliably work. Therefore we still set an `interrupted` flag that must be polled from tight loops. * Instead of polling via applause_push_sample() which gave interruption-support only to Stream:play(), we now have a separate checkint() function. * checkint() should be manually added to all tight loops. * Stream:foreach() and everthing based on it is now also supporting interruptions (via checkint()). * This internally works via applause_is_interrupted(). A C function was exposed only because LuaJIT does not support volatile-qualifiers and would optimize away reads to the interrupted-flag. As a side effect, we can also reset the hook. * Flags set in signal handlers should be `volatile`. * Added likely() and unlikely() macros to C code. * Updated sample.ipynb Jupyter notebook: Everything important is now supported, albeit requiring custom ILua patches. --- applause.lua | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) (limited to 'applause.lua') diff --git a/applause.lua b/applause.lua index 702e1ce..702913d 100644 --- a/applause.lua +++ b/applause.lua @@ -32,6 +32,22 @@ end cdef_include "applause.h" +--- Check whether the process was interrupted (SIGINT received). +-- +-- This checks and automatically raises an error if CTRL+C was pressed +-- or SIGINT was received. +-- The automatic way to handle SIGINT is **not** reliable in LuaJIT. +-- A call to this function should therefore be added to all tight +-- loops. +function checkint() + -- This does not poll the interrupted-flag directly + -- since LuaJIT does not support volatile qualifiers and could + -- optimize the read away. + if C.applause_is_interrupted() ~= 0 then + error("SIGINT received", 2) + end +end + -- -- Define C functions for benchmarking (POSIX libc) -- @@ -664,8 +680,7 @@ function Stream:play(first_port) local old_stepmul = collectgarbage("setstepmul", 100) local channels = self.channels - local state - self:foreach(function(frame) + local _, err = pcall(Stream.foreach, self, function(frame) -- Loop should get unrolled automatically for i = 1, channels do local sample = tonumber(frame[i]) @@ -674,7 +689,7 @@ function Stream:play(first_port) -- NOTE: Invalid port Ids are currently silently -- ignored. Perhaps it's better to check state or -- to access output_ports_count from applause.c. - state = C.applause_push_sample(first_port+i, sample) + local state = C.applause_push_sample(first_port+i, sample) -- React to buffer underruns. -- This is done here instead of in the realtime thread @@ -683,17 +698,13 @@ function Stream:play(first_port) if state == C.APPLAUSE_AUDIO_XRUN then io.stderr:write("WARNING: Buffer underrun detected\n") end - - if state == C.APPLAUSE_AUDIO_INTERRUPTED then return true end end end) collectgarbage("setpause", old_pause) collectgarbage("setstepmul", old_stepmul) - if state == C.APPLAUSE_AUDIO_INTERRUPTED then - error("SIGINT received", 2) - end + if err then error(err, 2) end end --- Execute function for each frame generated by the stream. @@ -702,21 +713,19 @@ end -- @func fnc -- Function to execute. -- It gets passed an array of samples, one for each channel. --- @fixme This is not currently interruptable and therefore not suitable --- to be executed dynamically at the command-line. +-- If it returns true, the loop is terminated. function Stream:foreach(fnc) - local clear = table.clear - -- NOTE: This implementation is for single-channel streams -- only. See also MuxStream:foreach(). + local clear = table.clear local frame = table.new(1, 0) local tick = self:gtick() - while true do + repeat + checkint() frame[1] = tick() clear(sampleCache) - if not frame[1] or fnc(frame) then break end - end + until not frame[1] or fnc(frame) end --- Benchmark stream (time to generate all samples). @@ -873,8 +882,6 @@ end -- @string[opt="full"] vbufmode Buffering mode. -- @int[opt] vbufsize The buffer size. -- @see file:setvbuf --- @fixme This is currently allowed for infinite streams as well, --- but there is no way to interrupt Stream:foreach(). function Stream:pipe(prog, vbufmode, vbufsize) local hnd = io.popen(prog, "w") hnd:setvbuf(vbufmode or "full", vbufsize) @@ -1282,7 +1289,8 @@ function MuxStream:foreach(fnc) local channels = self.channels local frame = table.new(channels, 0) - while true do + repeat + checkint() clear(sampleCache) for i = 1, channels do @@ -1291,9 +1299,7 @@ function MuxStream:foreach(fnc) -- length, if one ends all end if not frame[i] then return end end - - if fnc(frame) then break end - end + until fnc(frame) end --- Mux several streams with the source stream into a multi-channel stream. -- cgit v1.2.3