Skip to content

Thread exhausting while in background #3

Description

@tumata

Issue description

With two different applications I experienced the following bug which eventually leads to a crash:

When the application is in the background and we keep sending stats through the WatchRTC_SDK.RTCStatsReport callback then the application CPU will keep rising, hanging, and eventually will lead the OS to terminate the app.

See graph below:

Screenshot 2023-08-08 at 11 55 37

The profiler shows the same, with hangs, and also points to the WatchRTC SDK being the culprit for the hangs:

image

Cause

The following method func getStats(callback: @escaping (WatchRTC_SDK.RTCStatsReport) -> Void) provides the client with a callback object to be called later with available RTCStatsReport.

Following the example provided in the Vonage Demo App, this callback is saved and called possibly multiple times until a new callback is provided through the getStats delegate method.

When calling that saved callback while in the background, the WatchRTC will fail and eventually exhaust the thread, leading to a crash.

WatchRTC logs indicate that when going to the background everything seems to reinitialize properly:

WatchRTC log [DEBUG] - didEnterBackground
WatchRTC log [DEBUG] - disconnect
WatchRTC log [DEBUG] - Get stats timer stopped
WatchRTC log [DEBUG] - Socket disconnected

Unfortunately there is a clear issue with keeping on calling the callbacks from the getStats delegate method.

Possible fixes

1. Do not call the callback when in background

Simple patch. Call callback only when UIApplication.shared.applicationState != .background`

class WatchRTCClientLive {
    private var rtcStatsReportCallback: ((WatchRTC_SDK.RTCStatsReport) -> Void)?

    func sendRtcStatReport(jsonArrayOfReports: String) {
        guard let rtcStatReport = parseRTCStatsJsonString(jsonArrayOfReports: jsonArrayOfReports) else {
            return
        }
        if let rtcStatsReportCallback, UIApplication.shared.applicationState != .background {
            rtcStatsReportCallback(rtcStatReport)
        }
    }
}

extension WatchRTCClientLive: RtcDataProvider {
    func getStats(callback: @escaping (WatchRTC_SDK.RTCStatsReport) -> Void) {
        rtcStatsReportCallback = callback
    }
}

2. Only ever call the callback once

Once consumed, set the callback to nil and never call it again. This works because the getStats delegate method will not trigger while in the background, as expected.

class WatchRTCClientLive {
    private var rtcStatsReportCallback: ((WatchRTC_SDK.RTCStatsReport) -> Void)?

    func sendRtcStatReport(jsonArrayOfReports: String) {
        guard let rtcStatReport = parseRTCStatsJsonString(jsonArrayOfReports: jsonArrayOfReports) else {
            return
        }
        if let rtcStatsReportCallback {
            rtcStatsReportCallback(rtcStatReport)
            self.rtcStatsReportCallback = nil
        }
    }
}

extension WatchRTCClientLive: RtcDataProvider {
    func getStats(callback: @escaping (WatchRTC_SDK.RTCStatsReport) -> Void) {
        rtcStatsReportCallback = callback
    }
}

Comments

Both fixes (1) and (2) are only patches to the underlying issue.

I personally find it very odd for an iOS SDK to provide an API with a callback that is meant to be used multiple times, while at the same time updating this callback on a regular basis. This feels like an anti-pattern to me.

Thank you

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions