From 42dec4e38a4907595afec295b807c95d244f587b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 29 Jul 2025 13:05:15 +0200 Subject: [PATCH 1/3] Add rules file for documenting SDK offline behaviour --- .cursor/rules/offline.mdc | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .cursor/rules/offline.mdc diff --git a/.cursor/rules/offline.mdc b/.cursor/rules/offline.mdc new file mode 100644 index 00000000000..5a7f87805e0 --- /dev/null +++ b/.cursor/rules/offline.mdc @@ -0,0 +1,77 @@ + +# Java SDK Offline behaviour + +By default offline caching is enabled for Android but disabled for JVM. +It can be enabled by setting SentryOptions.cacheDirPath. + +For Android, AndroidEnvelopeCache is used. For JVM, if cache path has been configured, EnvelopeCache will be used. + +Any error, event, transaction, profile, replay etc. is turned into an envelope and then sent into ITransport.send. +The default implementation is AsyncHttpTransport. + +If an envelope is dropped due to rate limit and has previously been cached (Cached hint) it will be discarded from the IEnvelopeCache. + +AsyncHttpTransport.send will enqueue an AsyncHttpTransport.EnvelopeSender task onto an executor. + +Any envelope that doesn't have the Cached hint will be stored in IEnvelopeCache by the EventSender task. Previously cached envelopes (Cached hint) will have a noop cache passed to AsyncHttpTransport.EnvelopeSender and thus not cache again. It is also possible cache is disabled in general. + +An envelope being sent directly from SDK API like Sentry.captureException will not have the Retryable hint. + +In case the SDK is offline, it'll mark the envelope to be retried if it has the Retryable hint. +If the envelope is not retryable and hasn't been sent to offline cache, it's recorded as lost in a client report. + +In case the envelope can't be sent due to an error or network connection problems it'll be marked for retry if it has the Retryable hint. +If it's not retryable and hasn't been cached, it's recorded as lost in a client report. + +In case the envelope is sent successfully, it'll be discarded from cache. + +The SDK has multiple mechanisms to deal with envelopes on disk. +- OutboxSender: Sends events coming from other SDKs like NDK that wrote them to disk. +- io.sentry.EnvelopeSender: This is the offline cache. + +Both of these are set up through an integration (SendCachedEnvelopeIntegration) which is configured to use SendFireAndForgetOutboxSender or SendFireAndForgetEnvelopeSender. + +io.sentry.EnvelopeSender is able to pick up files in the cache directory and send them. +It will trigger sending envelopes in cache dir on init and when the connection status changes (e.g. the SDK comes back online, meaning it has Internet connection again). + +## When Envelope Files Are Removed From Cache + +Envelope files are removed from the cache directory in the following scenarios: + +### 1. Successful Send to Sentry Server +When `AsyncHttpTransport` successfully sends an envelope to the Sentry server, it calls `envelopeCache.discard(envelope)` to remove the cached file. This happens in `AsyncHttpTransport.EnvelopeSender.flush()` when `result.isSuccess()` is true. + +### 2. Rate Limited Previously Cached Envelopes +If an envelope is dropped due to rate limiting **and** has previously been cached (indicated by the `Cached` hint), it gets discarded immediately via `envelopeCache.discard(envelope)` in `AsyncHttpTransport.send()`. + +### 3. Offline Cache Processing (EnvelopeSender) +When the SDK processes cached envelope files from disk (via `EnvelopeSender`), files are deleted after processing **unless** they are marked for retry. In `EnvelopeSender.processFile()`, the file is deleted with `safeDelete(file)` if `!retryable.isRetry()`. + +### 4. Session File Management +Session-related files (session.json, previous_session.json) are removed during session lifecycle events like session start/end and abnormal exits. + +## Retry Mechanism + +**Important**: The SDK does NOT implement a traditional "max retry count" mechanism. Instead: + +### Infinite Retry Approach +- **Retryable envelopes**: Stay in cache indefinitely and are retried when conditions improve (network connectivity restored, rate limits expire, etc.) +- **Non-retryable envelopes**: If they fail to send, they're immediately recorded as lost (not cached for retry) + +### When Envelopes Are Permanently Lost (Not Due to Retry Limits) + +1. **Queue Overflow**: When the transport executor queue is full - recorded as `DiscardReason.QUEUE_OVERFLOW` + +2. **Network Errors (Non-Retryable)**: When an envelope isn't marked as retryable and fails due to network issues - recorded as `DiscardReason.NETWORK_ERROR` + +3. **Rate Limiting**: When envelope items are dropped due to active rate limits - recorded as `DiscardReason.RATELIMIT_BACKOFF` + +### Cache Processing Triggers +Cached envelopes are processed when: +- Network connectivity is restored (via connection status observer) +- SDK initialization occurs +- Rate limits expire +- Manual flush operations + +### File Deletion Implementation +The actual file deletion is handled by `EnvelopeCache.discard()` which calls `envelopeFile.delete()` and logs errors if deletion fails. From 55fbf7d1013a4ed877693bd47e3eef017b5a7a63 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 31 Jul 2025 13:01:47 +0200 Subject: [PATCH 2/3] add latest learnings --- .cursor/rules/offline.mdc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.cursor/rules/offline.mdc b/.cursor/rules/offline.mdc index 5a7f87805e0..9396268e74f 100644 --- a/.cursor/rules/offline.mdc +++ b/.cursor/rules/offline.mdc @@ -41,8 +41,9 @@ Envelope files are removed from the cache directory in the following scenarios: ### 1. Successful Send to Sentry Server When `AsyncHttpTransport` successfully sends an envelope to the Sentry server, it calls `envelopeCache.discard(envelope)` to remove the cached file. This happens in `AsyncHttpTransport.EnvelopeSender.flush()` when `result.isSuccess()` is true. -### 2. Rate Limited Previously Cached Envelopes +### 2. Rate Limited Previously Cached Envelopes If an envelope is dropped due to rate limiting **and** has previously been cached (indicated by the `Cached` hint), it gets discarded immediately via `envelopeCache.discard(envelope)` in `AsyncHttpTransport.send()`. +In this case the discarded envelope is recorded as lost in client reports. ### 3. Offline Cache Processing (EnvelopeSender) When the SDK processes cached envelope files from disk (via `EnvelopeSender`), files are deleted after processing **unless** they are marked for retry. In `EnvelopeSender.processFile()`, the file is deleted with `safeDelete(file)` if `!retryable.isRetry()`. @@ -50,6 +51,10 @@ When the SDK processes cached envelope files from disk (via `EnvelopeSender`), f ### 4. Session File Management Session-related files (session.json, previous_session.json) are removed during session lifecycle events like session start/end and abnormal exits. +### 5. Cache rotation +If the number of files in the cache directory has reached the configured limit (SentryOptions.maxCacheItems), the oldest file will be deleted to make room. +This happens in `CacheStrategy.rotateCacheIfNeeded`. The deleted envelope will be recorded as lost in client reports. + ## Retry Mechanism **Important**: The SDK does NOT implement a traditional "max retry count" mechanism. Instead: @@ -66,6 +71,8 @@ Session-related files (session.json, previous_session.json) are removed during s 3. **Rate Limiting**: When envelope items are dropped due to active rate limits - recorded as `DiscardReason.RATELIMIT_BACKOFF` +4. **Cache Overflow**: When the cache directory has reached maxCacheItems, old files are deleted - recorded as `DiscardReason.CACHE_OVERFLOW` + ### Cache Processing Triggers Cached envelopes are processed when: - Network connectivity is restored (via connection status observer) From 1e939de97fd4e6ddbd7e138e7e3a24147d823d9c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 1 Aug 2025 11:05:32 +0200 Subject: [PATCH 3/3] Update .cursor/rules/offline.mdc Co-authored-by: Markus Hintersteiner --- .cursor/rules/offline.mdc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.cursor/rules/offline.mdc b/.cursor/rules/offline.mdc index 9396268e74f..afb69233f38 100644 --- a/.cursor/rules/offline.mdc +++ b/.cursor/rules/offline.mdc @@ -1,4 +1,7 @@ - +--- +alwaysApply: true +description: Java SDK Offline behaviour +--- # Java SDK Offline behaviour By default offline caching is enabled for Android but disabled for JVM.