Skip to content

Commit 99a9d14

Browse files
jpnurmiclaude
andauthored
feat(native): offline caching (#1585)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7a2d3d7 commit 99a9d14

22 files changed

Lines changed: 360 additions & 108 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
**Features**:
66

77
- Symbolicate stack frames in crash daemon on Windows. ([#1595](https://github.com/getsentry/sentry-native/pull/1595))
8+
- Add offline caching support to the new experimental `native` backend. ([#1585](https://github.com/getsentry/sentry-native/pull/1585))
89

910
**Fixes**:
1011

12+
- Fix `cache_keep` to only cache envelopes when HTTP send fails, instead of unconditionally on restart. ([#1585](https://github.com/getsentry/sentry-native/pull/1585))
1113
- Fix external crash reporter to work with the new experimental `native` backend. ([#1589](https://github.com/getsentry/sentry-native/pull/1589))
1214

1315
## 0.13.3

examples/example.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,10 @@ main(int argc, char **argv)
940940
sentry_reinstall_backend();
941941
}
942942

943+
if (has_arg(argc, argv, "flush")) {
944+
sentry_flush(10000);
945+
}
946+
943947
if (has_arg(argc, argv, "sleep")) {
944948
sleep_s(10);
945949
}

include/sentry.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,12 +1476,14 @@ SENTRY_API int sentry_options_get_symbolize_stacktraces(
14761476
const sentry_options_t *opts);
14771477

14781478
/**
1479-
* Enables or disables storing envelopes in a persistent cache.
1479+
* Enables or disables storing envelopes that fail to send in a persistent
1480+
* cache.
14801481
*
1481-
* When enabled, envelopes are written to a `cache/` subdirectory within the
1482-
* database directory and retained regardless of send success or failure.
1483-
* The cache is cleared on startup based on the cache_max_items, cache_max_size,
1484-
* and cache_max_age options.
1482+
* When enabled, envelopes that fail to send are written to a `cache/`
1483+
* subdirectory within the database directory. The cache is cleared on startup
1484+
* based on the cache_max_items, cache_max_size, and cache_max_age options.
1485+
*
1486+
* Only applicable for HTTP transports.
14851487
*
14861488
* Disabled by default.
14871489
*/

src/backends/native/sentry_crash_context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ typedef struct {
260260
int crash_reporting_mode; // sentry_crash_reporting_mode_t
261261
bool debug_enabled; // Debug logging enabled in parent process
262262
bool attach_screenshot; // Screenshot attachment enabled in parent process
263+
bool cache_keep;
263264

264265
// Platform-specific crash context
265266
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)

src/backends/native/sentry_crash_daemon.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3040,6 +3040,7 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
30403040
// Use debug logging and screenshot settings from parent process
30413041
sentry_options_set_debug(options, ipc->shmem->debug_enabled);
30423042
options->attach_screenshot = ipc->shmem->attach_screenshot;
3043+
options->cache_keep = ipc->shmem->cache_keep;
30433044

30443045
// Set custom logger that writes to file
30453046
if (log_file) {

src/backends/sentry_backend_crashpad.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -565,11 +565,9 @@ process_completed_reports(
565565

566566
SENTRY_DEBUGF("caching %zu completed reports", reports.size());
567567

568-
sentry_path_t *cache_dir
569-
= sentry__path_join_str(options->database_path, "cache");
570-
if (!cache_dir || sentry__path_create_dir_all(cache_dir) != 0) {
568+
sentry_path_t *cache_dir = options->run->cache_path;
569+
if (sentry__path_create_dir_all(cache_dir) != 0) {
571570
SENTRY_WARN("failed to create cache dir");
572-
sentry__path_free(cache_dir);
573571
return;
574572
}
575573

@@ -593,8 +591,6 @@ process_completed_reports(
593591
sentry__path_free(out_path);
594592
sentry_envelope_free(envelope);
595593
}
596-
597-
sentry__path_free(cache_dir);
598594
}
599595

600596
static int

src/backends/sentry_backend_native.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ native_backend_startup(
176176
// Pass debug logging setting to daemon
177177
ctx->debug_enabled = options->debug;
178178
ctx->attach_screenshot = options->attach_screenshot;
179+
ctx->cache_keep = options->cache_keep;
179180

180181
// Set up event and breadcrumb paths
181182
sentry_path_t *run_path = options->run->run_path;

src/sentry_core.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,9 @@ sentry_init(sentry_options_t *options)
293293
}
294294

295295
if (options->cache_keep) {
296-
sentry__cleanup_cache(options);
296+
if (!sentry__transport_submit_cleanup(options->transport, options)) {
297+
sentry__cleanup_cache(options);
298+
}
297299
}
298300

299301
if (options->auto_session_tracking) {

src/sentry_database.c

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "sentry_json.h"
55
#include "sentry_options.h"
66
#include "sentry_session.h"
7+
#include "sentry_sync.h"
8+
#include "sentry_utils.h"
79
#include "sentry_uuid.h"
810
#include <errno.h>
911
#include <stdlib.h>
@@ -50,19 +52,32 @@ sentry__run_new(const sentry_path_t *database_path)
5052
return NULL;
5153
}
5254

55+
// `<db>/cache`
56+
sentry_path_t *cache_path = sentry__path_join_str(database_path, "cache");
57+
if (!cache_path) {
58+
sentry__path_free(run_path);
59+
sentry__path_free(lock_path);
60+
sentry__path_free(session_path);
61+
sentry__path_free(external_path);
62+
return NULL;
63+
}
64+
5365
sentry_run_t *run = SENTRY_MAKE(sentry_run_t);
5466
if (!run) {
5567
sentry__path_free(run_path);
5668
sentry__path_free(session_path);
5769
sentry__path_free(lock_path);
5870
sentry__path_free(external_path);
71+
sentry__path_free(cache_path);
5972
return NULL;
6073
}
6174

75+
run->refcount = 1;
6276
run->uuid = uuid;
6377
run->run_path = run_path;
6478
run->session_path = session_path;
6579
run->external_path = external_path;
80+
run->cache_path = cache_path;
6681
run->lock = sentry__filelock_new(lock_path);
6782
if (!run->lock) {
6883
goto error;
@@ -80,6 +95,15 @@ sentry__run_new(const sentry_path_t *database_path)
8095
return NULL;
8196
}
8297

98+
sentry_run_t *
99+
sentry__run_incref(sentry_run_t *run)
100+
{
101+
if (run) {
102+
sentry__atomic_fetch_and_add(&run->refcount, 1);
103+
}
104+
return run;
105+
}
106+
83107
void
84108
sentry__run_clean(sentry_run_t *run)
85109
{
@@ -90,12 +114,13 @@ sentry__run_clean(sentry_run_t *run)
90114
void
91115
sentry__run_free(sentry_run_t *run)
92116
{
93-
if (!run) {
117+
if (!run || sentry__atomic_fetch_and_add(&run->refcount, -1) != 1) {
94118
return;
95119
}
96120
sentry__path_free(run->run_path);
97121
sentry__path_free(run->session_path);
98122
sentry__path_free(run->external_path);
123+
sentry__path_free(run->cache_path);
99124
sentry__filelock_free(run->lock);
100125
sentry_free(run);
101126
}
@@ -151,6 +176,18 @@ sentry__run_write_external(
151176
return write_envelope(run->external_path, envelope);
152177
}
153178

179+
bool
180+
sentry__run_write_cache(
181+
const sentry_run_t *run, const sentry_envelope_t *envelope)
182+
{
183+
if (sentry__path_create_dir_all(run->cache_path) != 0) {
184+
SENTRY_ERRORF("mkdir failed: \"%s\"", run->cache_path->path);
185+
return false;
186+
}
187+
188+
return write_envelope(run->cache_path, envelope);
189+
}
190+
154191
bool
155192
sentry__run_write_session(
156193
const sentry_run_t *run, const sentry_session_t *session)
@@ -239,14 +276,6 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash)
239276
continue;
240277
}
241278

242-
sentry_path_t *cache_dir = NULL;
243-
if (options->cache_keep) {
244-
cache_dir = sentry__path_join_str(options->database_path, "cache");
245-
if (cache_dir) {
246-
sentry__path_create_dir_all(cache_dir);
247-
}
248-
}
249-
250279
sentry_pathiter_t *run_iter = sentry__path_iter_directory(run_dir);
251280
const sentry_path_t *file;
252281
while (run_iter && (file = sentry__pathiter_next(run_iter)) != NULL) {
@@ -291,25 +320,12 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash)
291320
} else if (sentry__path_ends_with(file, ".envelope")) {
292321
sentry_envelope_t *envelope = sentry__envelope_from_path(file);
293322
sentry__capture_envelope(options->transport, envelope);
294-
295-
if (cache_dir) {
296-
sentry_path_t *cached_file = sentry__path_join_str(
297-
cache_dir, sentry__path_filename(file));
298-
if (!cached_file
299-
|| sentry__path_rename(file, cached_file) != 0) {
300-
SENTRY_WARNF("failed to cache envelope \"%s\"",
301-
sentry__path_filename(file));
302-
}
303-
sentry__path_free(cached_file);
304-
continue;
305-
}
306323
}
307324

308325
sentry__path_remove(file);
309326
}
310327
sentry__pathiter_free(run_iter);
311328

312-
sentry__path_free(cache_dir);
313329
sentry__path_remove_all(run_dir);
314330
sentry__filelock_free(lock);
315331
}

src/sentry_database.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ typedef struct sentry_run_s {
1111
sentry_path_t *run_path;
1212
sentry_path_t *session_path;
1313
sentry_path_t *external_path;
14+
sentry_path_t *cache_path;
1415
sentry_filelock_t *lock;
16+
long refcount;
1517
} sentry_run_t;
1618

1719
/**
@@ -22,6 +24,11 @@ typedef struct sentry_run_s {
2224
*/
2325
sentry_run_t *sentry__run_new(const sentry_path_t *database_path);
2426

27+
/**
28+
* Increment the refcount and return the run pointer.
29+
*/
30+
sentry_run_t *sentry__run_incref(sentry_run_t *run);
31+
2532
/**
2633
* This will clean up all the files belonging to this run.
2734
*/
@@ -63,6 +70,13 @@ bool sentry__run_write_session(
6370
*/
6471
bool sentry__run_clear_session(const sentry_run_t *run);
6572

73+
/**
74+
* This will serialize and write the given envelope to disk into the cache
75+
* directory: `<database>/cache/<event-uuid>.envelope`
76+
*/
77+
bool sentry__run_write_cache(
78+
const sentry_run_t *run, const sentry_envelope_t *envelope);
79+
6680
/**
6781
* This function is essential to send crash reports from previous runs of the
6882
* program.

0 commit comments

Comments
 (0)