Skip to content

Commit da69758

Browse files
authored
Merge branch 'main' into dependabot/github_actions/actions/checkout-5
2 parents 92dfa2e + 1ee0178 commit da69758

File tree

8 files changed

+74
-41
lines changed

8 files changed

+74
-41
lines changed

Sources/Nimble/DSL+Wait.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public class NMBWait: NSObject {
6969
}
7070
}
7171
}
72-
}.timeout(timeout, forcefullyAbortTimeout: leeway).wait(
72+
}.timeout(timeout, forcefullyAbortTimeout: leeway, isContinuous: false).wait(
7373
"waitUntil(...)",
7474
sourceLocation: SourceLocation(fileID: fileID, filePath: file, line: line, column: column)
7575
)

Sources/Nimble/Polling+AsyncAwait.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ internal actor Poller<T> {
4242
fnName: fnName) {
4343
if self.updateMatcherResult(result: try await matcherRunner())
4444
.toBoolean(expectation: style) {
45-
if matchStyle.isContinous {
45+
if matchStyle.isContinuous {
4646
return .incomplete
4747
}
4848
return .finished(true)
4949
} else {
50-
if matchStyle.isContinous {
50+
if matchStyle.isContinuous {
5151
return .finished(false)
5252
} else {
5353
return .incomplete

Sources/Nimble/Polling.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public struct PollingDefaults: @unchecked Sendable {
6969
internal enum AsyncMatchStyle {
7070
case eventually, never, always
7171

72-
var isContinous: Bool {
72+
var isContinuous: Bool {
7373
switch self {
7474
case .eventually:
7575
return false
@@ -96,15 +96,16 @@ internal func poll<T>(
9696
pollInterval: poll,
9797
timeoutInterval: timeout,
9898
sourceLocation: actualExpression.location,
99-
fnName: fnName) {
99+
fnName: fnName,
100+
isContinuous: matchStyle.isContinuous) {
100101
lastMatcherResult = try matcher.satisfies(uncachedExpression)
101102
if lastMatcherResult!.toBoolean(expectation: style) {
102-
if matchStyle.isContinous {
103+
if matchStyle.isContinuous {
103104
return .incomplete
104105
}
105106
return .finished(true)
106107
} else {
107-
if matchStyle.isContinous {
108+
if matchStyle.isContinuous {
108109
return .finished(false)
109110
} else {
110111
return .incomplete

Sources/Nimble/Utils/AsyncAwait.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if !os(WASI)
22

3+
import CoreFoundation
34
import Dispatch
45
import Foundation
56

Sources/Nimble/Utils/AsyncTimerSequence.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if !os(WASI)
22

3+
import CoreFoundation
34
import Dispatch
45
import Foundation
56

Sources/Nimble/Utils/PollAwait.swift

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if !os(WASI)
22

3+
import CoreFoundation
34
import Dispatch
45
import Foundation
56

@@ -156,7 +157,7 @@ internal class AwaitPromiseBuilder<T> {
156157
self.trigger = trigger
157158
}
158159

159-
func timeout(_ timeoutInterval: NimbleTimeInterval, forcefullyAbortTimeout: NimbleTimeInterval) -> Self {
160+
func timeout(_ timeoutInterval: NimbleTimeInterval, forcefullyAbortTimeout: NimbleTimeInterval, isContinuous: Bool) -> Self {
160161
/// = Discussion =
161162
///
162163
/// There's a lot of technical decisions here that is useful to elaborate on. This is
@@ -195,24 +196,33 @@ internal class AwaitPromiseBuilder<T> {
195196
let timedOutSem = DispatchSemaphore(value: 0)
196197
let semTimedOutOrBlocked = DispatchSemaphore(value: 0)
197198
semTimedOutOrBlocked.signal()
198-
let runLoop = RunLoop.main
199-
runLoop.perform(inModes: [.default], block: {
199+
let runLoop = CFRunLoopGetMain()
200+
#if canImport(Darwin)
201+
let runLoopMode = CFRunLoopMode.defaultMode.rawValue
202+
#else
203+
let runLoopMode = kCFRunLoopDefaultMode
204+
#endif
205+
CFRunLoopPerformBlock(runLoop, runLoopMode) {
200206
if semTimedOutOrBlocked.wait(timeout: .now()) == .success {
201207
timedOutSem.signal()
202208
semTimedOutOrBlocked.signal()
203209
if self.promise.resolveResult(.timedOut) {
204-
RunLoop.main.stop()
210+
CFRunLoopStop(CFRunLoopGetMain())
205211
}
206212
}
207-
})
213+
}
208214
// potentially interrupt blocking code on run loop to let timeout code run
209-
runLoop.stop()
215+
CFRunLoopStop(runLoop)
210216
let now = DispatchTime.now() + forcefullyAbortTimeout.dispatchTimeInterval
211217
let didNotTimeOut = timedOutSem.wait(timeout: now) != .success
212218
let timeoutWasNotTriggered = semTimedOutOrBlocked.wait(timeout: .now()) == .success
213219
if didNotTimeOut && timeoutWasNotTriggered {
214-
if self.promise.resolveResult(.blockedRunLoop) {
215-
runLoop.stop()
220+
if self.promise.resolveResult(isContinuous ? .timedOut : .blockedRunLoop) {
221+
#if canImport(CoreFoundation)
222+
CFRunLoopStop(CFRunLoopGetMain())
223+
#else
224+
RunLoop.main._stop()
225+
#endif
216226
}
217227
}
218228
}
@@ -300,7 +310,11 @@ internal class Awaiter {
300310
if completionCount < 2 {
301311
func completeBlock() {
302312
if promise.resolveResult(.completed(result)) {
303-
RunLoop.main.stop()
313+
#if canImport(CoreFoundation)
314+
CFRunLoopStop(CFRunLoopGetMain())
315+
#else
316+
RunLoop.main._stop()
317+
#endif
304318
}
305319
}
306320

@@ -338,12 +352,20 @@ internal class Awaiter {
338352
do {
339353
if let result = try closure() {
340354
if promise.resolveResult(.completed(result)) {
341-
RunLoop.current.stop()
355+
#if canImport(CoreFoundation)
356+
CFRunLoopStop(CFRunLoopGetCurrent())
357+
#else
358+
RunLoop.current._stop()
359+
#endif
342360
}
343361
}
344362
} catch let error {
345363
if promise.resolveResult(.errorThrown(error)) {
346-
RunLoop.current.stop()
364+
#if canImport(CoreFoundation)
365+
CFRunLoopStop(CFRunLoopGetCurrent())
366+
#else
367+
RunLoop.current._stop()
368+
#endif
347369
}
348370
}
349371
}
@@ -363,6 +385,7 @@ internal func pollBlock(
363385
timeoutInterval: NimbleTimeInterval,
364386
sourceLocation: SourceLocation,
365387
fnName: String = #function,
388+
isContinuous: Bool,
366389
expression: @escaping () throws -> PollStatus) -> PollResult<Bool> {
367390
let awaiter = NimbleEnvironment.activeInstance.awaiter
368391
let result = awaiter.poll(pollInterval) { () throws -> Bool? in
@@ -371,29 +394,10 @@ internal func pollBlock(
371394
}
372395
return nil
373396
}
374-
.timeout(timeoutInterval, forcefullyAbortTimeout: timeoutInterval.divided)
397+
.timeout(timeoutInterval, forcefullyAbortTimeout: timeoutInterval.divided, isContinuous: isContinuous)
375398
.wait(fnName, sourceLocation: sourceLocation)
376399

377400
return result
378401
}
379402

380-
#if canImport(CoreFoundation)
381-
import CoreFoundation
382-
383-
extension RunLoop {
384-
func stop() {
385-
CFRunLoopStop(getCFRunLoop())
386-
}
387-
}
388-
389-
#else
390-
391-
extension RunLoop {
392-
func stop() {
393-
_stop()
394-
}
395-
}
396-
397-
#endif
398-
399403
#endif // #if !os(WASI)

Tests/NimbleTests/PollingTest+Require.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if !os(WASI)
22

3+
import CoreFoundation
34
import Dispatch
45
import Foundation
56
import XCTest

Tests/NimbleTests/PollingTest.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
#if !os(WASI)
22

3-
import Dispatch
4-
#if canImport(CoreFoundation)
53
import CoreFoundation
6-
#endif
4+
import Dispatch
75
import Foundation
86
import XCTest
97
import Nimble
@@ -139,6 +137,33 @@ final class PollingTest: XCTestCase {
139137
}
140138
}
141139
}
140+
141+
func testToEventuallyDetectsStalledMainThreadActivity() {
142+
func spinAndReturnTrue() -> Bool {
143+
Thread.sleep(forTimeInterval: 0.5)
144+
return true
145+
}
146+
let msg = "expected to eventually be true, got <true> (timed out, but main run loop was unresponsive)."
147+
failsWithErrorMessage(msg) {
148+
expect(spinAndReturnTrue()).toEventually(beTrue())
149+
}
150+
}
151+
152+
func testToNeverDoesNotFailStalledMainThreadActivity() {
153+
func spinAndReturnTrue() -> Bool {
154+
Thread.sleep(forTimeInterval: 0.5)
155+
return true
156+
}
157+
expect(spinAndReturnTrue()).toNever(beFalse())
158+
}
159+
160+
func testToAlwaysDetectsStalledMainThreadActivity() {
161+
func spinAndReturnTrue() -> Bool {
162+
Thread.sleep(forTimeInterval: 0.5)
163+
return true
164+
}
165+
expect(spinAndReturnTrue()).toAlways(beTrue())
166+
}
142167

143168
func testCombiningAsyncWaitUntilAndToEventuallyIsNotAllowed() {
144169
// Currently we are unable to catch Objective-C exceptions when built by the Swift Package Manager

0 commit comments

Comments
 (0)