Skip to content

Commit 8cbe355

Browse files
committed
Merge branch 'dev.linux'
2 parents d25bbbc + 6e9ba48 commit 8cbe355

File tree

18 files changed

+417
-61
lines changed

18 files changed

+417
-61
lines changed

Doc/signal-tests/signal-test-blocked.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ static void *threadMain(void *info) {
4545

4646
sigset_t set;
4747
sigpending(&set);
48-
fprintf(stderr, " Other thread pending: %d\n", sigismember(&set, s));
48+
fprintf(stderr, "🧵 Other thread pending: %d\n", sigismember(&set, s));
4949

5050
sigemptyset(&set);
5151
sigaddset(&set, s);

Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ let package = Package(
1414
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "0.4.0"),
1515
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.2"),
1616
.package(url: "https://github.com/apple/swift-system.git", from: "0.0.1"),
17+
.package(url: "https://github.com/swift-server/swift-backtrace.git", from: "1.2.3"),
1718
.package(url: "https://github.com/xcode-actions/clt-logger.git", from: "0.3.0")
1819
],
1920
targets: [
@@ -24,8 +25,9 @@ let package = Package(
2425

2526
.target(name: "signal-handling-tests-helper", dependencies: [
2627
.product(name: "ArgumentParser", package: "swift-argument-parser"),
28+
.product(name: "Backtrace", package: "swift-backtrace"),
2729
.product(name: "CLTLogger", package: "clt-logger"),
28-
.product(name: "Logging", package: "swift-log"),
30+
.product(name: "Logging", package: "swift-log"),
2931
.target(name: "SignalHandling")
3032
]),
3133
.testTarget(name: "SignalHandlingTests", dependencies: [

Readme.adoc

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ François Lamboley <[email protected]>
44
This package provides a Swift syntax for the low-level C `sigaction` function
55
and a way to delay or cancel sigaction handlers.
66

7+
It is compatible with macOS and Linux. Read the documentation carefully before
8+
using the sigaction handler delaying capabilities.
9+
710
== Example of Use
811

912
I believe good examples are worth thousands of words.
@@ -95,7 +98,9 @@ not notify `libdispatch`.
9598

9699
* There is a non-avoidable race-condition (AFAICT) between the time the signal
97100
is sent and set back to ignored;
98-
* The signal that is sent back has lost the siginfo of the original signal.
101+
* The signal that is sent back has lost the siginfo of the original signal;
102+
* On Linux signal delaying is fragile. See Linux caveat of the blocking strategy
103+
for more information.
99104

100105
=== Details of the Blocking Strategy (`SigactionDelayer_Block`)
101106

@@ -113,10 +118,22 @@ dedicated thread too!
113118
When the signal is received, `libdispatch` will notify the delayer, which will
114119
unblock the signal, thus allowing it to be delivered.
115120

116-
**Caveat of this method**: On macOS, when a signal blocked on all threads is
117-
received, it seems to be affected to an arbitrary thread. Unblocking the signal
118-
on another thread will not unblock it at all. To workaround this problem we
119-
check if the signal is pending on the dedicated thread before unblocking it. If
120-
it is not, we send the signal to our thread, thus losing the sigaction again,
121-
exactly like when using the unsigaction strategy. Plus the original signal will
122-
stay pending on the affected thread forever.
121+
**Caveats of this method**:
122+
123+
* On macOS, when a signal blocked on all threads is received, it seems to be
124+
assigned to an arbitrary thread. Unblocking the signal on another thread will
125+
not unblock it at all. To workaround this problem we check if the signal is
126+
pending on the dedicated thread before unblocking it. If it is not, we send the
127+
signal to our thread, thus losing the sigaction again, exactly like when using
128+
the unsigaction strategy. Plus the original signal will stay pending on the
129+
affected thread forever.
130+
* On Linux, there is an issue where contrary to what the man page says,
131+
`libdispatch` https://github.com/apple/swift-corelibs-libdispatch/pull/560[does
132+
modify the sigaction of a signal when a dispatch source for this signal is first
133+
installed].
134+
So in theory this strategy should not work (and to be honest, the other one
135+
should not either). However, it has been noticed that changing the sigaction
136+
_after_ the signal source has been installed is enough to avoid this problem. So
137+
we save the sigaction before installing the signal source, then restore it after
138+
the source is installed, and we’re good. This solution seems fragile though, and
139+
might break in the future, or not even work reliably right now.

Sources/SignalHandling/CStructsInSwift/Sigaction.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,23 @@ public struct Sigaction : Equatable, RawRepresentable {
4545
self.mask = Signal.set(from: rawValue.sa_mask)
4646
self.flags = SigactionFlags(rawValue: rawValue.sa_flags)
4747

48+
#if !os(Linux)
4849
switch OpaquePointer(bitPattern: unsafeBitCast(rawValue.__sigaction_u.__sa_handler, to: Int.self)) {
4950
case OpaquePointer(bitPattern: unsafeBitCast(SIG_IGN, to: Int.self)): self.handler = .ignoreHandler
5051
case OpaquePointer(bitPattern: unsafeBitCast(SIG_DFL, to: Int.self)): self.handler = .defaultHandler
5152
default:
5253
if flags.contains(.siginfo) {self.handler = .posix(rawValue.__sigaction_u.__sa_sigaction)}
5354
else {self.handler = .ansiC(rawValue.__sigaction_u.__sa_handler)}
5455
}
56+
#else
57+
switch OpaquePointer(bitPattern: unsafeBitCast(rawValue.__sigaction_handler.sa_handler, to: Int.self)) {
58+
case OpaquePointer(bitPattern: unsafeBitCast(SIG_IGN, to: Int.self)): self.handler = .ignoreHandler
59+
case OpaquePointer(bitPattern: unsafeBitCast(SIG_DFL, to: Int.self)): self.handler = .defaultHandler
60+
default:
61+
if flags.contains(.siginfo) {self.handler = .posix(rawValue.__sigaction_handler.sa_sigaction)}
62+
else {self.handler = .ansiC(rawValue.__sigaction_handler.sa_handler)}
63+
}
64+
#endif
5565

5666
if !isValid {
5767
SignalHandlingConfig.logger?.warning("Initialized an invalid Sigaction.")
@@ -75,12 +85,21 @@ public struct Sigaction : Equatable, RawRepresentable {
7585
ret.sa_mask = Signal.sigset(from: mask)
7686
ret.sa_flags = flags.rawValue
7787

88+
#if !os(Linux)
7889
switch handler {
7990
case .ignoreHandler: ret.__sigaction_u.__sa_handler = SIG_IGN
8091
case .defaultHandler: ret.__sigaction_u.__sa_handler = SIG_DFL
8192
case .ansiC(let h): ret.__sigaction_u.__sa_handler = h
8293
case .posix(let h): ret.__sigaction_u.__sa_sigaction = h
8394
}
95+
#else
96+
switch handler {
97+
case .ignoreHandler: ret.__sigaction_handler.sa_handler = SIG_IGN
98+
case .defaultHandler: ret.__sigaction_handler.sa_handler = SIG_DFL
99+
case .ansiC(let h): ret.__sigaction_handler.sa_handler = h
100+
case .posix(let h): ret.__sigaction_handler.sa_sigaction = h
101+
}
102+
#endif
84103

85104
return ret
86105
}

Sources/SignalHandling/CStructsInSwift/SigactionFlag.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public struct SigactionFlags : OptionSet {
3232
/**
3333
If this bit is set, the handler is reset back to `SIG_DFL` at the moment the
3434
signal is delivered. */
35-
public static let resetHandler = SigactionFlags(rawValue: SA_RESETHAND)
35+
public static let resetHandler = SigactionFlags(rawValue: CInt(SA_RESETHAND) /* On Linux, an UInt32 instead of Int32, so we cast… */)
3636

3737
/** See `sigaction(2)`. */
3838
public static let restart = SigactionFlags(rawValue: SA_RESTART)

Sources/SignalHandling/CStructsInSwift/SigactionHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public enum SigactionHandler : Equatable {
1919
case defaultHandler
2020

2121
case ansiC(@convention(c) (_ signalID: Int32) -> Void)
22-
case posix(@convention(c) (_ signalID: Int32, _ siginfo: UnsafeMutablePointer<__siginfo>?, _ userThreadContext: UnsafeMutableRawPointer?) -> Void)
22+
case posix(@convention(c) (_ signalID: Int32, _ siginfo: UnsafeMutablePointer<siginfo_t>?, _ userThreadContext: UnsafeMutableRawPointer?) -> Void)
2323

2424
public static func ==(lhs: SigactionHandler, rhs: SigactionHandler) -> Bool {
2525
switch (lhs, rhs) {

Sources/SignalHandling/CStructsInSwift/Signal.swift

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
4848
/* https://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html */
4949

5050
public static var programErrorSignals: Set<Signal> {
51+
let platformDependant: Set<Signal>
52+
#if !os(Linux)
53+
platformDependant = Set(arrayLiteral: .emulatorTrap)
54+
#else
55+
platformDependant = Set()
56+
#endif
5157
return Set(arrayLiteral:
5258
.arithmeticError,
5359
.illegalInstruction,
@@ -56,9 +62,8 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
5662
.abortTrap,
5763
.iot,
5864
.traceBreakpointTrap,
59-
.emulatorTrap,
6065
.badSystemCall
61-
)
66+
).union(platformDependant)
6267
}
6368

6469
/**
@@ -74,7 +79,9 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
7479
/** Usually the same as `abortTrap`. */
7580
public static let iot = Signal(rawValue: SIGIOT)
7681
public static let traceBreakpointTrap = Signal(rawValue: SIGTRAP)
82+
#if !os(Linux)
7783
public static let emulatorTrap = Signal(rawValue: SIGEMT)
84+
#endif
7885
public static let badSystemCall = Signal(rawValue: SIGSYS)
7986

8087
/* *** Termination Signals *** */
@@ -128,8 +135,10 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
128135

129136
public static let ioPossible = Signal(rawValue: SIGIO)
130137
public static let urgentIOCondition = Signal(rawValue: SIGURG)
138+
#if os(Linux)
131139
/* System V signal name similar to SIGIO */
132-
// public static let poll = Signal(rawValue: SIGPOLL)
140+
public static let poll = Signal(rawValue: SIGPOLL)
141+
#endif
133142

134143
/* *** Job Control Signals *** */
135144
/* https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html */
@@ -147,7 +156,7 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
147156

148157
public static let childExited = Signal(rawValue: SIGCHLD)
149158
/* Obsolete name for SIGCHLD */
150-
// public static let cildExited = Signal(rawValue: SIGCLD)
159+
// public static let cildExited = Signal(rawValue: SIGCLD)
151160
public static let continued = Signal(rawValue: SIGCONT)
152161
/** Suspends the program. Cannot be handled, ignored or blocked. */
153162
public static let suspendedBySignal = Signal(rawValue: SIGSTOP)
@@ -168,26 +177,48 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
168177
}
169178

170179
public static let brokenPipe = Signal(rawValue: SIGPIPE)
171-
// public static let resourceLost = Signal(rawValue: SIGLOST)
180+
// public static let resourceLost = Signal(rawValue: SIGLOST)
172181
public static let cputimeLimitExceeded = Signal(rawValue: SIGXCPU)
173182
public static let filesizeLimitExceeded = Signal(rawValue: SIGXFSZ)
174183

175184
/* *** Miscellaneous Signals *** */
176185
/* https://www.gnu.org/software/libc/manual/html_node/Miscellaneous-Signals.html */
177186

178187
public static var miscellaneousSignals: Set<Signal> {
188+
let platformDependant: Set<Signal>
189+
#if !os(Linux)
190+
platformDependant = Set(arrayLiteral: .informationRequest)
191+
#else
192+
platformDependant = Set()
193+
#endif
179194
return Set(arrayLiteral:
180195
.userDefinedSignal1,
181196
.userDefinedSignal2,
182-
.windowSizeChanges,
183-
.informationRequest
184-
)
197+
.windowSizeChanges
198+
).union(platformDependant)
185199
}
186200

187201
public static let userDefinedSignal1 = Signal(rawValue: SIGUSR1)
188202
public static let userDefinedSignal2 = Signal(rawValue: SIGUSR2)
189203
public static let windowSizeChanges = Signal(rawValue: SIGWINCH)
204+
#if !os(Linux)
190205
public static let informationRequest = Signal(rawValue: SIGINFO)
206+
#endif
207+
208+
#if os(Linux)
209+
/* *** Other Signals *** */
210+
/* Not in GNU doc */
211+
212+
public static var otherSignals: Set<Signal> {
213+
return Set(arrayLiteral:
214+
.stackFault,
215+
.powerFailure
216+
)
217+
}
218+
219+
public static let stackFault = Signal(rawValue: SIGSTKFLT)
220+
public static let powerFailure = Signal(rawValue: SIGPWR)
221+
#endif
191222

192223
public static func set(from sigset: sigset_t) -> Set<Signal> {
193224
var sigset = sigset
@@ -231,6 +262,7 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
231262
}
232263

233264
/** Will return `usr1` or similar for `.userDefinedSignal1` for instance. */
265+
#if !os(Linux)
234266
public var signalName: String? {
235267
guard rawValue >= 0 && rawValue < NSIG else {
236268
return nil
@@ -242,6 +274,7 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
242274
})
243275
})
244276
}
277+
#endif
245278

246279
/**
247280
Return a user readable description of the signal (always in English I think). */
@@ -258,7 +291,11 @@ public struct Signal : RawRepresentable, Hashable, Codable, CaseIterable, Custom
258291
}
259292

260293
public var description: String {
294+
#if !os(Linux)
261295
return "SIG\((signalName ?? "\(rawValue)").uppercased())"
296+
#else
297+
return "\(signalDescription ?? "\(rawValue)")"
298+
#endif
262299
}
263300

264301
}

0 commit comments

Comments
 (0)