Skip to content

feat: Add pending task queue for SDK initialization#7377

Open
itaybre wants to merge 4 commits intomainfrom
itaybrenner/cocoa-1010-setuser-not-work-after-upgade-to-8540
Open

feat: Add pending task queue for SDK initialization#7377
itaybre wants to merge 4 commits intomainfrom
itaybrenner/cocoa-1010-setuser-not-work-after-upgade-to-8540

Conversation

@itaybre
Copy link
Contributor

@itaybre itaybre commented Feb 4, 2026

📜 Description

Fixes setUser not working when the SDK is started on a background thread.

When calling SentrySDK.start() on a background thread, the SDK dispatches initialization work to the main thread. During this window, isEnabled returns false, causing setUser calls to be silently ignored. This PR introduces a pending task queue that stores these operations and executes them once the SDK is fully initialized.

💡 Motivation and Context

When users start the SDK on a background thread and immediately call setUser, there's a race condition where the main thread initialization hasn't completed yet. Previously, this would just log a fatal message and the user would never be set. Now, the operation is queued and executed once initialization completes.

Fixes #6872

💚 How did you test it?

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • [] I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
Cursor Bugbot found 1 potential issue for commit e438753

@itaybre itaybre added the ready-to-merge Use this label to trigger all PR workflows label Feb 4, 2026
@linear
Copy link

linear bot commented Feb 4, 2026

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • Add pending task queue for SDK initialization by itaybre in #7377

Internal Changes 🔧

  • Check AppKit linkage macOS by itaybre in #7343

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 6ba2d63

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.314%. Comparing base (ee272e8) to head (6ba2d63).
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #7377       +/-   ##
=============================================
+ Coverage   85.268%   85.314%   +0.045%     
=============================================
  Files          481       482        +1     
  Lines        28599     28634       +35     
  Branches     12443     12461       +18     
=============================================
+ Hits         24386     24429       +43     
+ Misses        4170      4162        -8     
  Partials        43        43               
Files with missing lines Coverage Δ
Sources/Sentry/SentrySDKInternal.m 85.000% <100.000%> (+0.492%) ⬆️
...ces/Swift/Core/Helper/SentryPendingTaskQueue.swift 100.000% <100.000%> (ø)
Sources/Swift/SentryDependencyContainer.swift 97.183% <100.000%> (+0.013%) ⬆️

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ee272e8...6ba2d63. Read the comment docs.

@itaybre itaybre marked this pull request as ready for review February 4, 2026 21:57
@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1197.24 ms 1213.81 ms 16.56 ms
Size 24.14 KiB 1.10 MiB 1.08 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
17b7e88 1223.04 ms 1253.48 ms 30.43 ms
29f846e 1210.51 ms 1238.66 ms 28.15 ms
d427374 1226.81 ms 1257.74 ms 30.94 ms
2f28bda 1221.21 ms 1255.09 ms 33.88 ms
a424cf3 1231.36 ms 1259.62 ms 28.25 ms
7e93d85 1205.28 ms 1243.71 ms 38.44 ms
86b8951 1211.20 ms 1247.92 ms 36.72 ms
2c4362a 1231.50 ms 1255.95 ms 24.45 ms
24ac927 1218.04 ms 1242.02 ms 23.98 ms
1357911 1224.57 ms 1261.00 ms 36.43 ms

App size

Revision Plain With Sentry Diff
17b7e88 24.14 KiB 1.06 MiB 1.04 MiB
29f846e 24.14 KiB 1.07 MiB 1.04 MiB
d427374 24.14 KiB 1.09 MiB 1.07 MiB
2f28bda 24.14 KiB 1.05 MiB 1.03 MiB
a424cf3 24.14 KiB 1.06 MiB 1.04 MiB
7e93d85 24.14 KiB 1.06 MiB 1.04 MiB
86b8951 24.14 KiB 1.08 MiB 1.06 MiB
2c4362a 24.14 KiB 1.07 MiB 1.04 MiB
24ac927 24.14 KiB 1.06 MiB 1.04 MiB
1357911 24.14 KiB 1.07 MiB 1.04 MiB

Previous results on branch: itaybrenner/cocoa-1010-setuser-not-work-after-upgade-to-8540

Startup times

Revision Plain With Sentry Diff
70747c5 1212.79 ms 1243.94 ms 31.15 ms

App size

Revision Plain With Sentry Diff
70747c5 24.14 KiB 1.10 MiB 1.08 MiB

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

// This handles the race condition when SDK is started on a background thread
// and methods like setUser are called before main thread initialization finishes.
// See https://github.com/getsentry/sentry-cocoa/issues/6872
[SentryDependencyContainer.sharedInstance.pendingTaskQueue executePendingTasks];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition causes incorrect ordering of setUser operations

Medium Severity

There's a race condition window between setCurrentHub (line 230) and executePendingTasks (line 236) where isEnabled returns true but pending tasks haven't executed yet. If setUser is called from another thread during this window, it calls the hub directly while older queued tasks are still pending. When executePendingTasks subsequently runs, it executes the older tasks, potentially overwriting the newer value with stale data. This can cause the wrong user to be set when multiple setUser calls happen around SDK initialization time.

Additional Locations (1)

Fix in Cursor Fix in Web

@itaybre itaybre marked this pull request as draft February 5, 2026 13:15
@philprime
Copy link
Member

While I see this is still a draft, I would like to raise two concerns/topics:

  1. While we advocate to initialize the SDK on the main thread, we do not prohibit doing it from a background thread. Now when users are moving SDK initialization to the background thread because the main thread is busy on launch, the SDK initialization waiting to dispatch initialization work to the main thread could further delay SDK initialization. Not sure yet if this applies but maybe worth investigating.
  2. We should consider adding a deduplication logic, so that identical tasks with side-effects are not run multiple times, e.g. if setUser is called once with a user and once without directly afterwards, we could skip the first call.

@itaybre
Copy link
Contributor Author

itaybre commented Feb 5, 2026

  1. We should consider adding a deduplication logic, so that identical tasks with side-effects are not run multiple times, e.g. if setUser is called once with a user and once without directly afterwards, we could skip the first call.

Yeah, that's why I moved it back to draft.

  1. While we advocate to initialize the SDK on the main thread, we do not prohibit doing it from a background thread. Now when users are moving SDK initialization to the background thread because the main thread is busy on launch, the SDK initialization waiting to dispatch initialization work to the main thread could further delay SDK initialization. Not sure yet if this applies but maybe worth investigating.

We can go down a rabbithole with enabling initializing the SDK from a background thread has 2 problems:

  • Stuff might not be tracked (like events or breadcrumbs)
  • Settings wont be set correctly

This fixed setting the user, but for example configuring the scope is harder to fix here.
Using the same strategy might make it asynchronous, while this is a callback I am not sure if users expect this.

I would recommend moving slowly and as needed

@itaybre itaybre marked this pull request as ready for review February 5, 2026 21:35
@philipphofmann
Copy link
Member

I’m not 100% sure this solution actually addresses the root cause. The core issue seems to be a race condition in SentrySDK.start when it’s called from a background thread: the method returns before the hub is fully set up.

I’m mostly brainstorming here, but I see a few possible options — none of them are perfect:

Option A: Install the hub immediately in SentrySDK.start (on the calling thread)

Set up a working hub right away, and delay enriching the scope (which currently happens on the main thread).

  • Pros: We at least have a usable hub immediately.
  • Cons: If captureMessage is called right after start, some scope data might be missing.

Option B: Block captures until scope enrichment completes

If someone calls captureMessage before the main-thread work finishes, we wait until it’s done.

  • Pros: Scope data is always correct.
  • Cons: Calling this from a background thread can suddenly block and slow things down; this mostly just shifts the cost and only helps a subset of users.

Option C: Block all SDK interactions until initialization fully completes

Allow start from a background thread, but make all subsequent SDK calls wait until the main-thread initialization finishes.

  • Pros: Clear and consistent behavior.
  • Cons: Adds implicit blocking and can negatively impact performance; not great ergonomics.

Option D: Disallow background-thread initialization

Explicitly require SentrySDK.start to be called on the main thread.

  • Pros: Simple and avoids the race entirely.
  • Cons: Not very flexible and likely frustrating for some users.

Option E: Provide a “SDK fully initialized” callback

Expose a callback that fires once all initialization (including main-thread work) is done.

  • Pros: Explicit and gives users control.
  • Cons: Requires users to know about and correctly use the callback.

So overall, I’m not fully convinced yet. The proposed solution seems to address only part of the problem and still introduces blocking in some scenarios. I think we need to be careful here, because all of these options come with tradeoffs, and none of them feel ideal.

@itaybre
Copy link
Contributor Author

itaybre commented Feb 6, 2026

I’m not 100% sure this solution actually addresses the root cause. The core issue seems to be a race condition in SentrySDK.start when it’s called from a background thread: the method returns before the hub is fully set up.

I’m mostly brainstorming here, but I see a few possible options — none of them are perfect:

Option A: Install the hub immediately in SentrySDK.start (on the calling thread)

Set up a working hub right away, and delay enriching the scope (which currently happens on the main thread).

  • Pros: We at least have a usable hub immediately.
  • Cons: If captureMessage is called right after start, some scope data might be missing.

Option B: Block captures until scope enrichment completes

If someone calls captureMessage before the main-thread work finishes, we wait until it’s done.

  • Pros: Scope data is always correct.
  • Cons: Calling this from a background thread can suddenly block and slow things down; this mostly just shifts the cost and only helps a subset of users.

Option C: Block all SDK interactions until initialization fully completes

Allow start from a background thread, but make all subsequent SDK calls wait until the main-thread initialization finishes.

  • Pros: Clear and consistent behavior.
  • Cons: Adds implicit blocking and can negatively impact performance; not great ergonomics.

Option D: Disallow background-thread initialization

Explicitly require SentrySDK.start to be called on the main thread.

  • Pros: Simple and avoids the race entirely.
  • Cons: Not very flexible and likely frustrating for some users.

Option E: Provide a “SDK fully initialized” callback

Expose a callback that fires once all initialization (including main-thread work) is done.

  • Pros: Explicit and gives users control.
  • Cons: Requires users to know about and correctly use the callback.

So overall, I’m not fully convinced yet. The proposed solution seems to address only part of the problem and still introduces blocking in some scenarios. I think we need to be careful here, because all of these options come with tradeoffs, and none of them feel ideal.

I don't believe there is going to be any perfect solution here.

Doing an async initialization will always introduce problems, this is why I went for a solution that was the most friendly with the user:

  • Allow setting the configuration while the SDK is not fully initialized
    • Apply it when the SDK starts
  • Events, breadcrumbs, messages, etc are time sensitive and require the full SDK initialized to make sense, this is why I left them as they are currently.

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

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

setUser not work after upgade to 8.54.0

3 participants