Skip to content

Conversation

@Cykelero
Copy link

Bug

If user code changes the SDK configuration while the app is in the background, then the SDK can create a SessionManager.updateSessionDuration() timer that it never invalidates. After a while, these runaway timers add up, and can trigger many times a second, consuming problematic amounts of CPU power.

When I first noticed this behavior, it was causing Retcon.app to use 15-25% CPU while in the background, as it was trying to persist 2,834 sessions, extremely frequently.

Fix

This commit adds a check for foreground state before creating the timer.

Cause

Changing SDK configuration (by calling TelemetryDeck.initialize()), or even just by setting a new value on TelemetryManagerConfiguration.sessionID, causes TelemetryDeck to create a new session update timer, regardless of foreground state. This is a problem, because the SDK only invalidates the session update timer when going back to a background state. As such, the reference to the timer created in the background is silently replaced when creating a new timer, instead of first invalidating the former timer.

Calling TelemetryDeck.initialize() actually causes the timer to be created after a fixed 0.5s delay, making the bug very likely to occur if a macOS app updates its TelemetryDeck configuration upon being focused.

The following code reliably triggers the issue, if the user switches to the app, then quickly switches away:

NotificationCenter.default.addObserver(forName: NSApplication.didBecomeActiveNotification, object: nil, queue: nil) { _ in
	someExistingTelemetryDeckConfiguration.sessionID = UUID() // reset sessionID when entering foreground
	TelemetryDeck.initialize(config: someExistingTelemetryDeckConfiguration)
}

@Cykelero
Copy link
Author

This PR also reduces the frequency of the update timer, to further reduce power usage. In most situations, this doesn't affect at all the accuracy of the duration measurement, as far as I understand, because elapsed time is tallied up whenever the app is defocused.

Also, ideally, the code would be updated to more defensively prevent duplicate timers, by always checking for a running timer before overriding the sessionDurationUpdater reference. I didn't want to make too large changes, though, as I don't want accidentally break code I don't fully understand! It's just a bit disappointing to once again see this sort of unreliability in the TelemetryDeck SDK.

If TelemetryDeck.initialize() is called while the app is in the background, then the SessionManager creates a new session update timer in startNewSession(), that is ultimately never invalidated. These runaway timers can add up, and consume significant resources.

This commit prevents this from happening, by always checking for foreground state before creating the timer.
Elapsed time is counted anyway, whenever updateSessionDuration() is called. This includes when switching away from the app.
@kkostov
Copy link
Contributor

kkostov commented Jan 12, 2026

Hi @Cykelero!

Thanks a lot for this PR and the bug report. You're definitely right that this can be made more defensive.

To give some context:
The TelemetryDeck.initialize API is intended to setup the TelemetryDeck client for the first time in the lifecycle of an app. To shutdown the client once it has been started, we have the TelemetryDeck.terminate() API which must be called before calling initialize again. I've opened #273 to investigate if there may be still some observers that are not correctly disposed.

In the meantime, you can affect changes to configuration via one of the provided helpers. For example, to reset the session id, you can call TelemetryDeck.generateNewSession(). There is also TelemetryDeck.updateDefaultUserID(to:).

@kkostov kkostov self-assigned this Jan 12, 2026
@Cykelero
Copy link
Author

Thank you for the details, and the fix suggestion! Switching to TelemetryDeck.generateNewSession() indeed readily fixed the issue. Perfect!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants