Skip to content

Commit e6f02b9

Browse files
authored
Merge pull request #87 from noppoMan/handle-signals
Handle signals
2 parents d325cf1 + ea33c71 commit e6f02b9

6 files changed

Lines changed: 212 additions & 1 deletion

File tree

Sources/Hexaville/main.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ func detectVersion() -> String {
216216
return "Unknown"
217217
}
218218

219+
// SIGINT handling
220+
SignalHandler.shared.trap(with: .int) {
221+
defer {
222+
exit(SignalHandler.Signal.int.rawValue)
223+
}
224+
SignalEventEmitter.shared.emit(with: .int)
225+
}
226+
219227
let hexavilleCLI = CLI(name: "hexaville", version: detectVersion())
220228
hexavilleCLI.commands = [GenerateProject(), Deploy(), RoutesCommand()]
221229
_ = hexavilleCLI.go()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// EventEmitter.swift
3+
// HexavillePackageDescription
4+
//
5+
// Created by Yuki Takei on 2017/10/17.
6+
//
7+
8+
import Foundation
9+
import Prorsum
10+
11+
/**
12+
Thread safety EventEmitter
13+
*/
14+
public class EventEmitter<T> {
15+
16+
private var mutex = Mutex()
17+
18+
public var onceListenersCount: Int {
19+
return onceListeners.count
20+
}
21+
22+
public var onListenersCount: Int {
23+
return onListeners.count
24+
}
25+
26+
private var onceListeners: [(T) -> Void] = []
27+
28+
private var onListeners: [(T) -> Void] = []
29+
30+
public init() {}
31+
32+
public func emit(with value: T) {
33+
defer {
34+
mutex.lock()
35+
onceListeners.removeAll()
36+
mutex.unlock()
37+
}
38+
39+
for handle in onceListeners {
40+
handle(value)
41+
}
42+
43+
for handle in onListeners {
44+
handle(value)
45+
}
46+
}
47+
48+
public func once(handler: @escaping (T) -> Void) {
49+
defer {
50+
mutex.unlock()
51+
}
52+
mutex.lock()
53+
onceListeners.append(handler)
54+
}
55+
56+
public func on(handler: @escaping (T) -> Void) {
57+
defer {
58+
mutex.unlock()
59+
}
60+
mutex.lock()
61+
onListeners.append(handler)
62+
}
63+
}

Sources/HexavilleCore/Proc.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,25 @@ public struct Proc {
2424

2525
public let stderr: Any?
2626

27+
public let pid: Int32?
28+
2729
public init(_ exetutablePath: String, _ arguments: [String] = [], environment: [String: String] = ProcessInfo.processInfo.environment) {
2830
let process = Process()
2931
process.launchPath = exetutablePath
3032
process.arguments = arguments
3133
process.environment = environment
3234
process.launch()
35+
36+
// handle SIGINT
37+
SignalEventEmitter.shared.once { sig in
38+
assert(sig == .int)
39+
process.interrupt()
40+
}
41+
3342
process.waitUntilExit()
3443
terminationStatus = process.terminationStatus
3544
stdout = process.standardOutput
3645
stderr = process.standardError
46+
pid = process.processIdentifier
3747
}
3848
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// SignalHandler.swift
3+
// HexavillePackageDescription
4+
//
5+
// Created by Yuki Takei on 2017/10/17.
6+
//
7+
#if os(OSX)
8+
import Darwin
9+
#else
10+
import Glibc
11+
#endif
12+
13+
import Foundation
14+
import Dispatch
15+
16+
public class SignalEventEmitter: EventEmitter<SignalHandler.Signal> {
17+
public static let shared = SignalEventEmitter()
18+
}
19+
20+
public class SignalHandler {
21+
22+
public enum Signal: Int32 {
23+
case hup = 1
24+
case int = 2
25+
case quit = 3
26+
case abrt = 6
27+
case kill = 9
28+
case alrm = 14
29+
case term = 15
30+
}
31+
32+
public static let shared = SignalHandler()
33+
34+
private var handlers: [() -> Void] = []
35+
36+
private init() {}
37+
38+
public func trap(with signal: Signal, handler: @escaping () -> Void) {
39+
handlers.append(handler)
40+
41+
let action: @convention(c) (Int32) -> () = { sig in
42+
for handle in SignalHandler.shared.handlers {
43+
handle()
44+
}
45+
}
46+
47+
#if os(macOS)
48+
var signalAction = sigaction(
49+
__sigaction_u: unsafeBitCast(action, to: __sigaction_u.self),
50+
sa_mask: 0,
51+
sa_flags: 0
52+
)
53+
54+
_ = withUnsafePointer(to: &signalAction) { actionPointer in
55+
sigaction(signal.rawValue, actionPointer, nil)
56+
}
57+
58+
#elseif os(Linux)
59+
var sigAction = sigaction()
60+
sigAction.__sigaction_handler = unsafeBitCast(
61+
action,
62+
to: sigaction.__Unnamed_union___sigaction_handler.self
63+
)
64+
_ = sigaction(signal.rawValue, &sigAction, nil)
65+
#endif
66+
}
67+
68+
public func raise(_ signal: Signal) {
69+
#if os(macOS)
70+
_ = Darwin.raise(signal.rawValue)
71+
#elseif os(Linux)
72+
_ = Glibc.raise(signal.rawValue)
73+
#endif
74+
}
75+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//
2+
// EventEmitterTests.swift
3+
// HexavillePackageDescription
4+
//
5+
// Created by Yuki Takei on 2017/10/17.
6+
//
7+
8+
import Foundation
9+
10+
import XCTest
11+
@testable import HexavilleCore
12+
13+
class EventEmitterTests: XCTestCase {
14+
15+
func testOn() {
16+
let exp = expectation(description: "testOnce")
17+
18+
let evm = EventEmitter<Int>()
19+
20+
evm.on {
21+
XCTAssertEqual($0 + 1, 2)
22+
exp.fulfill()
23+
}
24+
25+
evm.emit(with: 1)
26+
27+
waitForExpectations(timeout: 1, handler: nil)
28+
29+
XCTAssertEqual(evm.onListenersCount, 1)
30+
}
31+
32+
func testOnce() {
33+
let exp = expectation(description: "testOnce")
34+
35+
let evm = EventEmitter<Int>()
36+
37+
evm.once {
38+
XCTAssertEqual($0 + 1, 2)
39+
exp.fulfill()
40+
}
41+
42+
evm.emit(with: 1)
43+
44+
waitForExpectations(timeout: 1, handler: nil)
45+
46+
XCTAssertEqual(evm.onceListenersCount, 0)
47+
}
48+
49+
static var allTests = [
50+
("testOn", testOn),
51+
("testOnce", testOnce),
52+
]
53+
}
54+

Tests/LinuxMain.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import XCTest
44
XCTMain([
55
testCase(HexavilefileLoaderTest.allTests),
66
testCase(DotEnvParserTests.allTests),
7-
testCase(SwiftVersionTests.allTests)
7+
testCase(SwiftVersionTests.allTests),
8+
testCase(EventEmitterTests.allTests)
89
])

0 commit comments

Comments
 (0)