UE5 server crash when OnHealthCheckInternal is called

0

Hi there!

  • I compiled the SDK v5.0.3 for Unreal following the documentation provided with the SDK.
  • Copied the plugin to my Unreal Engine v5.1.0 project and made necessary changes in .uproject & build file, etc.
  • Build the game for server target and run the server.
[2023.06.15-06.08.53:838][  0]GameServerLog: Initializing the GameLift Server
[2023.06.15-06.08.53:839][  0]GameServerLog: AUTH_TOKEN: <omitted>
[2023.06.15-06.08.53:839][  0]GameServerLog: HOST_ID: <omitted>
[2023.06.15-06.08.53:839][  0]GameServerLog: FLEET_ID: <omitted>
[2023.06.15-06.08.53:839][  0]GameServerLog: WEBSOCKET_URL: wss://eu-west-1.api.amazongamelift.com
[2023.06.15-06.08.53:840][  0]GameServerLog: PID: 25592
[2023.06.15-06.08.54:501][  0]GameServerLog: GameLift InitSDK succeeded
[2023.06.15-06.08.54:501][  0]GameServerLog: Calling Process Ready
[2023.06.15-06.08.54:663][  0]GameServerLog: Process Ready Succeded
[2023.06.15-06.08.54:664][  0]GameServerLog: Init GameLift complete

So far so good.

However, after exactly 1 min. things get hairy and server crashes with no error but an Engine exit requested (reason: Win RequestExit) message.

After attaching to the process and some debugging I was able to find out that this happens on <omitted>Server.exe!OnHealthCheckInternal(void * state) Line 245 on the stack trace.

Screenshot is from GameLiftServerSDK.cpp.

OnHealthCheckInternal

Moving up in the stack, I see <omitted>Server.exe!FProcessParameters::OnHealthCheckFunction() Line 50 is called.

Screenshot is from GameLiftServerSDK.h.

OnHealthCheckFunction

On top of the stack I have <omitted>Server.exe!TDelegateBase<FDefaultDelegateUserPolicy>::IsBound() Line 236. This is where the crash happens.

Screenshot is from Engine/Source/Runtime/Core/Public/Delegates/DelegateBase.h.

IsBound

Please note that I am returned a true to my processReadyOutcome.IsSuccess() call in the game mode where GameLift is initialized. Which makes me assume HealthCheck callback should be no problem. But unfortunately it's not the case.

// Calling ProcessReady tells GameLift this game server is ready to receive incoming game sessions!
UE_LOG(GameServerLog, Log, TEXT("Calling Process Ready"));
FGameLiftGenericOutcome processReadyOutcome = gameLiftSdkModule->ProcessReady(params);
if (processReadyOutcome.IsSuccess())
{
    UE_LOG(GameServerLog, Log, TEXT("Process Ready Succeded"));
}
else
{
    UE_LOG(GameServerLog, Log, TEXT("ERROR: Process Ready Failed"));
    FGameLiftError processReadyError = processReadyOutcome.GetError();
    UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *processReadyError.m_errorMessage);
}
UE_LOG(GameServerLog, Log, TEXT("Init GameLift complete"));

What might be the problem here which causes the server to crash?

  • Is it in fact not able to initialize GameLift but fails to report an error?
  • Might this be an engine issue where IsBound() is called?
  • Am I failing to compile the SDK correctly even though I'm following the README.md files step by step provided by the SDK?

Any help would be highly appreciated. I'm stuck here. Thanks!

Merih
asked 10 months ago341 views
2 Answers
0
Accepted Answer

It was an issue with FProcessParameters definition. Added static keyword and issue is resolved.

Here is the slightly modified and fixed code which is taken from the GameLift documentation here. Hope it helps someone having the same issue.

void A<omitted>GameMode::InitGameLift()
{
	UE_LOG(GameServerLog, Log, TEXT("Initializing the GameLift Server"));

	// Getting the module first.
	FGameLiftServerSDKModule* gameLiftSdkModule = &FModuleManager::LoadModuleChecked<FGameLiftServerSDKModule>(FName("GameLiftServerSDK"));

	// Define the server parameters
	FServerParameters serverParameters;

	// AuthToken returned from the "aws gamelift get-compute-auth-token" API. Note this will expire and require a new call to the API after 15 minutes.
	if (FParse::Value(FCommandLine::Get(), TEXT("-authtoken="), serverParameters.m_authToken))
	{
		UE_LOG(GameServerLog, Log, TEXT("AUTH_TOKEN: %s"), *serverParameters.m_authToken)
	}

	// The Host/Compute ID of the GameLift Anywhere instance.
	if (FParse::Value(FCommandLine::Get(), TEXT("-hostid="), serverParameters.m_hostId))
	{
		UE_LOG(GameServerLog, Log, TEXT("HOST_ID: %s"), *serverParameters.m_hostId)
	}

	// The EC2 or Anywhere Fleet ID.
	if (FParse::Value(FCommandLine::Get(), TEXT("-fleetid="), serverParameters.m_fleetId))
	{
		UE_LOG(GameServerLog, Log, TEXT("FLEET_ID: %s"), *serverParameters.m_fleetId)
	}

	// The WebSocket URL (GameLiftServiceSdkEndpoint).
	if (FParse::Value(FCommandLine::Get(), TEXT("-websocketurl="), serverParameters.m_webSocketUrl))
	{
		UE_LOG(GameServerLog, Log, TEXT("WEBSOCKET_URL: %s"), *serverParameters.m_webSocketUrl)
	}

	// The PID of the running process
	serverParameters.m_processId = FString::Printf(TEXT("%d"), GetCurrentProcessId());
	UE_LOG(GameServerLog, Log, TEXT("PID: %s"), *serverParameters.m_processId);

	// InitSDK will establish a local connection with GameLift's agent to enable further communication.
	FGameLiftGenericOutcome initSdkOutcome = gameLiftSdkModule->InitSDK(serverParameters);
	if (initSdkOutcome.IsSuccess())
	{
		UE_LOG(GameServerLog, Log, TEXT("GameLift InitSDK succeeded"));
	}
	else
	{
		UE_LOG(GameServerLog, Log, TEXT("ERROR: InitSDK failed"));
		FGameLiftError gameLiftError = initSdkOutcome.GetError();
		UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *gameLiftError.m_errorMessage);
		return;
	}

	// When a game session is created, GameLift sends an activation request to the game server and passes along the game session object containing game properties and other settings.
	// Here is where a game server should take action based on the game session object.
	// Once the game server is ready to receive incoming player connections, it should invoke GameLiftServerAPI.ActivateGameSession()
	auto onGameSession = [=](Aws::GameLift::Server::Model::GameSession gameSession)
	{
		FString gameSessionId = FString(gameSession.GetGameSessionId());
		UE_LOG(GameServerLog, Log, TEXT("GameSession Initializing: %s"), *gameSessionId);
		gameLiftSdkModule->ActivateGameSession();
	};

	static FProcessParameters params;
	params.OnStartGameSession.BindLambda(onGameSession);

	// OnProcessTerminate callback. GameLift will invoke this callback before shutting down an instance hosting this game server.
	// It gives this game server a chance to save its state, communicate with services, etc., before being shut down.
	// In this case, we simply tell GameLift we are indeed going to shutdown.
	params.OnTerminate.BindLambda([=]()
		{
			UE_LOG(GameServerLog, Log, TEXT("Game Server Process is terminating"));
			gameLiftSdkModule->ProcessEnding();
		});

	// This is the HealthCheck callback.
	// GameLift will invoke this callback every 60 seconds or so.
	// Here, a game server might want to check the health of dependencies and such.
	// Simply return true if healthy, false otherwise.
	// The game server has 60 seconds to respond with its health status. GameLift will default to 'false' if the game server doesn't respond in time.
	// In this case, we're always healthy!
	params.OnHealthCheck.BindLambda([]()
		{
			UE_LOG(GameServerLog, Log, TEXT("Performing Health Check"));
			return true;
		});

	// This game server tells GameLift that it will listen on port 7777 for incoming player connections.
	params.port = 7777;

	// Here, the game server tells GameLift what set of files to upload when the game session ends.
	// GameLift will upload everything specified here for the developers to fetch later.
	TArray<FString> logfiles;
	logfiles.Add(TEXT("C:\\game\\<omitted>\\Saved\\Logs"));
	params.logParameters = logfiles;

	// Calling ProcessReady tells GameLift this game server is ready to receive incoming game sessions!
	UE_LOG(GameServerLog, Log, TEXT("Calling Process Ready"));
	FGameLiftGenericOutcome processReadyOutcome = gameLiftSdkModule->ProcessReady(params);
	if (processReadyOutcome.IsSuccess())
	{
		UE_LOG(GameServerLog, Log, TEXT("Process Ready Succeded"));
	}
	else
	{
		UE_LOG(GameServerLog, Log, TEXT("ERROR: Process Ready Failed"));
		FGameLiftError processReadyError = processReadyOutcome.GetError();
		UE_LOG(GameServerLog, Log, TEXT("ERROR: %s"), *processReadyError.m_errorMessage);
	}
	UE_LOG(GameServerLog, Log, TEXT("Init GameLift complete"));
}

Thanks.

Merih Dereli

Merih
answered 10 months ago
0

Couple of comments on your solution. Example from GameLift documentation is just to make you started, so do not consider it as best practice. In my opinion calls to GameLift SDK should not be made from GameMode, but you should create class YourProjectGameLiftSubsystem that is derived from UGameInstanceSubsystem with private member FProcessParameters GameLiftProcessParams; Also use of Lambda functions for OnHealthCheck, OnTerminate and onGameSession is ok if you are just doing some logging but it would be better to create regular functions and then bind them to those delegates e.g.

GameLiftProcessParams.OnStartGameSession.BindUObject(this, &UYourProjectGameLiftSubsystem::OnGameSessionActivated);
void UYourProjectGameLiftSubsystem::OnGameSessionActivated(Aws::GameLift::Server::Model::GameSession ActivatedSession) { ... }
answered 10 months ago
  • Hey! Thanks a lot for your comments. Will definitely consider them.

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions