此包为底层的 C sigaction
函数提供了 Swift 语法,以及延迟或取消 sigaction 处理程序的方法。
它兼容 macOS 和 Linux。在使用 sigaction 处理程序延迟功能之前,请仔细阅读文档。
我相信好的例子胜过千言万语。
使用 Swift 语法的简单传统 sigaction 处理程序示例
let action = Sigaction(handler: .ansiC({ sigID in
logger.debug("Handling signal \(Signal(rawValue: sigID)) from sigaction")
}))
try action.install(on: .terminated)
延迟的 sigaction 示例
let delayedSigaction = try SigactionDelayer_Unsig.registerDelayedSigaction(Signal.terminated, handler: { signal, doneHandler in
logger.debug("Received signal \(signal); delaying it to clean-up")
myAsyncCleanUp{
logger.debug("Cleanup is done, allowing signal to go through")
doneHandler(true)
})
})
在同一代码中包含这两个示例时,当程序收到终止信号(macOS 上为 15)时,将记录以下内容
Received signal SIGTERM; delaying it to clean-up
Cleanup is done, allowing signal to go through
Handling signal SIGTERM from sigaction
延迟信号是一个不寻常的过程,在使用此方法之前,您应该了解其内部工作原理。
我们有两种延迟策略。一种是将信号设置为忽略,直到信号被允许通过(SigactionDelayer_Unsig
);另一种是阻止传入信号,直到信号被允许通过(SigactionDelayer_Block
)。
两种策略都有优点和缺点。
当信号被阻止或忽略时,也会使用 libdispatch
(又名 GCD) 对其进行监控,libdispatch
本身使用 BSD 上的 kqueue
和 Linux 上的 signalfd
监控信号。这允许在需要时解除信号阻止或设置 sigaction 处理程序。
两种方法的重要注意事项:libdispatch
只能检测发送给整个进程的信号,而不能检测发送给线程的信号。因此,发送给线程的延迟信号将被永久阻止或忽略。
这是推荐的方法。
此方法不需要任何特定的引导。您可以随时延迟信号。
但是,一旦信号被注册为延迟,就不应手动更改 sigaction。(此项目的 Sigaction
结构提供的 install
方法除外,该方法知道信号上可能存在的取消信号操作,并可以相应地更新它们。)
当信号首次注册为延迟时,会发生以下几件事
sigaction 处理程序保存在内部结构中;
信号被设置为忽略;
生成一个专用线程(如果尚未生成),该线程解除阻止所有信号;
创建一个 dispatch source 以使用 GCD 监控传入信号。
然后,当收到信号时,会发生以下情况
libdispatch
通知 sigaction 延迟器,延迟器将反过来
临时重新安装信号的已保存 sigaction(在客户端表示可以之后),以及
将信号发送到专用线程。这会触发 sigaction,但不会通知 libdispatch
。
最后,延迟器将信号设置回被忽略状态。
此方法的注意事项:
在信号发送和设置回被忽略状态之间存在不可避免的竞争条件 (AFAICT);
发送回去的信号丢失了原始信号的 siginfo;
在 Linux 上,信号延迟很脆弱。有关更多信息,请参阅阻止策略的 Linux 注意事项。
您必须在使用此方法之前对其进行引导,在创建任何线程之前,将您要延迟的所有信号提供给引导方法。引导将首先阻止当前(主)线程上的所有给定信号,然后生成一个线程,在该线程上将解除阻止所有这些信号。
这允许我们的专用线程成为唯一允许接收要监控的信号的线程。
当信号注册为延迟时,延迟器也会阻止专用线程上的信号!
当收到信号时,libdispatch
将通知延迟器,延迟器将解除阻止信号,从而允许信号被传递。
此方法的注意事项:
在 macOS 上,当收到在所有线程上被阻止的信号时,它似乎被分配给任意线程。在另一个线程上解除阻止信号根本不会解除阻止它。为了解决这个问题,我们在解除阻止信号之前检查信号是否在专用线程上挂起。如果不是,我们将信号发送到我们的线程,从而再次丢失 sigaction,就像使用 unsigaction 策略时一样。此外,原始信号将永远在受影响的线程上保持挂起状态。
在 Linux 上,存在一个问题,与手册页所说的相反,当首次安装此信号的 dispatch source 时,libdispatch
确实会修改信号的 sigaction。因此,从理论上讲,这种策略不应该有效(老实说,另一种策略也不应该有效)。但是,有人注意到,在安装信号源*之后*更改 sigaction 足以避免此问题。因此,我们在安装信号源之前保存 sigaction,然后在安装源之后恢复它,我们就没问题了。但是,此解决方案似乎很脆弱,并且将来可能会失效,甚至现在都可能无法可靠地工作。