From 403743fe48bf77932a90a1be2a64aff471506a1e Mon Sep 17 00:00:00 2001 From: Wess Cope Date: Tue, 29 Nov 2016 18:12:11 -0500 Subject: [PATCH] Adds signal termination handling when using control C, ensuring processes are also terminated --- .swift-version | 1 + Package.swift | 3 +- Sources/overlook/overlook.swift | 2 +- Sources/signals/signals.swift | 46 +++++++++++++++++++++++++ Sources/task/manager.swift | 61 ++++++++++++++++++++++----------- 5 files changed, 91 insertions(+), 22 deletions(-) create mode 100644 .swift-version create mode 100644 Sources/signals/signals.swift diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..cb2b00e --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.0.1 diff --git a/Package.swift b/Package.swift index ea43291..7907182 100644 --- a/Package.swift +++ b/Package.swift @@ -7,8 +7,9 @@ let package = Package( Target(name: "watch"), Target(name: "config", dependencies: ["json"]), Target(name: "json"), - Target(name: "task"), + Target(name: "task", dependencies: ["signals"]), Target(name: "env"), + Target(name: "signals"), ], dependencies: [ diff --git a/Sources/overlook/overlook.swift b/Sources/overlook/overlook.swift index 31a9bb8..31988fb 100644 --- a/Sources/overlook/overlook.swift +++ b/Sources/overlook/overlook.swift @@ -15,7 +15,7 @@ import env public class Overlook { static let name = "overlook" - static let version = "0.1.1" + static let version = "0.1.2" static let desc = "File monitoring tool that excutes on change. Used anywhere." static let dotTemplate:[String:Any] = [ diff --git a/Sources/signals/signals.swift b/Sources/signals/signals.swift new file mode 100644 index 0000000..c878e91 --- /dev/null +++ b/Sources/signals/signals.swift @@ -0,0 +1,46 @@ +// +// signals.swift +// overlook +// +// Created by Wesley Cope on 11/29/16. +// +// + +import Foundation +import Darwin + +public enum Signal { + case hup + case int + case quit + case abrt + case kill + case alrm + case term + case user(Int) + + public static var all:[Signal] = [.hup, .int, .quit, .abrt, .kill, .alrm, .term,] + + public var rawValue:Int32 { + switch self { + case .hup: return Int32(SIGHUP) + case .int: return Int32(SIGINT) + case .quit: return Int32(SIGQUIT) + case .abrt: return Int32(SIGABRT) + case .kill: return Int32(SIGKILL) + case .alrm: return Int32(SIGALRM) + case .term: return Int32(SIGTERM) + case .user(let sig): return Int32(sig) + } + } +} + +public typealias SignalHandler = @convention(c)(Int32) -> Void + +public func signalTrap(_ signal:Signal, handler:SignalHandler) { + var signalAction = sigaction(__sigaction_u: unsafeBitCast(handler, to: __sigaction_u.self), sa_mask: 0, sa_flags: 0) + _ = withUnsafePointer(to: &signalAction) { actionPointer in + + Darwin.sigaction(signal.rawValue, actionPointer, nil) + } +} diff --git a/Sources/task/manager.swift b/Sources/task/manager.swift index 9940a36..65a242e 100644 --- a/Sources/task/manager.swift +++ b/Sources/task/manager.swift @@ -7,7 +7,8 @@ // import Foundation -import PathKit +import PathKit +import signals public class TaskManager { private let defaultPath = "/usr/bin/env" @@ -15,36 +16,46 @@ public class TaskManager { private let lock = DispatchSemaphore(value: 1) private let taskQueue:DispatchQueue = DispatchQueue(label: "com.overlook.queue") - + + static weak var this:TaskManager? = nil + public init() {} - + public func add(_ task:Task) { sync { var current = tasks.filter { $0 != task } current.append(task) - + tasks = current } } - + @discardableResult public func create(_ arguments:[String], callback: @escaping TaskHandler) -> Task { let task = Task(arguments: arguments, path: defaultPath, callback: callback) - + add(task) - + return task } - + public func start(task:Task) { let filtered = tasks.filter { $0 == task } - + for task in filtered { task.start() } } - + public func start() { + TaskManager.this = self + + signalTrap(.int) { signal in + TaskManager.this?.kill() + + exit(signal) + } + for task in tasks { taskQueue.async(execute: task.workItem) } @@ -53,49 +64,59 @@ public class TaskManager { public func restart() { sync { let current = tasks - + for task in current { while(task.isRunning) { task.workItem.cancel() task.stop() } } - + tasks.removeAll() - + tasks = current.map { $0.copy() as! Task } start() } } - + public func stop(task:Task) { sync { let filtered = tasks.filter { $0 == task } - + for task in filtered { + task.workItem.cancel() task.stop() } } } - + public func remove(task:Task) { sync { var current = tasks let filtered = current.filter { $0 == task } - + for (index, task) in filtered.enumerated() { task.workItem.cancel() task.stop() - + current.remove(at: index) } - + tasks = current } } - + + public func kill() { + sync { + for task in tasks { + task.workItem.cancel() + task.stop() + } + } + } + private func sync(block:((Void) -> Void)) { lock.wait() block()