Skip to content

Commit 76b98de

Browse files
authored
Deadlock workaround (#219)
* Add test for deadlock. * wip * wip * run more tests on linux
1 parent 5003569 commit 76b98de

File tree

3 files changed

+41
-25
lines changed

3 files changed

+41
-25
lines changed

Sources/Dependencies/DependencyValues.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import XCTestDynamicOverlay
1313

1414
#if _runtime(_ObjC)
1515
extension DispatchQueue {
16-
fileprivate static func mainSync<R>(execute block: @Sendable () -> R) -> R {
16+
fileprivate static func mainASAP(execute block: @escaping @Sendable () -> Void) {
1717
if Thread.isMainThread {
1818
return block()
1919
} else {
20-
return Self.main.sync(execute: block)
20+
return Self.main.async(execute: block)
2121
}
2222
}
2323
}
@@ -129,7 +129,7 @@ public struct DependencyValues: Sendable {
129129
/// that the library manages for you when you use the ``Dependency`` property wrapper.
130130
public init() {
131131
#if _runtime(_ObjC)
132-
DispatchQueue.mainSync {
132+
DispatchQueue.mainASAP {
133133
guard
134134
let XCTestObservation = objc_getProtocol("XCTestObservation"),
135135
let XCTestObservationCenter = NSClassFromString("XCTestObservationCenter"),

Tests/DependenciesTests/DependencyValuesTests.swift

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -398,32 +398,30 @@ final class DependencyValuesTests: XCTestCase {
398398
self.wait(for: [expectation], timeout: 1)
399399
}
400400

401-
#if !os(Linux)
402-
@MainActor
403-
func testEscapingInFeatureModel_InstanceVariablePropagated() {
404-
let expectation = self.expectation(description: "escape")
401+
@MainActor
402+
func testEscapingInFeatureModel_InstanceVariablePropagated() async {
403+
let expectation = self.expectation(description: "escape")
405404

406-
@MainActor
407-
class FeatureModel /*: ObservableObject*/ {
408-
@Dependency(\.fullDependency) var fullDependency
409-
func doSomething(expectation: XCTestExpectation) {
410-
DispatchQueue.main.async {
411-
XCTAssertEqual(self.fullDependency.value, 42)
412-
expectation.fulfill()
413-
}
405+
@MainActor
406+
class FeatureModel /*: ObservableObject*/ {
407+
@Dependency(\.fullDependency) var fullDependency
408+
func doSomething(expectation: XCTestExpectation) {
409+
DispatchQueue.main.async {
410+
XCTAssertEqual(self.fullDependency.value, 42)
411+
expectation.fulfill()
414412
}
415413
}
414+
}
416415

417-
let model = withDependencies {
418-
$0.fullDependency.value = 42
419-
} operation: {
420-
FeatureModel()
421-
}
422-
423-
model.doSomething(expectation: expectation)
424-
self.wait(for: [expectation], timeout: 1)
416+
let model = withDependencies {
417+
$0.fullDependency.value = 42
418+
} operation: {
419+
FeatureModel()
425420
}
426-
#endif
421+
422+
model.doSomething(expectation: expectation)
423+
await fulfillment(of: [expectation], timeout: 1)
424+
}
427425

428426
func testEscapingInFeatureModel_NotPropagated() async {
429427
let expectation = self.expectation(description: "escape")
@@ -679,6 +677,21 @@ final class DependencyValuesTests: XCTestCase {
679677
}
680678
}
681679
}
680+
681+
@MainActor
682+
func testDeadlock() async {
683+
DispatchQueue(label: "queue", qos: .utility).async {
684+
@Dependency(\.date) var date
685+
_ = date
686+
}
687+
688+
// Block main thread for 0.1 seconds.
689+
let start = Date()
690+
while Date().timeIntervalSince(start) < 0.1 {}
691+
692+
@Dependency(\.date) var date
693+
_ = date
694+
}
682695
}
683696

684697
struct CountInitDependency: TestDependencyKey {

Tests/DependenciesTests/FireAndForgetTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import Dependencies
22
import XCTest
33

4-
@MainActor
54
final class FireAndForgetTests: XCTestCase {
65
@Dependency(\.fireAndForget) var fireAndForget
76

87
// NB: These tests fail/crash in Wasm.
98
#if !os(WASI)
9+
@MainActor
1010
func testTestContext() async throws {
1111
let didExecute = ActorIsolated(false)
1212

@@ -19,6 +19,7 @@ final class FireAndForgetTests: XCTestCase {
1919
XCTAssertEqual(value, true)
2020
}
2121

22+
@MainActor
2223
func testTestContext_Cancellation() async throws {
2324
let didExecute = ActorIsolated(false)
2425

@@ -36,6 +37,7 @@ final class FireAndForgetTests: XCTestCase {
3637
XCTAssertEqual(value, true)
3738
}
3839

40+
@MainActor
3941
func testLiveContext() async throws {
4042
try await withDependencies {
4143
$0.context = .live
@@ -58,6 +60,7 @@ final class FireAndForgetTests: XCTestCase {
5860
#endif
5961

6062
#if !os(Linux) && !os(WASI) && !os(Windows)
63+
@MainActor
6164
func testLiveContext_DependencyAccess() async {
6265
await withDependencies {
6366
$0.context = .live

0 commit comments

Comments
 (0)