Skip to content

Commit 830a46c

Browse files
authored
Merge pull request #830 from appwrite/fix-apple-multiple-subscriptions
Fix Apple realtime with multiple subscriptions
2 parents 7a10919 + 4644ea2 commit 830a46c

File tree

7 files changed

+170
-105
lines changed

7 files changed

+170
-105
lines changed

templates/apple/Package.swift.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let package = Package(
2222
),
2323
],
2424
dependencies: [
25-
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"),
25+
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.17.0"),
2626
.package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"),
2727
],
2828
targets: [

templates/swift/Sources/Models/RealtimeModels.swift.twig

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import Foundation
22

33
public class RealtimeSubscription {
4-
public var close: () -> Void
4+
private var close: () async throws -> Void
55

6-
init(close: @escaping () -> Void) {
6+
init(close: @escaping () async throws-> Void) {
77
self.close = close
88
}
9+
10+
public func close() async throws {
11+
try await self.close()
12+
}
913
}
1014

1115
public class RealtimeCallback {
@@ -14,7 +18,7 @@ public class RealtimeCallback {
1418

1519
init(
1620
for channels: Set<String>,
17-
and callback: @escaping (RealtimeResponseEvent) -> Void
21+
with callback: @escaping (RealtimeResponseEvent) -> Void
1822
) {
1923
self.channels = channels
2024
self.callback = callback

templates/swift/Sources/Services/Realtime.swift.twig

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,23 @@ open class Realtime : Service {
77

88
private let TYPE_ERROR = "error"
99
private let TYPE_EVENT = "event"
10-
private let DEBOUNCE_MILLIS = 1
10+
private let DEBOUNCE_NANOS = 1_000_000
1111

1212
private var socketClient: WebSocketClient? = nil
1313
private var activeChannels = Set<String>()
1414
private var activeSubscriptions = [Int: RealtimeCallback]()
1515

1616
let connectSync = DispatchQueue(label: "ConnectSync")
17-
let callbackSync = DispatchQueue(label: "CallbackSync")
1817

1918
private var subCallDepth = 0
2019
private var reconnectAttempts = 0
2120
private var subscriptionsCounter = 0
2221
private var reconnect = true
2322

24-
private func createSocket() {
23+
private func createSocket() async throws {
2524
guard activeChannels.count > 0 else {
2625
reconnect = false
27-
closeSocket()
26+
try await closeSocket()
2827
return
2928
}
3029

@@ -38,17 +37,31 @@ open class Realtime : Service {
3837

3938
if (socketClient != nil) {
4039
reconnect = false
41-
closeSocket()
42-
} else {
43-
socketClient = WebSocketClient(url, tlsEnabled: !client.selfSigned, delegate: self)!
40+
try await closeSocket()
4441
}
4542

46-
try! socketClient?.connect()
43+
socketClient = WebSocketClient(
44+
url,
45+
tlsEnabled: !client.selfSigned,
46+
delegate: self
47+
)
48+
49+
try await socketClient?.connect()
4750
}
4851

49-
private func closeSocket() {
50-
socketClient?.close()
51-
//socket?.close(RealtimeCode.POLICY_VIOLATION.value, null)
52+
private func closeSocket() async throws {
53+
guard let client = socketClient,
54+
let group = client.threadGroup else {
55+
return
56+
}
57+
58+
if (client.isConnected) {
59+
let promise = group.any().makePromise(of: Void.self)
60+
client.close(promise: promise)
61+
try await promise.futureResult.get()
62+
}
63+
64+
try await group.shutdownGracefully()
5265
}
5366

5467
private func getTimeout() -> Int {
@@ -63,8 +76,8 @@ open class Realtime : Service {
6376
public func subscribe(
6477
channel: String,
6578
callback: @escaping (RealtimeResponseEvent) -> Void
66-
) -> RealtimeSubscription {
67-
return subscribe(
79+
) async throws -> RealtimeSubscription {
80+
return try await subscribe(
6881
channels: [channel],
6982
payloadType: String.self,
7083
callback: callback
@@ -74,8 +87,8 @@ open class Realtime : Service {
7487
public func subscribe(
7588
channels: Set<String>,
7689
callback: @escaping (RealtimeResponseEvent) -> Void
77-
) -> RealtimeSubscription {
78-
return subscribe(
90+
) async throws -> RealtimeSubscription {
91+
return try await subscribe(
7992
channels: channels,
8093
payloadType: String.self,
8194
callback: callback
@@ -86,8 +99,8 @@ open class Realtime : Service {
8699
channel: String,
87100
payloadType: T.Type,
88101
callback: @escaping (RealtimeResponseEvent) -> Void
89-
) -> RealtimeSubscription {
90-
return subscribe(
102+
) async throws -> RealtimeSubscription {
103+
return try await subscribe(
91104
channels: [channel],
92105
payloadType: T.self,
93106
callback: callback
@@ -98,36 +111,38 @@ open class Realtime : Service {
98111
channels: Set<String>,
99112
payloadType: T.Type,
100113
callback: @escaping (RealtimeResponseEvent) -> Void
101-
) -> RealtimeSubscription {
114+
) async throws -> RealtimeSubscription {
102115
subscriptionsCounter += 1
103-
let counter = subscriptionsCounter
116+
117+
let count = subscriptionsCounter
104118

105119
channels.forEach {
106120
activeChannels.insert($0)
107121
}
108122

109-
activeSubscriptions[counter] = RealtimeCallback(
123+
activeSubscriptions[count] = RealtimeCallback(
110124
for: Set(channels),
111-
and: callback
125+
with: callback
112126
)
113127

114128
connectSync.sync {
115129
subCallDepth+=1
116130
}
117131

118-
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(DEBOUNCE_MILLIS)) {
119-
if (self.subCallDepth == 1) {
120-
self.createSocket()
121-
}
122-
self.connectSync.sync {
123-
self.subCallDepth-=1
124-
}
132+
try await Task.sleep(nanoseconds: UInt64(DEBOUNCE_NANOS))
133+
134+
if self.subCallDepth == 1 {
135+
try await self.createSocket()
136+
}
137+
138+
connectSync.sync {
139+
self.subCallDepth -= 1
125140
}
126141

127142
return RealtimeSubscription {
128-
self.activeSubscriptions[counter] = nil
143+
self.activeSubscriptions[count] = nil
129144
self.cleanUp(channels: channels)
130-
self.createSocket()
145+
try await self.createSocket()
131146
}
132147
}
133148

@@ -163,7 +178,7 @@ extension Realtime: WebSocketClientDelegate {
163178
}
164179
}
165180

166-
public func onClose(channel: Channel, data: Data) {
181+
public func onClose(channel: Channel, data: Data) async throws {
167182
if (!reconnect) {
168183
reconnect = true
169184
return
@@ -173,10 +188,11 @@ extension Realtime: WebSocketClientDelegate {
173188

174189
print("Realtime disconnected. Re-connecting in \(timeout / 1000) seconds.")
175190

176-
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(timeout)) {
177-
self.reconnectAttempts += 1
178-
self.createSocket()
179-
}
191+
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000))
192+
193+
self.reconnectAttempts += 1
194+
195+
try await self.createSocket()
180196
}
181197

182198
public func onError(error: Swift.Error?, status: HTTPResponseStatus?) {
@@ -188,16 +204,10 @@ extension Realtime: WebSocketClientDelegate {
188204
}
189205

190206
func handleResponseEvent(from json: [String: Any]) {
191-
guard let data = json["data"] as? [String: Any] else {
192-
return
193-
}
194-
guard let channels = data["channels"] as? Array<String> else {
195-
return
196-
}
197-
guard let events = data["events"] as? Array<String> else {
198-
return
199-
}
200-
guard let payload = data["payload"] as? [String: Any] else {
207+
guard let data = json["data"] as? [String: Any],
208+
let channels = data["channels"] as? [String],
209+
let events = data["events"] as? [String],
210+
let payload = data["payload"] as? [String: Any] else {
201211
return
202212
}
203213
guard channels.contains(where: { channel in

0 commit comments

Comments
 (0)