perf(core): Create outbox and cache dirs lazily instead of during init (JAVA-613)#5792
perf(core): Create outbox and cache dirs lazily instead of during init (JAVA-613)#5792runningcode wants to merge 4 commits into
Conversation
…t (JAVA-613) Sentry.initConfigurations created the outbox and cache directories synchronously on the init thread, which on Android is the main thread. Create each directory lazily in its consumer instead: the cache dir on the first envelope write (transport thread), and the outbox dir in the file-observer integration (executor thread) and before writing the startup-crash marker. The native SDK already creates the outbox dir itself during sentry_init, so NDK crash writes are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5b1a06b | 352.27 ms | 413.70 ms | 61.43 ms |
| 2124a46 | 319.19 ms | 415.04 ms | 95.85 ms |
| ad8da22 | 314.52 ms | 352.47 ms | 37.95 ms |
| d15471f | 304.55 ms | 408.43 ms | 103.87 ms |
| 0ee65e9 | 317.37 ms | 366.50 ms | 49.13 ms |
| 2195398 | 351.77 ms | 433.22 ms | 81.45 ms |
| ee747ae | 358.21 ms | 389.41 ms | 31.20 ms |
| abfcc92 | 309.54 ms | 380.32 ms | 70.78 ms |
| 8558cac | 306.16 ms | 355.24 ms | 49.09 ms |
| d15471f | 310.26 ms | 377.04 ms | 66.78 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5b1a06b | 0 B | 0 B | 0 B |
| 2124a46 | 1.58 MiB | 2.12 MiB | 551.51 KiB |
| ad8da22 | 1.58 MiB | 2.29 MiB | 719.83 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| 0ee65e9 | 0 B | 0 B | 0 B |
| 2195398 | 0 B | 0 B | 0 B |
| ee747ae | 1.58 MiB | 2.10 MiB | 530.95 KiB |
| abfcc92 | 1.58 MiB | 2.13 MiB | 557.31 KiB |
| 8558cac | 0 B | 0 B | 0 B |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
…ect (JAVA-613) Replace the duplicated "create the dir if it does not exist" idiom in the envelope cache, outbox file observer, and startup-crash-marker paths with a single LazyDirectory type that materializes the directory on first access. CacheStrategy now owns its directory as a LazyDirectory: write paths call getOrCreate(), while path-building and validity checks use getFile() so they do not create the directory as a side effect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Native needs to be merged first: getsentry/sentry-native#1889 |
Have the composition roots (EnvelopeCache.create and AndroidEnvelopeCache) build the LazyDirectory and pass it into CacheStrategy, so the cache no longer constructs its own directory collaborator from a path string. The public String constructor is kept and delegates, preserving binary compatibility. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a2a855b. Configure here.
| final String cacheDirPath = options.getCacheDirPath(); | ||
| if (cacheDirPath != null) { | ||
| final File cacheDir = new File(cacheDirPath); | ||
| cacheDir.mkdirs(); |
There was a problem hiding this comment.
Cache send errors on missing dir
Medium Severity
Removing init-time mkdirs() for the cache dir leaves it absent on fresh installs, but SendCachedEnvelopeFireAndForgetIntegration still processes that path at startup via DirectoryProcessor. When the dir does not exist, listFiles() returns null and logs an ERROR that the cache dir is not a directory—noise on every cold start without profiling (which would incidentally create the parent via profilingTracesDir.mkdirs()). Lazy creation only happens later in EnvelopeCache.storeInternal, so the startup read path was not covered.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a2a855b. Configure here.
| final @NotNull String path) { | ||
| // Materialize the outbox dir here (on the executor) so the observer can watch it for envelopes | ||
| // written by hybrid SDKs, instead of blocking Sentry.init on the mkdirs. | ||
| new LazyDirectory(path).getOrCreate(); |
There was a problem hiding this comment.
Outbox missing after init returns
Medium Severity
Outbox creation moved onto the executor inside startOutboxSender, so after Sentry.init returns the outbox may still be missing when NDK is disabled (native no longer materializes it during init). Hybrid SDKs that write envelopes to the outbox immediately after init can fail in that window, whereas init previously created the directory synchronously before returning.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a2a855b. Configure here.


📜 Description
Sentry.initConfigurationscreated the outbox and cache directories synchronously viamkdirs()on the init thread — which on Android is the main thread. This moves both off the synchronous init path by creating each directory lazily in its consumer:EnvelopeCache.storeInternal, so the first envelope write (on the transport thread) creates it. Covers envelopes, sessions, crash markers, and the Android ANR/tombstone markers, which all live undercacheDirPath.EnvelopeFileObserverIntegration.startOutboxSender(runs on the executor) beforestartWatching(), and before writing the startup-crash marker inAndroidEnvelopeCache. The native SDK already creates the outbox dir itself duringsentry_init, so NDK crash writes are unaffected.💡 Motivation and Context
Part of the "Reduce SDK init time [Android]" effort (JAVA-613). The
mkdirs()stat/create calls block the caller during init; every consumer of these dirs already runs on the executor or transport thread (or, for native, creates its own dir), so the directories can be created lazily off the main thread without changing behavior.Items 2 (
context.getCacheDir()stat) and 3 (profiling-configexists()/canRead()stat) from the issue were investigated and intentionally left as-is: the former is required synchronously to setcacheDirPathand the framework already guarantees the base dir, and the latter is the persisted gate for app-start profiling that must run at ContentProvider time.💚 How did you test it?
Added unit tests covering lazy creation for each path:
EnvelopeCacheTest: cache dir is created on first store when absent.AndroidEnvelopeCacheTest: outbox dir is created when writing the startup-crash marker and the dir does not exist yet.EnvelopeFileObserverIntegrationTest:registercreates the outbox dir when it does not exist yet.SentryTest: updated the three init tests to assert the dirs are no longer created during initialization.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Verify the init-time improvement as a batch via Perfetto/macrobenchmark (individually below noise).