Skip to content

Commit

Permalink
Adjust implementation for async connect method
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Jul 23, 2024
1 parent ac008a4 commit dec1c3f
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ let package = Package(
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", from: "1.1.1"),
.package(url: "https://github.com/StanfordSpezi/Spezi.git", from: "1.4.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews.git", from: "1.5.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziBluetooth", exact: "3.0.0-beta.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziBluetooth", exact: "3.0.0-beta.2"),
.package(url: "https://github.com/StanfordSpezi/SpeziNetworking", from: "2.1.1"),
.package(url: "https://github.com/StanfordBDHG/XCTestExtensions.git", .upToNextMinor(from: "0.4.12"))
] + swiftLintPackage(),
Expand Down
60 changes: 45 additions & 15 deletions Sources/SpeziDevices/PairedDevices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,15 @@ public final class PairedDevices: @unchecked Sendable {

pendingConnectionAttempts[device.id] = Task {
await previousTask?.value // make sure its ordered
await device.connect()
do {
try await device.connect()
} catch {
guard !Task.isCancelled else {
return
}
logger.warning("Failed connection attempt for device \(device.label). Retrying ...")
connectionAttempt(for: device)
}
}
}

Expand Down Expand Up @@ -394,27 +402,49 @@ extension PairedDevices {
throw DevicePairingError.invalidState
}

await device.connect()

let id = device.id
let timeoutHandler = { @Sendable @MainActor in
_ = self.ongoingPairings.removeValue(forKey: id)?.signalTimeout()
}

async let _ = withTimeout(of: timeout, perform: timeoutHandler)
try await withThrowingDiscardingTaskGroup { group in
// timeout task
group.addTask {
await withTimeout(of: timeout) { @MainActor in
_ = self.ongoingPairings.removeValue(forKey: id)?.signalTimeout()
}
}

// connect task
group.addTask { @MainActor in
do {
try await device.connect()
} catch {
if error is CancellationError {
self.ongoingPairings.removeValue(forKey: id)?.signalCancellation()
}

try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
ongoingPairings[id] = PairingContinuation(continuation)
throw error
}
}
} onCancel: {
Task { @MainActor [weak device] in
ongoingPairings.removeValue(forKey: id)?.signalCancellation()
await device?.disconnect()

// pairing task
group.addTask { @MainActor in
try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
self.ongoingPairings[id] = PairingContinuation(continuation)
}
} onCancel: {
Task { @MainActor [weak device] in
self.ongoingPairings.removeValue(forKey: id)?.signalCancellation()
await device?.disconnect()
}
}
}
}

// if cancelled the continuation throws an CancellationError
// the task group above should exit with a CancellationError anyways, but safe to double check here
guard !Task.isCancelled else {
throw CancellationError()
}

await registerPairedDevice(device)
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/SpeziDevicesTests/PairedDevicesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ final class PairedDevicesTests: XCTestCase {
}()


await device.connect()
try await device.connect()
try await Task.sleep(for: .seconds(1.1))
XCTAssertEqual(device.state, .connected)

Expand Down Expand Up @@ -113,7 +113,7 @@ final class PairedDevicesTests: XCTestCase {
}
device.$nearby.inject(true)

await device.connect()
try await device.connect()
try await XCTAssertThrowsErrorAsync(await devices.pair(with: device)) { error in
XCTAssertEqual(try XCTUnwrap(error as? DevicePairingError), .invalidState)
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/UITests/TestApp/BluetoothViewsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct BluetoothViewsTest: View {
Task {
switch device.state {
case .disconnected, .disconnecting:
await device.connect()
try await device.connect()
case .connecting, .connected:
await device.disconnect()
}
Expand Down
6 changes: 3 additions & 3 deletions Tests/UITests/TestApp/DevicesTestView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ struct DevicesTestView: View {
device.$advertisementData.inject(AdvertisementData()) // trigger onChange advertisement
}
AsyncButton {

Check failure on line 50 in Tests/UITests/TestApp/DevicesTestView.swift

View workflow job for this annotation

GitHub Actions / Build and Test iOS Latest / Test using xcodebuild or run fastlane

invalid conversion from throwing function of type '() async throws -> Void' to non-throwing function type '() async -> Void'

Check failure on line 50 in Tests/UITests/TestApp/DevicesTestView.swift

View workflow job for this annotation

GitHub Actions / Build and Test iOS / Test using xcodebuild or run fastlane

invalid conversion from throwing function of type '() async throws -> Void' to non-throwing function type '() async -> Void'
await device.connect()
await weightScale.connect()
await bloodPressureCuff.connect()
try await device.connect()
try await weightScale.connect()
try await bloodPressureCuff.connect()
} label: {
Label("Connect", systemImage: "cable.connector")
}
Expand Down

0 comments on commit dec1c3f

Please sign in to comment.