Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**Features**:

- Auto-populate `event.user.id` with a persistent per-installation UUID when no explicit user ID is set. ([#1661](https://github.com/getsentry/sentry-native/pull/1661))
- Add cache keep modes, including `SENTRY_CACHE_KEEP_ALWAYS` to cache envelopes regardless of upload result. ([#1707](https://github.com/getsentry/sentry-native/pull/1707))
- Crashpad: add error log for oversized envelopes (HTTP 413 Content Too Large). ([#1706](https://github.com/getsentry/sentry-native/pull/1706), [crashpad#155](https://github.com/getsentry/crashpad/pull/155))

**Fixes**:
Expand Down
8 changes: 7 additions & 1 deletion examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -723,12 +723,18 @@ main(int argc, char **argv)
if (has_arg(argc, argv, "require-user-consent")) {
sentry_options_set_require_user_consent(options, true);
}
if (has_arg(argc, argv, "cache-keep")) {
if (has_arg(argc, argv, "cache-keep")
|| has_arg(argc, argv, "cache-keep-always")) {
// true corresponds to SENTRY_CACHE_KEEP_OFFLINE for backwards
// compatibility with older SDK versions that don't have the enum
sentry_options_set_cache_keep(options, true);
sentry_options_set_cache_max_size(options, 16 * 1024 * 1024); // 16 MB
sentry_options_set_cache_max_age(options, 5 * 24 * 60 * 60); // 5 days
sentry_options_set_cache_max_items(options, 5);
}
if (has_arg(argc, argv, "cache-keep-always")) {
sentry_options_set_cache_keep(options, SENTRY_CACHE_KEEP_ALWAYS);
}
if (has_arg(argc, argv, "http-retry")) {
sentry_options_set_http_retry(options, true);
}
Expand Down
36 changes: 28 additions & 8 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,28 @@ typedef enum {
SENTRY_CRASH_REPORTING_MODE_NATIVE_WITH_MINIDUMP = 2,
} sentry_crash_reporting_mode_t;

/**
* Controls if and when envelopes are kept in the persistent cache.
*/
typedef enum {
/** Do not keep envelopes in the persistent cache. */
SENTRY_CACHE_KEEP_NONE = 0,

/**
* Envelopes that cannot be uploaded immediately are written to a `cache/`
* subdirectory within the database directory. This includes network
* failures and envelopes captured while user consent is revoked.
*/
SENTRY_CACHE_KEEP_OFFLINE = 1,

/**
* Envelopes are written to the cache regardless of the upload result. Kept
* entries use non-retry filenames and are not picked up by the
* retry queue.
*/
SENTRY_CACHE_KEEP_ALWAYS = 2,
} sentry_cache_keep_t;

/**
* Creates a new options struct.
* Can be freed with `sentry_options_free`.
Expand Down Expand Up @@ -1509,23 +1531,21 @@ SENTRY_API int sentry_options_get_symbolize_stacktraces(
const sentry_options_t *opts);

/**
* Enables or disables storing envelopes that fail to send in a persistent
* cache.
* Configures if and when envelopes are kept in a persistent cache.
*
* When enabled, envelopes that fail to send are written to a `cache/`
* subdirectory within the database directory. The cache is cleared on startup
* based on the cache_max_items, cache_max_size, and cache_max_age options.
* Kept envelopes are written to a `cache/` subdirectory within the database
* directory. The cache is cleared on startup based on the cache_max_items,
* cache_max_size, and cache_max_age options.
*
* When combined with `sentry_options_set_require_user_consent`, envelopes
* captured while consent is revoked are also written to the cache. With
* `http_retry` enabled, they are sent once consent is given.
*
* Only applicable for HTTP transports.
*
* Disabled by default.
* `SENTRY_CACHE_KEEP_NONE` by default.
*/
SENTRY_API void sentry_options_set_cache_keep(
sentry_options_t *opts, int enabled);
SENTRY_API void sentry_options_set_cache_keep(sentry_options_t *opts, int mode);

/**
* Sets the maximum number of items in the cache directory.
Expand Down
3 changes: 2 additions & 1 deletion src/backends/native/sentry_crash_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,10 @@ typedef struct {
int crash_reporting_mode; // sentry_crash_reporting_mode_t
bool debug_enabled; // Debug logging enabled in parent process
bool attach_screenshot; // Screenshot attachment enabled in parent process
bool cache_keep;
int cache_keep; // sentry_cache_keep_t
bool require_user_consent;
bool enable_large_attachments;
bool http_retry;
uint64_t shutdown_timeout;

// Atomic user consent (sentry_user_consent_t), updated whenever user
Expand Down
6 changes: 5 additions & 1 deletion src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -3289,7 +3289,7 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
// Use debug logging and screenshot settings from parent process
sentry_options_set_debug(options, ipc->shmem->debug_enabled);
options->attach_screenshot = ipc->shmem->attach_screenshot;
options->cache_keep = ipc->shmem->cache_keep;
options->cache_keep = (sentry_cache_keep_t)ipc->shmem->cache_keep;
options->enable_large_attachments = ipc->shmem->enable_large_attachments;
options->http_retry = false;
options->shutdown_timeout = ipc->shmem->shutdown_timeout;
Expand Down Expand Up @@ -3351,6 +3351,10 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
if (options->transport) {
SENTRY_DEBUG("Starting transport");
sentry__transport_startup(options->transport, options);
// Set http_retry after transport startup to keep daemon-side retry
// polling disabled, while letting capture cache consent-revoked
// envelopes in retry format for the app to send on restart.
options->http_retry = ipc->shmem->http_retry;
} else {
SENTRY_WARN("No transport available");
}
Expand Down
3 changes: 2 additions & 1 deletion src/backends/sentry_backend_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ native_backend_startup(
// Pass debug logging setting to daemon
ctx->debug_enabled = options->debug;
ctx->attach_screenshot = options->attach_screenshot;
ctx->cache_keep = options->cache_keep;
ctx->cache_keep = (int)options->cache_keep;
ctx->require_user_consent = options->require_user_consent;
ctx->enable_large_attachments = options->enable_large_attachments;
ctx->http_retry = options->http_retry;
ctx->shutdown_timeout = options->shutdown_timeout;
sentry__atomic_store(
&ctx->user_consent, sentry__atomic_fetch(&options->run->user_consent));
Expand Down
7 changes: 6 additions & 1 deletion src/sentry_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,8 @@ sentry__capture_envelope(sentry_transport_t *transport,
}
bool cached = false;
if (options->cache_keep || options->http_retry) {
cached = sentry__run_write_cache(options->run, envelope, 0);
int retry_count = options->http_retry ? 0 : -1;
cached = sentry__run_write_cache(options->run, envelope, retry_count);
Comment thread
jpnurmi marked this conversation as resolved.
if (cached && !sentry__run_should_skip_upload(options->run)) {
// consent given meanwhile -> trigger retry to avoid waiting
// until the next retry poll
Expand Down Expand Up @@ -1683,6 +1684,10 @@ sentry__launch_external_crash_reporter(
return false;
}

if (options->cache_keep == SENTRY_CACHE_KEEP_ALWAYS) {
sentry__run_write_cache(options->run, envelope, -1);
Comment thread
JoshuaMoelans marked this conversation as resolved.
}

sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope);
char *envelope_filename = sentry__uuid_as_filename(&event_id, ".envelope");
if (!envelope_filename) {
Expand Down
11 changes: 8 additions & 3 deletions src/sentry_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ sentry_options_new(void)
opts->crashpad_limit_stack_capture_to_sp = false;
opts->enable_metrics = true;
opts->enable_logs = true;
opts->cache_keep = false;
opts->cache_keep = SENTRY_CACHE_KEEP_NONE;
opts->cache_max_age = 0;
opts->cache_max_size = 0;
opts->cache_max_items = 30;
Expand Down Expand Up @@ -510,9 +510,14 @@ sentry_options_get_symbolize_stacktraces(const sentry_options_t *opts)
}

void
sentry_options_set_cache_keep(sentry_options_t *opts, int enabled)
sentry_options_set_cache_keep(sentry_options_t *opts, int mode)
{
opts->cache_keep = !!enabled;
if (mode < SENTRY_CACHE_KEEP_NONE) {
mode = SENTRY_CACHE_KEEP_OFFLINE;
Comment thread
jpnurmi marked this conversation as resolved.
} else if (mode > SENTRY_CACHE_KEEP_ALWAYS) {
mode = SENTRY_CACHE_KEEP_ALWAYS;
}
opts->cache_keep = (sentry_cache_keep_t)mode;
}

void
Expand Down
2 changes: 1 addition & 1 deletion src/sentry_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct sentry_options_s {
bool enable_logging_when_crashed;
bool propagate_traceparent;
bool crashpad_limit_stack_capture_to_sp;
bool cache_keep;
sentry_cache_keep_t cache_keep;

time_t cache_max_age;
size_t cache_max_size;
Expand Down
26 changes: 22 additions & 4 deletions src/sentry_retry.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ typedef enum {

struct sentry_retry_s {
sentry_run_t *run;
bool cache_keep;
sentry_cache_keep_t cache_keep;
uint64_t startup_time;
volatile long state;
volatile long scheduled;
Expand Down Expand Up @@ -92,7 +92,8 @@ compare_retry_items(const void *a, const void *b)
}

static bool
handle_result(sentry_retry_t *retry, const retry_item_t *item, int status_code)
handle_result(sentry_retry_t *retry, const retry_item_t *item,
const sentry_envelope_t *envelope, int status_code)
{
// Only network failures (status_code < 0) trigger retries. HTTP responses
// including 5xx (500, 502, 503, 504) are discarded:
Expand Down Expand Up @@ -127,13 +128,28 @@ handle_result(sentry_retry_t *retry, const retry_item_t *item, int status_code)

// cache on last attempt
if (exhausted && retry->cache_keep && status_code < 0) {
if (retry->cache_keep == SENTRY_CACHE_KEEP_ALWAYS) {
sentry_path_t *cached_path
= sentry__run_make_cache_path(retry->run, 0, -1, item->uuid);
bool cached = cached_path && sentry__path_is_file(cached_path);
sentry__path_free(cached_path);
if (cached) {
sentry__path_remove(item->path);
return false;
}
}
if (!sentry__run_move_cache(retry->run, item->path, -1)) {
sentry__cache_remove_envelope(item->path);
}
return false;
}

sentry__cache_remove_envelope(item->path);
if (retry->cache_keep == SENTRY_CACHE_KEEP_ALWAYS
&& sentry__run_write_cache(retry->run, envelope, -1)) {
sentry__path_remove(item->path);
} else {
sentry__cache_remove_envelope(item->path);
}
Comment thread
sentry[bot] marked this conversation as resolved.
return false;
}

Expand Down Expand Up @@ -216,8 +232,10 @@ sentry__retry_send(sentry_retry_t *retry, uint64_t before,
SENTRY_DEBUGF("retrying envelope (%d/%d)", items[i].count + 1,
SENTRY_RETRY_ATTEMPTS);
int status_code = send_cb(envelope, data);
bool keep_retry
= handle_result(retry, &items[i], envelope, status_code);
sentry_envelope_free(envelope);
if (!handle_result(retry, &items[i], status_code)) {
if (!keep_retry) {
total--;
}
// stop on network failure to avoid wasting time on a dead
Expand Down
12 changes: 8 additions & 4 deletions src/transports/sentry_http_transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ typedef struct {
sentry_http_send_func_t send_func;
void (*shutdown_client)(void *client);
sentry_retry_t *retry;
bool cache_keep;
sentry_cache_keep_t cache_keep;
sentry_run_t *run;
bool send_client_reports;
} http_transport_state_t;
Expand Down Expand Up @@ -635,7 +635,7 @@ retry_send_cb(sentry_envelope_t *envelope, void *_state)
bool reported = add_client_report(envelope, state, &report);
sentry_value_t ref_paths = collect_attachment_refs(envelope);
int status_code = http_send_envelope(envelope, state);
if (status_code < 0) {
if (status_code < 0 || state->cache_keep == SENTRY_CACHE_KEEP_ALWAYS) {
prune_attachment_refs(state->run, ref_paths, envelope);
} else {
prune_attachment_refs(state->run, ref_paths, NULL);
Expand Down Expand Up @@ -688,8 +688,8 @@ http_send_task(void *_envelope, void *_state)
sentry_value_t ref_paths = collect_attachment_refs(envelope);
int status_code = http_send_envelope(envelope, state);

const sentry_envelope_t *ref_owner = NULL;
if (status_code < 0) {
const sentry_envelope_t *ref_owner = NULL;
if (sentry_value_get_length(ref_paths) > 0
&& status_code == RESULT_SHUTDOWN
&& sentry__run_write_envelope(state->run, envelope)) {
Expand All @@ -711,7 +711,11 @@ http_send_task(void *_envelope, void *_state)
}
prune_attachment_refs(state->run, ref_paths, ref_owner);
} else {
prune_attachment_refs(state->run, ref_paths, NULL);
if (state->cache_keep == SENTRY_CACHE_KEEP_ALWAYS
&& sentry__run_write_cache(state->run, envelope, -1)) {
ref_owner = envelope;
}
prune_attachment_refs(state->run, ref_paths, ref_owner);
}
sentry_value_decref(ref_paths);

Expand Down
43 changes: 34 additions & 9 deletions tests/test_integration_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import time
import pytest

from . import run
from . import make_dsn, run
from .conditions import has_breakpad, has_files, has_http, is_qemu

pytestmark = [
Expand All @@ -11,7 +11,14 @@
]


@pytest.mark.parametrize("cache_keep", [True, False])
@pytest.mark.parametrize(
"cache_args,expect_cache",
[
([], False),
(["cache-keep"], True),
(["cache-keep-always"], True),
],
)
@pytest.mark.parametrize(
"backend",
[
Expand All @@ -24,7 +31,7 @@
),
],
)
def test_cache_keep(cmake, backend, cache_keep, unreachable_dsn):
def test_cache_keep(cmake, backend, cache_args, expect_cache, unreachable_dsn):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": backend})
cache_dir = tmp_path.joinpath(".sentry-native/cache")
env = dict(os.environ, SENTRY_DSN=unreachable_dsn)
Expand All @@ -33,8 +40,7 @@ def test_cache_keep(cmake, backend, cache_keep, unreachable_dsn):
run(
tmp_path,
"sentry_example",
["log", "no-http-retry", "flush", "crash"]
+ (["cache-keep"] if cache_keep else []),
["log", "no-http-retry", "flush", "crash"] + cache_args,
expect_failure=True,
env=env,
)
Expand All @@ -45,13 +51,12 @@ def test_cache_keep(cmake, backend, cache_keep, unreachable_dsn):
run(
tmp_path,
"sentry_example",
["log", "no-http-retry", "flush", "no-setup"]
+ (["cache-keep"] if cache_keep else []),
["log", "no-http-retry", "flush", "no-setup"] + cache_args,
env=env,
)

assert cache_dir.exists() or cache_keep is False
if cache_keep:
assert cache_dir.exists() or expect_cache is False
if expect_cache:
cache_files = list(cache_dir.glob("*.envelope"))
assert len(cache_files) == 1
if backend != "inproc":
Expand All @@ -60,6 +65,26 @@ def test_cache_keep(cmake, backend, cache_keep, unreachable_dsn):
assert cache_files[0].stem == dmp_files[0].stem


def test_cache_keep_always(cmake, httpserver):
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "inproc"})
cache_dir = tmp_path.joinpath(".sentry-native/cache")
env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver))

httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK")
with httpserver.wait(timeout=10) as waiting:
run(
tmp_path,
"sentry_example",
["log", "cache-keep-always", "flush", "capture-event"],
env=env,
)
assert waiting.result

cache_files = list(cache_dir.glob("*.envelope"))
assert len(cache_files) == 1
assert len(cache_files[0].stem) == 36


@pytest.mark.parametrize(
"backend",
[
Expand Down
Loading
Loading