Skip to content

Commit 300ce44

Browse files
jpnurmiclaude
andcommitted
ref: move user_consent to sentry_run_t for thread-safe access
Move require_user_consent and user_consent from sentry_options_t to sentry_run_t as atomic longs. Add two helpers on sentry_run_t: * sentry__run_should_skip_upload() — lock-free consent check the batcher (and future workers) can call directly through their refcounted run pointer, replacing the fragile raw long* into options. * sentry__run_load_user_consent() — loads the persisted consent file (`<database>/user-consent`) into the run using an atomic store, so callers outside the options lock (e.g. future crash-time consent refreshes) can update it safely while worker threads read it. Pure refactor: only existing call sites (batcher) are updated. No new consent checks or daemon changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2a6ec9d commit 300ce44

File tree

8 files changed

+62
-36
lines changed

8 files changed

+62
-36
lines changed

src/sentry_batcher.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,7 @@ sentry__batcher_flush(sentry_batcher_t *batcher, bool crash_safe)
224224
// crash
225225
sentry__run_write_envelope(batcher->run, envelope);
226226
sentry_envelope_free(envelope);
227-
} else if (!batcher->user_consent
228-
|| sentry__atomic_fetch(batcher->user_consent)
229-
== SENTRY_USER_CONSENT_GIVEN) {
227+
} else if (!sentry__run_should_skip_upload(batcher->run)) {
230228
// Normal operation: use transport for HTTP transmission
231229
sentry__transport_send_envelope(batcher->transport, envelope);
232230
} else {
@@ -374,12 +372,10 @@ sentry__batcher_startup(
374372
{
375373
// dsn is incref'd because release() decref's it and may outlive options.
376374
batcher->dsn = sentry__dsn_incref(options->dsn);
377-
// transport, run, and user_consent are non-owning refs, safe because they
375+
// transport and run are non-owning refs, safe because they
378376
// are only accessed in flush() which is bound by the options lifetime.
379377
batcher->transport = options->transport;
380378
batcher->run = options->run;
381-
batcher->user_consent
382-
= options->require_user_consent ? (long *)&options->user_consent : NULL;
383379

384380
// Mark thread as starting before actually spawning so thread can transition
385381
// to RUNNING. This prevents shutdown from thinking the thread was never

src/sentry_batcher.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ typedef struct {
4949
sentry_dsn_t *dsn;
5050
sentry_transport_t *transport;
5151
sentry_run_t *run;
52-
long *user_consent; // (atomic) NULL if consent not required
5352
} sentry_batcher_t;
5453

5554
typedef struct {

src/sentry_core.c

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -71,35 +71,12 @@ sentry__options_unlock(void)
7171
sentry__mutex_unlock(&g_options_lock);
7272
}
7373

74-
static void
75-
load_user_consent(sentry_options_t *opts)
76-
{
77-
sentry_path_t *consent_path
78-
= sentry__path_join_str(opts->database_path, "user-consent");
79-
char *contents = sentry__path_read_to_buffer(consent_path, NULL);
80-
sentry__path_free(consent_path);
81-
switch (contents ? contents[0] : 0) {
82-
case '1':
83-
opts->user_consent = SENTRY_USER_CONSENT_GIVEN;
84-
break;
85-
case '0':
86-
opts->user_consent = SENTRY_USER_CONSENT_REVOKED;
87-
break;
88-
default:
89-
opts->user_consent = SENTRY_USER_CONSENT_UNKNOWN;
90-
break;
91-
}
92-
sentry_free(contents);
93-
}
94-
9574
bool
9675
sentry__should_skip_upload(void)
9776
{
9877
bool skip = true;
9978
SENTRY_WITH_OPTIONS (options) {
100-
skip = options->require_user_consent
101-
&& sentry__atomic_fetch((long *)&options->user_consent)
102-
!= SENTRY_USER_CONSENT_GIVEN;
79+
skip = sentry__run_should_skip_upload(options->run);
10380
}
10481
return skip;
10582
}
@@ -207,8 +184,9 @@ sentry_init(sentry_options_t *options)
207184
SENTRY_WARN("failed to initialize run directory");
208185
goto fail;
209186
}
187+
options->run->require_user_consent = options->require_user_consent;
210188

211-
load_user_consent(options);
189+
sentry__run_load_user_consent(options->run, options->database_path);
212190

213191
if (!options->dsn || !options->dsn->is_valid) {
214192
const char *raw_dsn = sentry_options_get_dsn(options);
@@ -437,7 +415,7 @@ static void
437415
set_user_consent(sentry_user_consent_t new_val)
438416
{
439417
SENTRY_WITH_OPTIONS (options) {
440-
if (sentry__atomic_store((long *)&options->user_consent, new_val)
418+
if (sentry__atomic_store(&options->run->user_consent, new_val)
441419
!= new_val) {
442420
if (options->backend
443421
&& options->backend->user_consent_changed_func) {
@@ -486,7 +464,7 @@ sentry_user_consent_get(void)
486464
sentry_user_consent_t rv = SENTRY_USER_CONSENT_UNKNOWN;
487465
SENTRY_WITH_OPTIONS (options) {
488466
rv = (sentry_user_consent_t)(int)sentry__atomic_fetch(
489-
(long *)&options->user_consent);
467+
&options->run->user_consent);
490468
}
491469
return rv;
492470
}

src/sentry_core.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
/**
3535
* This function will check the user consent, and return `true` if uploads
3636
* should *not* be sent to the sentry server, and be discarded instead.
37+
*
38+
* Note: This function acquires the options lock internally. Use
39+
* `sentry__run_should_skip_upload` from worker threads that may run while
40+
* the options are locked during SDK shutdown.
3741
*/
3842
bool sentry__should_skip_upload(void);
3943

src/sentry_database.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ sentry__run_new(const sentry_path_t *database_path)
7474
}
7575

7676
run->refcount = 1;
77+
run->require_user_consent = 0;
78+
run->user_consent = SENTRY_USER_CONSENT_UNKNOWN;
7779
run->uuid = uuid;
7880
run->run_path = run_path;
7981
run->session_path = session_path;
@@ -96,6 +98,38 @@ sentry__run_new(const sentry_path_t *database_path)
9698
return NULL;
9799
}
98100

101+
bool
102+
sentry__run_should_skip_upload(sentry_run_t *run)
103+
{
104+
return sentry__atomic_fetch(&run->require_user_consent)
105+
&& (sentry__atomic_fetch(&run->user_consent)
106+
!= SENTRY_USER_CONSENT_GIVEN);
107+
}
108+
109+
void
110+
sentry__run_load_user_consent(
111+
sentry_run_t *run, const sentry_path_t *database_path)
112+
{
113+
sentry_path_t *consent_path
114+
= sentry__path_join_str(database_path, "user-consent");
115+
char *contents = sentry__path_read_to_buffer(consent_path, NULL);
116+
sentry__path_free(consent_path);
117+
long new_consent;
118+
switch (contents ? contents[0] : 0) {
119+
case '1':
120+
new_consent = SENTRY_USER_CONSENT_GIVEN;
121+
break;
122+
case '0':
123+
new_consent = SENTRY_USER_CONSENT_REVOKED;
124+
break;
125+
default:
126+
new_consent = SENTRY_USER_CONSENT_UNKNOWN;
127+
break;
128+
}
129+
sentry__atomic_store(&run->user_consent, new_consent);
130+
sentry_free(contents);
131+
}
132+
99133
sentry_run_t *
100134
sentry__run_incref(sentry_run_t *run)
101135
{

src/sentry_database.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,25 @@ typedef struct sentry_run_s {
1414
sentry_path_t *cache_path;
1515
sentry_filelock_t *lock;
1616
long refcount;
17+
long require_user_consent; // (atomic) bool
18+
long user_consent; // (atomic) sentry_user_consent_t
1719
} sentry_run_t;
1820

21+
/**
22+
* This function will check the user consent, and return `true` if uploads
23+
* should *not* be sent to the sentry server, and be discarded instead.
24+
*
25+
* This is a lock-free variant of `sentry__should_skip_upload`, safe to call
26+
* from worker threads while the options are locked during SDK shutdown.
27+
*/
28+
bool sentry__run_should_skip_upload(sentry_run_t *run);
29+
30+
/**
31+
* Loads the persisted user consent (`<database>/user-consent`) into the run.
32+
*/
33+
void sentry__run_load_user_consent(
34+
sentry_run_t *run, const sentry_path_t *database_path);
35+
1936
/**
2037
* This creates a new application run including its associated directory and
2138
* lockfile:

src/sentry_options.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ sentry_options_new(void)
4545
}
4646
sentry_options_set_sdk_name(opts, SENTRY_SDK_NAME);
4747
opts->max_breadcrumbs = SENTRY_BREADCRUMBS_MAX;
48-
opts->user_consent = SENTRY_USER_CONSENT_UNKNOWN;
4948
opts->auto_session_tracking = true;
5049
opts->system_crash_reporter_enabled = false;
5150
opts->attach_screenshot = false;

src/sentry_options.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ struct sentry_options_s {
8888
struct sentry_backend_s *backend;
8989
sentry_session_t *session;
9090

91-
long user_consent;
9291
long refcount;
9392
uint64_t shutdown_timeout;
9493
sentry_handler_strategy_t handler_strategy;

0 commit comments

Comments
 (0)