Skip to content
Draft
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
36 changes: 33 additions & 3 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,28 @@ run_threads(thread_func_t func)
}
#endif

#if defined(SENTRY_PLATFORM_MACOS)
#if defined(SENTRY_PLATFORM_WINDOWS)
static unsigned __stdcall
app_hang_demo_thread(void *arg)
{
(void)arg;
/* The first heartbeat latches this thread as the monitored target. Beat for
* 500 ms so the daemon sees a healthy baseline before the freeze. */
for (int i = 0; i < 10; i++) {
sentry_app_hang_heartbeat();
Sleep(50);
}
/* Add a couple of breadcrumbs before freezing so the captured app-hang
* event carries them (the daemon reads the breadcrumb ring files the host
* writes on each sentry_add_breadcrumb). */
sentry_add_breadcrumb(
sentry_value_new_breadcrumb(NULL, "app-hang demo: about to freeze"));
sentry_add_breadcrumb(create_debug_crumb("app-hang demo breadcrumb"));
/* Freeze for 3x the configured timeout (3000 ms). */
Sleep(3000);
return 0;
}
#elif defined(SENTRY_PLATFORM_MACOS)
static void *
app_hang_demo_thread(void *arg)
{
Expand Down Expand Up @@ -883,7 +904,7 @@ main(int argc, char **argv)
options, SENTRY_CRASH_UPLOAD_MODE_ASYNC);
}

#if defined(SENTRY_PLATFORM_MACOS)
#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_MACOS)
if (has_arg(argc, argv, "app-hang")) {
sentry_options_set_app_hang_enabled(options, 1);
sentry_options_set_app_hang_timeout_ms(options, 1000);
Expand All @@ -901,18 +922,27 @@ main(int argc, char **argv)
return EXIT_FAILURE;
}

#if defined(SENTRY_PLATFORM_MACOS)
#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_MACOS)
/* app-hang: spawn the demo thread BEFORE any other post-init work so it
* begins heartbeating immediately. The thread freezes for 3x the timeout,
* giving the daemon time to detect the hang and ship the envelope. We wait
* for it here so main does not exit before the transport has flushed.
* NOTE: this mode is intentionally exclusive – do not combine with crash/
* abort/etc. since those would terminate the process first. */
if (has_arg(argc, argv, "app-hang")) {
# if defined(SENTRY_PLATFORM_WINDOWS)
HANDLE t = (HANDLE)_beginthreadex(
NULL, 0, app_hang_demo_thread, NULL, 0, NULL);
if (t) {
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
}
# else
pthread_t t;
if (0 == pthread_create(&t, NULL, app_hang_demo_thread, NULL)) {
pthread_join(t, NULL);
}
# endif
sentry_close();
return EXIT_SUCCESS;
}
Expand Down
6 changes: 3 additions & 3 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1709,8 +1709,8 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_session_replay_duration(
* The host process keeps running.
*
* Off by default. This setting only has an effect when using the `native`
* backend. In this initial release the feature is macOS-only; the call is a
* silent no-op on other platforms.
* backend. The feature is supported on macOS and Windows; the call is a silent
* no-op on other platforms.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_app_hang_enabled(
sentry_options_t *opts, int enabled);
Expand All @@ -1737,7 +1737,7 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_app_hang_timeout_ms(
* No-op if
* - app-hang detection is not enabled
* - the native backend is not active
* - the platform is not macOS
* - the platform is neither macOS nor Windows
*/
SENTRY_EXPERIMENTAL_API void sentry_app_hang_heartbeat(void);

Expand Down
14 changes: 9 additions & 5 deletions src/backends/native/sentry_crash_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,19 @@ typedef struct {
uint32_t module_count;
sentry_module_info_t modules[SENTRY_CRASH_MAX_MODULES];

/* App-hang detection.
/* App-hang detection (Windows + macOS, native backend only).
*
* Sync model:
* - app_hang_enabled, app_hang_timeout_ms: written by host before daemon
* is signalled ready; read by daemon at startup. No further mutation.
* - app_hang_target_tid: latched once by host on first heartbeat.
* Daemon reads, never writes.
* - app_hang_last_heartbeat_ms: written on every heartbeat.
*/
* - app_hang_target_tid: latched once by host on first heartbeat via a
* compare-exchange (InterlockedCompareExchange64 on Windows,
* atomic_compare_exchange_strong on macOS). Daemon reads, never writes.
* - app_hang_last_heartbeat_ms: written on every heartbeat with a relaxed
* 64-bit store. Daemon reads with a relaxed load. Torn reads are not a
* correctness issue — the daemon compares against its remembered value
* from the previous tick. (On 64-bit Windows/macOS the aligned store is
* atomic; the tear note applies to 32-bit Windows.) */
bool app_hang_enabled;
uint64_t app_hang_timeout_ms;
volatile uint64_t app_hang_target_tid;
Expand Down
Loading
Loading