Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] - Fatal error with VideoInputRenderTarget when restart the level and waited for a while #388

Open
kjs1715 opened this issue Jan 3, 2025 · 2 comments
Labels
bug Something isn't working never-stale

Comments

@kjs1715
Copy link

kjs1715 commented Jan 3, 2025

Frontend Version:
E.g. UE5.2

Problem component
E.g. Pixel Streaming C++ plugin

Description
The issue happened on the team project and was reproduced in the empty project as well. We made a blueprint with SceneCaptureComponent2D, PixelStreamerComponent, and fed the render target by setting up the VideoInput for the streamer. When the application is running, it shows the correct view from the render target through the browser. However, when we try to call the Open Level node with key press, the application crashes with showing a fatal error message.

Related Issue:
#281

The previous issue was responded by @mcottontensor , I applied that and found new occurs with new issue when I call "RestartLevel " from the command console and wait for 30 sec - 1mins:

[2025.01.02-21.32.36:950][813]LogWindows: Error: === Critical error: ===
[2025.01.02-21.32.36:950][813]LogWindows: Error: 
[2025.01.02-21.32.36:950][813]LogWindows: Error: Fatal error!
[2025.01.02-21.32.36:950][813]LogWindows: Error: 
[2025.01.02-21.32.36:950][813]LogWindows: Error: Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0xffffffffffffffff
[2025.01.02-21.32.36:950][813]LogWindows: Error: 
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76b273c6b ORPlannerContent.exe!FPixelStreamingSignallingConnection::KeepAlive() [C:\UnrealEngine5.3\Engine\Plugins\Media\PixelStreaming\Source\PixelStreaming\Private\PixelStreamingSignallingConnection.cpp:328]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76b266067 ORPlannerContent.exe!TBaseRawMethodDelegateInstance<0,FPixelStreamingSignallingConnection,void __cdecl(void),FNotThreadSafeNotCheckedDelegateUserPolicy>::Execute() [C:\UnrealEngine5.3\Engine\Source\Runtime\Core\Public\Delegates\DelegateInstancesImpl.h:518]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff769d9fec4 ORPlannerContent.exe!FTimerUnifiedDelegate::Execute() [C:\UnrealEngine5.3\Engine\Source\Runtime\Engine\Public\TimerManager.h:51]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff769e0a7e7 ORPlannerContent.exe!FTimerManager::Tick() [C:\UnrealEngine5.3\Engine\Source\Runtime\Engine\Private\TimerManager.cpp:933]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76929e93b ORPlannerContent.exe!UWorld::Tick() [C:\UnrealEngine5.3\Engine\Source\Runtime\Engine\Private\LevelTick.cpp:1580]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff769007e53 ORPlannerContent.exe!UGameEngine::Tick() [C:\UnrealEngine5.3\Engine\Source\Runtime\Engine\Private\GameEngine.cpp:1772]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76a5b8ac2 ORPlannerContent.exe!FEngineLoop::Tick() [C:\UnrealEngine5.3\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:5825]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76a5d0a1c ORPlannerContent.exe!GuardedMain() [C:\UnrealEngine5.3\Engine\Source\Runtime\Launch\Private\Launch.cpp:188]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76a5d0afa ORPlannerContent.exe!GuardedMainWrapper() [C:\UnrealEngine5.3\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:118]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76a5d3965 ORPlannerContent.exe!LaunchWindowsStartup() [C:\UnrealEngine5.3\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:258]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76a5e31b4 ORPlannerContent.exe!WinMain() [C:\UnrealEngine5.3\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:299]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff76f548c5a ORPlannerContent.exe!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
[2025.01.02-21.32.36:950][813]LogWindows: Error: [Callstack] 0x00007ff83e3e7614 KERNEL32.DLL!UnknownFunction []
[2025.01.02-21.32.36:950][813]LogWindows: Error: 
[2025.01.02-21.32.36:960][813]LogExit: Executing StaticShutdownAfterError
[2025.01.02-21.32.36:963][813]LogWindows: FPlatformMisc::RequestExit(1, LaunchWindowsStartup.ExceptionHandler)
[2025.01.02-21.32.36:963][813]LogWindows: FPlatformMisc::RequestExitWithStatus(1, 3, LaunchWindowsStartup.ExceptionHandler)
[2025.01.02-21.32.36:963][813]LogCore: Engine exit requested (reason: Win RequestExit)
@kjs1715 kjs1715 added the bug Something isn't working label Jan 3, 2025
@kjs1715 kjs1715 changed the title [BUG] - Fatal error with VideoInputRenderTarget when restart the level and wait for a while [BUG] - Fatal error with VideoInputRenderTarget when restart the level and waited for a while Jan 6, 2025
@DenisTensorWorks
Copy link
Collaborator

Thank you for reporting the issue! It is a bug on the UE side, we are tracking it internally as RTCP-7877 for fixing in 5.6.

@mcottontensor
Copy link
Collaborator

A fix for this has been submitted to the plugin but unfortunately it wont be released until 5.6 since it does not meet the requirements for a hotfix.

However like the previous fix I can list you the changes that have been made so you can implement the fix yourself.
Since this fix is a continuation of the last fix I provided I'll give the changes from the unedited versions of the files. Please replace the last fix additions with the following.
Engine\Plugins\Media\PixelStreaming\Source\PixelStreaming\Public\PixelStreamingSignallingConnection.h
Remove the KeepAlive member function.
At the bottom of the class, add the following

	// we can used WeakPtrs pointing to this SharedPtr to detect when this class is destroyed
	// that allows the following Safe... delayed executions to safely check if the connection
	// no longer exists.
	TSharedPtr<int> AliveSemaphore;

	/**
	 * These templates are defined in the cpp since theyre private and we dont want to include the private
	 * include file "Utils.h" here in this public header.
	 */
	template<typename T> void SafeGameThreadExecute(T&& Func);
	template<typename T> void SafeGameThreadExecuteAndWait(uint32 Timeout, T&& Func);

Engine\Plugins\Media\PixelStreaming\Source\PixelStreaming\Private\PixelStreamingSignallingConnection.cpp
Add these templated functions somewhere. (In the submitted fix they're just under the DEFINE_LOG_CATEGORY macro call)

template<typename T>
void FPixelStreamingSignallingConnection::SafeGameThreadExecute(T&& Func)
{
	TWeakPtr<int> WeakSemaphore = AliveSemaphore;
	UE::PixelStreaming::DoOnGameThread([this, WeakSemaphore, Func]() {
		if (WeakSemaphore.IsValid())
		{
			Func();
		}
	});
}

template<typename T>
void FPixelStreamingSignallingConnection::SafeGameThreadExecuteAndWait(uint32 Timeout, T&& Func)
{
	TWeakPtr<int> WeakSemaphore = AliveSemaphore;
	UE::PixelStreaming::DoOnGameThreadAndWait(Timeout, [this, WeakSemaphore, Func]() {
		if (WeakSemaphore.IsValid())
		{
			Func();
		}
	});
}

At the top of the constructor, add this line.

AliveSemaphore = MakeShared<int>(1);

And at the bottom of the destructor, add this.

AliveSemaphore.Reset();

Remove the KeepAlive member function definition.
Change the StartKeepAliveTimer function to look like this

void FPixelStreamingSignallingConnection::StartKeepAliveTimer()
{
	// Dereferencing needs to happen on the game thread
	// we dont need to wait since its just setting the timer
	SafeGameThreadExecute([this]() {
		using namespace UE::PixelStreamingSignallingConnection::Private;
		FTimerManager* TimerManager = GetTimerManager();
		if (TimerManager && !TimerManager->IsTimerActive(TimerHandle_KeepAlive))
		{
			TimerManager->SetTimer(TimerHandle_KeepAlive, FTimerDelegate::CreateLambda([this, TimerManager, TimerHandle = TimerHandle_KeepAlive, WeakSemaphore = AliveSemaphore.ToWeakPtr()]() {
				if (WeakSemaphore.IsValid())
				{
					FJsonObjectPtr Json = MakeShared<FJsonObject>();
					const double UnixTime = FDateTime::UtcNow().ToUnixTimestamp();
					Json->SetStringField(TEXT("type"), TEXT("ping"));
					Json->SetNumberField(TEXT("time"), UnixTime);
					SendMessage(UE::PixelStreaming::ToString(Json, false));
				}
				else
				{
					// Owning connection has gone away for whatever reason. Clear this timer.
					// TimerHandle captured is const and we cant use TimerHandle_KeepAlive because 'this' might have been deleted.
					// ClearTimer takes a non const reference so we cant use TimerHandle.
					FTimerHandle Handle = TimerHandle;
					TimerManager->ClearTimer(Handle);
				}
			}), KEEP_ALIVE_INTERVAL, true);
		}
	});
}

Change the StopKeepAlive function to look like this

void FPixelStreamingSignallingConnection::StopKeepAliveTimer()
{
	// Dereferencing needs to happen on the game thread
	// we need to wait because if we're destructing this object we dont
	// want to call the callback mid/post destruction
	SafeGameThreadExecuteAndWait(MAX_uint32, [this]() {
		using namespace UE::PixelStreamingSignallingConnection::Private;
		FTimerManager* TimerManager = GetTimerManager();
		if (TimerManager)
		{
			TimerManager->ClearTimer(TimerHandle_KeepAlive);
		}
	});
}

Change the StartReconnectTimer function to look like this

void FPixelStreamingSignallingConnection::StartReconnectTimer()
{
	// Dereferencing needs to happen on the game thread
	SafeGameThreadExecute([this]() {
		if (IsEngineExitRequested())
		{
			return;
		}
		using namespace UE::PixelStreamingSignallingConnection::Private;
		FTimerManager* TimerManager = GetTimerManager();
		if (TimerManager && !TimerManager->IsTimerActive(TimerHandle_Reconnect))
		{
			float ReconnectInterval = UE::PixelStreaming::Settings::CVarPixelStreamingSignalingReconnectInterval.GetValueOnAnyThread();
			FTimerDelegate delegate = FTimerDelegate::CreateLambda([this, WeakSemaphore = AliveSemaphore.ToWeakPtr()]() {
				if (WeakSemaphore.IsValid())
				{
					Connect(Url, true);
				}
				});
			TimerManager->SetTimer(TimerHandle_Reconnect, delegate, ReconnectInterval, true);
		}
	});
}

Change the StopReconnectTimer function to look like this

void FPixelStreamingSignallingConnection::StopReconnectTimer()
{
	// GWorld dereferencing needs to happen on the game thread
	SafeGameThreadExecute([this]() {
		if (IsEngineExitRequested())
		{
			return;
		}
		using namespace UE::PixelStreamingSignallingConnection::Private;
		FTimerManager* TimerManager = GetTimerManager();
		if (TimerManager)
		{
			TimerManager->ClearTimer(TimerHandle_Reconnect);
		}
	});
}

And that should cover it. The issue is that we submit things to another thread that call into this class but sometimes the connection gets destroyed while work is still in the thread queue with no way to remove it. To prevent calling into a deleted object we use a weak pointer that becomes invalid when our class gets deleted and if that's the case the submitted work just aborts and averts a crash.

Hopefully that should fix the related issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working never-stale
Projects
None yet
Development

No branches or pull requests

4 participants