ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Ruby Signal.trap 回调里哪些方法不能调用?如何写出可重入的信号处理?

Ruby Signal.trap 回调里哪些方法不能调用?如何写出可重入的信号处理? Ruby Signal.trap 回调里哪些方法不能调用如何写出可重入的信号处理【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby在 Ruby 中用Signal.trap注册信号处理时一个常见的踩坑点是回调里随手调用Mutex#synchronize、往文件写日志程序运行一阵子后死锁或状态错乱。原因不是线程安全没做好而是信号回调要求可重入reentrant——线程安全并不等于可重入。Ruby 仓库在 doc/language/signals.rdoc 中专门写了一节「Caveats for implementing Signal.trap callbacks」给出了明确的不安全清单和安全清单。本文基于这份文档和 signal.c 中的方法文档说明回调里哪些方法不能调用以及如何组织出一个可重入的信号处理路径。先理解回调的执行时机doc/language/signals.rdoc 说明了 Ruby VM 的一个实现细节VM 会推迟Signal.trap回调的执行直到 VM 自身的内部数据结构处于安全状态但它无法知道你的代码里的数据结构何时安全Ruby 通过向系统注册一个很短的 C 函数来实现延迟信号处理这个 C 函数只包含 async-signal-safe 函数作用仅是通知 VM 稍后在主 Ruby 线程中执行通过Signal.trap注册的回调。所以传给Signal.trap的代码必须自己保证可重入。文档强调「线程安全」不保证可重入性Mutex#lock和Mutex#synchronize这类常用于保证线程安全的方法反而妨碍可重入性。回调里明确不能调用的方法doc/language/signals.rdoc 给出的判断原则是凡是没有被明确列在下面安全清单里的在有疑问时都应视为不安全。明确不安全的调用方法文档给出的原因Mutex#lock、Mutex#synchronize以及任何使用它们直接或间接的代码显式声明为不安全包括标准库中使用Mutex提供可重入性的Monitor带 block 的Dir.chdir文档将其列入不安全清单IO#sync为false时的任何 IO 写操作包括IO#write、IO#write_nonblock、IO#puts管道和 socket 默认IO#sync true除非你关闭过sync否则向它们写是安全的File#flock底层flock(2)调用不是 POSIX 规定的 async-signal-safe 函数回调里常见可安全执行的操作同一份文档列出了常见安全操作局部变量、实例变量、类变量的赋值与读取大多数对象分配以及Array、Hash、String、Struct、Time的初始化不执行 block 的常见Array、Hash、String、Struct操作——但如果其他线程正在迭代这些对象需要小心Hash#[]、Hash#[]前提是Hash.new没有传入一个不安全的 blockThread::Queue#push和Thread::SizedQueue#push自 Ruby 2.1 起通过Thread.new/Thread.start创建新线程——文档明确指出这可以用来绕过信号处理器中 Mutex 不可用的问题在传给Signal.trap的 block 内部再次调用Signal.trap是安全的对Integer和Float的算术运算、-、%、*、/。文档补充信号处理器不会在两次相邻的局部变量访问之间运行因此对Integer和Float使用、-这类简写不会触发数据竞争。系统调用包装方法中安全的一组Ruby 封装了许多 async-signal-safe 的 C 函数因此 doc/language/signals.rdoc 给出了一份**不完整Incomplete list**的系统调用包装方法安全清单包括Dir.chdir不带 block 形式、Dir.mkdir、Dir.openFile#truncate、File.link、File.open、File.readlink、File.rename、File.stat、File.symlink、File.truncate、File.unlink、File.utimeIO#close、IO#dup、IO#fsync、IO#read、IO#read_nonblock、IO#stat、IO#sysread、IO#syswriteIO.select、IO.pipeProcess.clock_gettime、Process.exit!、Process.fork、Process.kill、Process.pid、Process.ppid、Process.waitpid注意这份清单是不完整的不在清单里不等于一定不安全但按文档原则有疑问时应视为不安全。如何写出可重入的信号处理结合上面的清单文档支持的可重入写法有两条主路径。路径一回调里只做状态记录重活交给新线程这是文档直接给出的方案Thread.new/Thread.start可用于绕过信号处理器中不能使用 Mutex 的限制。# 回调内只做变量赋值安全需要加锁的重活放到新线程里可选分支按需选用 needs_reload false Signal.trap(:TERM) do needs_reload true end Thread.new do loop do if needs_reload # 这里可以使用 Mutex 等线程安全设施因为不在信号回调上下文里 needs_reload false reload_config end sleep 0.1 end end路径二回调内只使用安全清单里的操作典型组合是变量赋值 Thread::Queue#push或IO#syswriteIO#syswrite在安全清单内而带缓冲的IO#write在sync false时不安全queue Thread::Queue.new Signal.trap(:USR1) do |signo| # Thread::Queue#push 自 Ruby 2.1 起在回调中安全 queue.push(signo) end主循环或其他线程用Thread::Queue#pop取出事件做后续处理。验证回调确实被触发仓库中 spec/ruby/core/signal/trap_spec.rb 展示了标准的验证方式用Signal.trap(:HUP)注册回调然后Process.kill :HUP, Process.pid给自己发信号用Thread.pass until done等待回调执行最后检查回调里记录的状态。可以照搬这个模式done false Signal.trap(:HUP) do # 只放安全清单内的操作例如赋值 done true end Process.kill :HUP, Process.pid Thread.pass until done puts done ? handler ran : handler did not runSignal.trap还会返回上一个处理器spec 中同样用它做恢复先saved_trap Signal.trap(:HUP, proc)结束时Signal.trap(:HUP, saved_trap)还原避免测试或调试污染进程的信号状态。另外signal.c 中Signal.trap的文档给出了:EXIT信号的特殊用法进程终止前触发Signal.trap(0, proc { puts Terminating: #{$$} }) Signal.trap(CLD) { puts Child died } fork Process.wait文档示例输出PID 为文档中的示例值你的机器上会不同Terminating: 27461 Child died Terminating: 27460注册失败时的报错现象以下报错都来自 signal.c 的实现与 spec/ruby/core/signal/trap_spec.rb 的断言对:SEGV、:BUS、:ILL、:FPE、:VTALRM这类 Ruby 保留信号调用Signal.trap抛出ArgumentError消息为cant trap reserved signal: SIG%sdoc/NEWS/NEWS-2.0.0 记载了这一自 Ruby 2.0 起的不兼容变更对KILL、STOP调用Signal.trap抛出ArgumentError或Errno::EINVAL消息匹配Invalid argument|Signal already used by VM or OS信号名不存在或信号号越界时抛出ArgumentError例如invalid signal number (300)、unsupported signal SIGUSR10。Signal.trap的command参数还支持特殊值见 signal.c 方法文档IGNORE/SIG_IGN忽略信号、DEFAULT/SIG_DFL恢复 Ruby 默认处理器、EXIT让进程按信号终止、SYSTEM_DEFAULT使用操作系统的默认处理器。小结这份清单的边界安全清单和系统调用清单都来自 doc/language/signals.rdoc其中系统调用清单明确标注为不完整判断顺序以文档为准先查不安全清单Mutex 系、带 block 的Dir.chdir、syncfalse的 IO 写、File#flock再查安全清单都不在时按「视为不安全」处理需要加锁或调用任意代码时把重活移出回调用Thread.new开新线程或先经Thread::Queue#push把事件交给其他线程处理。【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表