捕获 SIGINT 和清理的正确方法?

Correct way to catch SIGINT and cleanup?

编写一个 cli 工具,在启动时打开 OS X 网络代理,在关闭时我想再次将其关闭。捕获 SIGINT 并执行应用程序清理的正确方法是什么?尝试了以下并跟踪了消息,但没有 运行 系统命令或退出:

Signal::INT.trap do
  puts "trap"
  fork do
    system "networksetup -setwebproxystate Wi-Fi off"
  end
  exit
end

此代码确实退出但给出了 'Invalid memory access' 错误

at_exit do
  fork do
    system "networksetup -setwebproxystate Wi-Fi off"
  end
end

LibC.signal Signal::INT.value, ->(s : Int32) { exit }
Invalid memory access (signal 10) at address 0x10d3a8e00
[0x10d029b4b] *CallStack::print_backtrace:Int32 +107
[0x10d0100d5] __crystal_sigfault_handler +181
[0x7fff6c5b3b3d] _sigtramp +29

更新

这是使用 Signal::INT.trap 的完整 'app',对我来说 运行ning 将正确打开和关闭 OS X 代理设置,但循环将继续运行中断信号后

fork do
  system "networksetup -setwebproxy Wi-Fi  127.0.0.1 4242"
end

Signal::INT.trap do
  puts "trap"
  fork do
    system "networksetup -setwebproxystate Wi-Fi off"
  end
  exit
end

loop do
  sleep 1
  puts "foo"
end

您可以使用 Fibers?

spawn do
  system "networksetup -setwebproxy Wi-Fi  127.0.0.1 4242"
end

sleep 0.1

Signal::INT.trap do
  puts "trap"
  spawn do
    system "networksetup -setwebproxystate Wi-Fi off"
  end
  sleep 0.1
  exit
end

loop do
  sleep 1
  puts "foo"
end

恕我直言,麻烦来自 crystal-lang 的 fork,它有一些奇怪的语义。

当您尝试启动工作进程以 运行 system 调用时,crystal 也复制了 loop...

并且当exit执行时,第一个循环退出,而不是分叉的循环。

要验证这一点,您可以像这样将一些 sleep 写入 forkINT.trap 块:

fork do
  system "echo \"start\""
end

Signal::INT.trap do
  puts "trap"
  fork do
    system "echo \"off\""
    sleep 15
  end
  sleep 20
  exit
end

loop do
  sleep 1
  puts "foo"
end

然后尝试连续观察ps命令的结果。

@Sergey Fedorov 使用光纤回答了替代方法。

进一步阅读:Process.fork has dangerous semantics