Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

## Unreleased

**Features**:

- Added `sentry_app_hang_pause` to allow pausing of the app hang detection mechanism. This is useful in settings where an app can be put in the background, causing the monitored thread to suspend execution. ([#1928](https://github.com/getsentry/sentry-native/pull/1928))

**Fixes**:

- Honor checks before launching crash reporter ([#1906](https://github.com/getsentry/sentry-native/pull/1906))

## 0.16.0
Expand Down
12 changes: 12 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -2829,6 +2829,18 @@ SENTRY_EXPERIMENTAL_API uint64_t sentry_options_get_app_hang_timeout(
*/
SENTRY_EXPERIMENTAL_API void sentry_app_hang_heartbeat(void);

/**
* Pauses app-hang detection without changing the watched thread.
*
* While paused, the watchdog does not capture app hangs. A subsequent heartbeat
* from the watched thread automatically resumes detection. Heartbeats from
* other threads continue to be ignored.
*
* This function is a no-op unless app-hang detection is enabled via
* `sentry_options_set_enable_app_hang_tracking`.
*/
Comment thread
bitsandfoxes marked this conversation as resolved.
SENTRY_EXPERIMENTAL_API void sentry_app_hang_pause(void);

/**
* Type of the `before_send_metric` callback.
*
Expand Down
31 changes: 31 additions & 0 deletions src/sentry_app_hang_latch.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ sentry__app_hang_should_capture(
static uint64_t g_target_tid = 0;
static uint64_t g_last_heartbeat_ms = 0;
static volatile long g_app_hang_active = 0;
static volatile long g_app_hang_paused = 0;

void
sentry__app_hang_set_active(bool active)
Expand All @@ -67,6 +68,23 @@ sentry__app_hang_is_active(void)
return sentry__atomic_fetch(&g_app_hang_active) != 0;
}

void
sentry__app_hang_set_paused(bool paused)
{
if (paused) {
// Clear the old timestamp before publishing the paused state, so a
// concurrent heartbeat cannot leave detection armed with stale data.
sentry__atomic_store_u64(&g_last_heartbeat_ms, 0);
}
sentry__atomic_store(&g_app_hang_paused, paused ? 1 : 0);
Comment thread
bitsandfoxes marked this conversation as resolved.
}

bool
sentry__app_hang_is_paused(void)
{
return sentry__atomic_fetch(&g_app_hang_paused) != 0;
}

uint64_t
sentry__app_hang_current_tid(void)
{
Expand Down Expand Up @@ -97,6 +115,7 @@ sentry__app_hang_latch_reset(void)
{
sentry__atomic_store_u64(&g_target_tid, 0);
sentry__atomic_store_u64(&g_last_heartbeat_ms, 0);
sentry__atomic_store(&g_app_hang_paused, 0);
}

void
Expand All @@ -117,5 +136,17 @@ sentry_app_hang_heartbeat(void)
// ignore heartbeats from other threads
sentry__atomic_store_u64(
&g_last_heartbeat_ms, sentry__monotonic_time());
// Publish the fresh timestamp before resuming so the watchdog cannot
// observe an unpaused detector paired with a stale heartbeat.
Comment thread
sentry[bot] marked this conversation as resolved.
sentry__atomic_store(&g_app_hang_paused, 0);
}
}

void
sentry_app_hang_pause(void)
{
if (!sentry__app_hang_is_active()) {
return;
}
sentry__app_hang_set_paused(true);
}
3 changes: 3 additions & 0 deletions src/sentry_app_hang_latch.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ void sentry__app_hang_set_active(bool active);
// Whether app-hang detection is currently armed.
bool sentry__app_hang_is_active(void);

void sentry__app_hang_set_paused(bool paused);
bool sentry__app_hang_is_paused(void);

sentry_value_t sentry__app_hang_make_event(
void **ips, size_t frame_count, uint64_t freeze_ms);

Expand Down
6 changes: 6 additions & 0 deletions src/sentry_app_hang_monitor.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ worker(void *arg)
if (!sentry__app_hang_is_active()) {
break;
}
if (sentry__app_hang_is_paused()) {
continue;
}

const sentry_app_hang_latch_t latch = sentry__app_hang_current_latch();
uint64_t now = sentry__monotonic_time();
Expand All @@ -108,6 +111,9 @@ worker(void *arg)
if (!sentry__app_hang_is_active()) {
break;
}
if (sentry__app_hang_is_paused()) {
continue;
}
// Only mark this freeze as fired when an event was actually
// captured. A transient stackwalk failure (0 frames) must not
// suppress retries while the thread remains stuck.
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/test_app_hang.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,38 @@ SENTRY_TEST(app_hang_latch)
sentry__app_hang_set_active(false);
}

SENTRY_TEST(app_hang_pause_resumes_on_heartbeat)
{
#if !SENTRY_HAS_THREAD_STACKWALK
SKIP_TEST();
#endif
Comment thread
bitsandfoxes marked this conversation as resolved.
sentry__app_hang_latch_reset();
sentry__app_hang_set_active(true);
sentry_app_hang_heartbeat();

sentry_app_hang_latch_t l = sentry__app_hang_current_latch();
uint64_t target = l.target_tid;
uint64_t first_heartbeat = l.last_heartbeat_ms;
TEST_CHECK(target != 0);
TEST_CHECK(first_heartbeat != 0);

sentry_app_hang_pause();
l = sentry__app_hang_current_latch();
TEST_CHECK(sentry__app_hang_is_paused());
TEST_CHECK(l.target_tid == target);
TEST_CHECK(l.last_heartbeat_ms == 0);

sleep_ms(1);
sentry_app_hang_heartbeat();
l = sentry__app_hang_current_latch();
TEST_CHECK(!sentry__app_hang_is_paused());
TEST_CHECK(l.target_tid == target);
TEST_CHECK(l.last_heartbeat_ms > first_heartbeat);

sentry__app_hang_latch_reset();
sentry__app_hang_set_active(false);
}

SENTRY_TEST(app_hang_make_event)
{
void *ips[2] = { (void *)0x1000, (void *)0x2000 };
Expand Down
1 change: 1 addition & 0 deletions tests/unit/tests.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ XX(app_hang_end_to_end)
XX(app_hang_latch)
XX(app_hang_make_event)
XX(app_hang_monitor_fires)
XX(app_hang_pause_resumes_on_heartbeat)
XX(app_hang_should_capture)
XX(assert_sdk_name)
XX(assert_sdk_user_agent)
Expand Down
Loading