[Swift] DispatchSourceを使ってUNIXシグナルを受信する


はじめに

以下の環境で動作確認しました。

  • macOS 12.1 Monterey
  • Swift 5.5.1

実装例

DispatchSourceを使ってUNIXシグナルを受信する実装例を示します。以下はSIGINTを受信して「Received SIGINT」と表示するプログラムです。

なお、この記事ではsigaction関数とsignal関数を使う2パターンを掲載します。どのような違いがあり、どちらを使うべきかについては各自で調べてください。

sigaction関数を使う実装例

import Foundation

func run() {
  let dg = DispatchGroup()

  // sigaction構造体とsigaction関数の名前の衝突を防ぐため型エイリアスを定義する
  typealias SignalAction = sigaction

  var ignoreAction = SignalAction(
    __sigaction_u: unsafeBitCast(SIG_IGN, to: __sigaction_u.self),
    sa_mask: 0,
    sa_flags: 0
  )

  _ = withUnsafePointer(to: &ignoreAction) { ignoreActionPtr in
    // SIGINTを受信したときのデフォルトの挙動を無効にしておく
    sigaction(SIGINT, ignoreActionPtr, nil)
  }

  // シグナル受信用のスレッドを作成する
  let queue = DispatchQueue.global(qos: .default)

  let src = DispatchSource.makeSignalSource(signal: SIGINT, queue: queue)

  src.setEventHandler {
    print("Received SIGINT")
    dg.leave()
  }

  // シグナルの受信を開始する
  src.activate()

  // シグナルを受信するまでプログラムが終了しないように待機する
  dg.enter()
  dg.wait()

  print("Done")
}

run()

signal関数を使う実装例

import Foundation

func run() {
  let dg = DispatchGroup()

  // SIGINTを受信したときのデフォルトの挙動を無効にしておく
  signal(SIGINT, SIG_IGN)

  // シグナル受信用のスレッドを作成する
  let queue = DispatchQueue.global(qos: .default)

  let src = DispatchSource.makeSignalSource(signal: SIGINT, queue: queue)

  src.setEventHandler {
    print("Received SIGINT")
    dg.leave()
  }

  // シグナルの受信を開始する
  src.activate()

  // シグナルを受信するまでプログラムが終了しないように待機する
  dg.enter()
  dg.wait()

  print("Done")
}

run()

補足事項

makeSignalSourceにメインスレッド(DispatchQueue.main)を渡すと処理がブロックされるためシグナルが受信できません。そのため上記の実装例ではDispatchQueue.globalでシグナル受信用のスレッドを作成しています。

試運転

上記の実装例をmain.swiftとして保存してください。実行すると以下のように表示されます。

キーボードでCtrl-Cを入力するかkillコマンドでSIGINTを送信してください。

$ swift main.swift
Received SIGINT
Done

参考資料