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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add out-of-process hang tracking with `Native` backend on Windows, Mac and Linux ([#1427](https://github.com/getsentry/sentry-unreal/pull/1427))

### Dependencies

- Bump CLI from v3.5.0 to v3.5.1 ([#1438](https://github.com/getsentry/sentry-unreal/pull/1438))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,11 @@ bool FAndroidSentrySubsystem::IsHangTrackingSupported() const
return false;
}

bool FAndroidSentrySubsystem::IsOutOfProcessHangTrackingEnabled() const
{
return false;
}

void FAndroidSentrySubsystem::CaptureFeedback(TSharedPtr<ISentryFeedback> feedback)
{
TSharedPtr<FAndroidSentryFeedback> feedbackAndroid = StaticCastSharedPtr<FAndroidSentryFeedback>(feedback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class FAndroidSentrySubsystem : public ISentrySubsystem
virtual TSharedPtr<ISentryId> CaptureEnsure(const FString& type, const FString& message) override;
virtual TSharedPtr<ISentryId> CaptureHang(uint32 HungThreadId) override;
virtual bool IsHangTrackingSupported() const override;
virtual bool IsOutOfProcessHangTrackingEnabled() const override;
virtual void CaptureFeedback(TSharedPtr<ISentryFeedback> feedback) override;
virtual void SetUser(TSharedPtr<ISentryUser> user) override;
virtual void RemoveUser() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,11 @@ bool FAppleSentrySubsystem::IsHangTrackingSupported() const
return false;
}

bool FAppleSentrySubsystem::IsOutOfProcessHangTrackingEnabled() const
{
return false;
}

void FAppleSentrySubsystem::CaptureFeedback(TSharedPtr<ISentryFeedback> feedback)
{
TSharedPtr<FAppleSentryFeedback> feedbackApple = StaticCastSharedPtr<FAppleSentryFeedback>(feedback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class FAppleSentrySubsystem : public ISentrySubsystem
virtual TSharedPtr<ISentryId> CaptureEnsure(const FString& type, const FString& message) override;
virtual TSharedPtr<ISentryId> CaptureHang(uint32 HungThreadId) override;
virtual bool IsHangTrackingSupported() const override;
virtual bool IsOutOfProcessHangTrackingEnabled() const override;
virtual void CaptureFeedback(TSharedPtr<ISentryFeedback> feedback) override;
virtual void SetUser(TSharedPtr<ISentryUser> user) override;
virtual void RemoveUser() override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ FGenericPlatformSentrySubsystem::FGenericPlatformSentrySubsystem()
, isScreenshotAttachmentEnabled(false)
, isSessionReplayAttachmentEnabled(false)
, isGpuDumpAttachmentEnabled(false)
, bOutOfProcessHangTracking(false)
, initTimestamp(FDateTime::UtcNow())
{
}
Expand Down Expand Up @@ -560,6 +561,15 @@ void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* se
ConfigureNetworkConnectFunc(options);
ConfigureStackCaptureStrategy(options);

#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX
bOutOfProcessHangTracking = settings->EnableHangTracking && settings->UseNativeBackend;
if (bOutOfProcessHangTracking)
{
sentry_options_set_enable_app_hang_tracking(options, 1);
sentry_options_set_app_hang_timeout_ms(options, static_cast<uint64_t>(settings->HangTimeoutDuration * 1000.0));
}
#endif

if (settings->EnableExternalCrashReporter)
{
ConfigureCrashReporterPath(options);
Expand Down Expand Up @@ -621,6 +631,16 @@ void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* se
isStackTraceEnabled = settings->AttachStacktrace;
isPiiAttachmentEnabled = settings->SendDefaultPii;

if (bOutOfProcessHangTracking && isEnabled)
{
// OnEndFrame is broadcast on the game thread, so the first invocation latches it as the monitored
// thread and every subsequent frame refreshes the heartbeat the daemon watches for staleness
AppHangHeartbeatHandle = FCoreDelegates::OnEndFrame.AddLambda([]()
{
sentry_app_hang_heartbeat();
});
}

// Best-effort at writing user consent to disk so that user consent can change at runtime and persist
// We should never have a valid user consent state return "Unknown", so assume that no consent value is written if we see this
if (settings->bRequireUserConsent && GetUserConsent() == EUserConsent::Unknown)
Expand Down Expand Up @@ -657,6 +677,12 @@ void FGenericPlatformSentrySubsystem::InitWithSettings(const USentrySettings* se

void FGenericPlatformSentrySubsystem::Close()
{
if (AppHangHeartbeatHandle.IsValid())
{
FCoreDelegates::OnEndFrame.Remove(AppHangHeartbeatHandle);
AppHangHeartbeatHandle.Reset();
}

isEnabled = false;

#ifdef USE_SENTRY_SESSION_REPLAY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem

virtual void HandleAssert() override {}
virtual bool IsHangTrackingSupported() const override { return false; }
virtual bool IsOutOfProcessHangTrackingEnabled() const override { return bOutOfProcessHangTracking; }
virtual FString GetDeviceType() const override { return TEXT("Desktop"); }

USentryBeforeSendHandler* GetBeforeSendHandler() const;
Expand Down Expand Up @@ -154,6 +155,10 @@ class FGenericPlatformSentrySubsystem : public ISentrySubsystem
bool isSessionReplayAttachmentEnabled;
bool isGpuDumpAttachmentEnabled;

bool bOutOfProcessHangTracking;

FDelegateHandle AppHangHeartbeatHandle;

FDateTime initTimestamp;

FString databaseParentPath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#if PLATFORM_ANDROID
#include "Android/AndroidSentryMetric.h"
#elif PLATFORM_APPLE
#elif PLATFORM_APPLE && !USE_SENTRY_NATIVE
#include "Apple/AppleSentryMetric.h"
#elif USE_SENTRY_NATIVE
#include "GenericPlatform/GenericPlatformSentryMetric.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,6 @@ class ISentrySubsystem
/** Unreal-specific methods that are not part of the platform's Sentry SDK API */
virtual void HandleAssert() = 0;
virtual bool IsHangTrackingSupported() const = 0;
virtual bool IsOutOfProcessHangTrackingEnabled() const = 0;
virtual FString GetDeviceType() const = 0;
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class FNullSentrySubsystem : public ISentrySubsystem

virtual void HandleAssert() override {}
virtual bool IsHangTrackingSupported() const override { return false; }
virtual bool IsOutOfProcessHangTrackingEnabled() const override { return false; }
virtual FString GetDeviceType() const override { return TEXT("Unknown"); }
};

Expand Down
5 changes: 4 additions & 1 deletion plugin-dev/Source/Sentry/Private/SentrySubsystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,11 @@ void USentrySubsystem::Initialize()
SubsystemNativeImpl->CaptureEnsure(TEXT("Ensure failed"), EnsureMessage.TrimStartAndEnd());
});

if (Settings->EnableHangTracking && SubsystemNativeImpl->IsHangTrackingSupported())
if (Settings->EnableHangTracking && SubsystemNativeImpl->IsHangTrackingSupported() && !SubsystemNativeImpl->IsOutOfProcessHangTrackingEnabled())
{
// When the platform/backend provides its own out-of-process app-hang detector (e.g. native backend
// on Windows), it's wired during InitWithSettings and the engine-side watcher is skipped to avoid
// double-reporting. Otherwise fall back to the FThreadHeartBeat-based watcher.
ConfigureHangTracking();
}

Expand Down
Loading