Skip to content

Commit a222dd4

Browse files
authored
fix(realtime): handle timeout when subscribing to channel (#349)
* fix(realtime): handle timeout when subscribing to channel * test: add test for channel subscribe timeout * Fix typo * test: add test for subscribe with timeout * remove duplicated test
1 parent 41e3c6e commit a222dd4

File tree

5 files changed

+272
-112
lines changed

5 files changed

+272
-112
lines changed

Sources/Realtime/V2/PushV2.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,18 @@ actor PushV2 {
2323
await channel?.socket?.push(message)
2424

2525
if channel?.config.broadcast.acknowledgeBroadcasts == true {
26-
return await withCheckedContinuation {
27-
receivedContinuation = $0
26+
do {
27+
return try await withTimeout(interval: channel?.socket?.options.timeoutInterval ?? 10) {
28+
await withCheckedContinuation {
29+
self.receivedContinuation = $0
30+
}
31+
}
32+
} catch is TimeoutError {
33+
channel?.logger?.debug("Push timed out.")
34+
return .timeout
35+
} catch {
36+
channel?.logger?.error("Error sending push: \(error)")
37+
return .error
2838
}
2939
}
3040

Sources/Realtime/V2/RealtimeChannelV2.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,18 @@ public actor RealtimeChannelV2 {
108108
)
109109
)
110110

111-
_ = await statusChange.first { @Sendable in $0 == .subscribed }
111+
do {
112+
try await withTimeout(interval: socket?.options.timeoutInterval ?? 10) { [self] in
113+
_ = await statusChange.first { @Sendable in $0 == .subscribed }
114+
}
115+
} catch {
116+
if error is TimeoutError {
117+
logger?.debug("subscribe timed out.")
118+
await subscribe()
119+
} else {
120+
logger?.error("subscribe failed: \(error)")
121+
}
122+
}
112123
}
113124

114125
public func unsubscribe() async {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// Task+withTimeout.swift
3+
//
4+
//
5+
// Created by Guilherme Souza on 19/04/24.
6+
//
7+
8+
import Foundation
9+
10+
@discardableResult
11+
package func withTimeout<R: Sendable>(
12+
interval: TimeInterval,
13+
@_inheritActorContext operation: @escaping @Sendable () async throws -> R
14+
) async throws -> R {
15+
try await withThrowingTaskGroup(of: R.self) { group in
16+
defer {
17+
group.cancelAll()
18+
}
19+
20+
let deadline = Date(timeIntervalSinceNow: interval)
21+
22+
group.addTask {
23+
try await operation()
24+
}
25+
26+
group.addTask {
27+
let interval = deadline.timeIntervalSinceNow
28+
if interval > 0 {
29+
try await Task.sleep(nanoseconds: NSEC_PER_SEC * UInt64(interval))
30+
}
31+
try Task.checkCancellation()
32+
throw TimeoutError()
33+
}
34+
35+
return try await group.next()!
36+
}
37+
}
38+
39+
package struct TimeoutError: Error, Hashable {}

0 commit comments

Comments
 (0)