Description
Bug report
- I confirm this is a bug with Supabase, not with my own application.
- I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
The Realtime library is re-subscribing infinitely, locking up the app. The subscribe function calls itself again when it runs into a try-catch error.
To Reproduce
Running my app with Supabase integration, logged in and connected to the realtime notifications over night on my Mac, it becomes occasionally unresponsive with infinite calls to the subscribe() func in the realtime library. The Mac is in sleep state and probably doesn't maintain a continuous stable internet connection.
Expected behavior
It should give up re-subscribing after an error after a few retries to prevent an infinite loop.
System information
- OS: macOS
- Version of supabase: 2.29.3
See some logging I could extract from XCode:
inifnite-resubscribe.txt
Debugging
XCode was still open and running luckily when it happened, so I could track it back to the original call. Find above also the extracted stack trace.
In the function from the RealtimeChannelV2 file, I can see it calls subscribe()
and in the try/catch block it calls subscribe()
again infinitely if the subscribe call times out. Ideally, it gives up or at least checks if the device is in sleep mode or has no connection or similar.
public func subscribe() async {
if socket.status != .connected {
if socket.options.connectOnSubscribe != true {
reportIssue(
"You can't subscribe to a channel while the realtime client is not connected. Did you forget to call `realtime.connect()`?"
)
return
}
await socket.connect()
}
status = .subscribing
logger?.debug("Subscribing to channel \(topic)")
let joinConfig = RealtimeJoinConfig(
broadcast: config.broadcast,
presence: config.presence,
postgresChanges: mutableState.clientChanges,
isPrivate: config.isPrivate
)
let payload = RealtimeJoinPayload(
config: joinConfig,
accessToken: await socket._getAccessToken(),
version: socket.options.headers[.xClientInfo]
)
let joinRef = socket.makeRef()
mutableState.joinRef = joinRef
logger?.debug("Subscribing to channel with body: \(joinConfig)")
await push(
ChannelEvent.join,
ref: joinRef,
payload: try! JSONObject(payload)
)
do {
try await withTimeout(interval: socket.options.timeoutInterval) { [self] in
_ = await statusChange.first { @Sendable in $0 == .subscribed }
}
} catch {
if error is TimeoutError {
logger?.debug("Subscribe timed out.")
await subscribe() // <----- Called over and over again as the connection is timing out repeatedly in sleep mode
} else {
logger?.error("Subscribe failed: \(error)")
}
}
}