Server receives ack from Client after successful RPC

Disclaimer: maybe what I want doesn’t even make sense with UE4’s architecture. I’m a converted webdev

I’m working on a Multiplayer game with a dedicated server. I’m dealing with a crash that I think happens because the server is requesting clients to do some work and then immediately issuing a ServerTravel before they’re done (hence causing a null access somewhere).

Scenario:

// LobbyPlayerController.h
UFUNCTION(Client, Reliable, BlueprintCallable) void Client_PrepareTransitionToGame();

//LobbyPlayerController.cpp
Client_PrepareTransitionToGame_Implementation()
{
    if (WaitingForPlayerWidgetInstance != nullptr)
    {
        WaitingForPlayerWidgetInstance->RemoveFromViewport();
    }
}

//LobbyGameMode.cpp
void APreGameLobbyMode::PrepareTransitionToGame() const
{
    FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator();
    for (; It; ++It)
    {
        ALobbyPlayerController* PC = Cast<ALobbyPlayerController>(*It);
        if (PC)
        {
            PC->Client_PrepareTransitionToGame();
        }
    }

    GetWorld()->ServerTravel(MapData.PathToMap);
}

Problem:

Sometimes the call to RemoveFromViewport causes a crash. It happens rarely, but it happens. The following log is what we get right before the crash on a Client.
[2020.05.20-18.07.44:102][167]LogWindows: Error: [Callstack] 0x00007ff75f4c6ba3 Immortal.exe!UUserWidget::RemoveFromViewport() [p:\ue4\unrealengine-4.24.3-source\engine\source\runtime\umg\private\userwidget.cpp:1134]

I believe the problem is the Server Travel is causing some reference to dying mid-process. Clearly, when calling the method everything is in a valid state. So, what I want to do is create some sort of Ack workflow where Server calls the method Client_PrepareTransitionToGame on each Client’s controller, and then the server waits for each of them to report back saying “I’m ready to transition”. I want to use a callback for that purpose, so I don’t have to expose a public method on LobbyGameMode.

Idea:

// LobbyGameMode.h
private:
    void AckReadyToTransition(int32 PublicID);
    TSet<int32> PlayersReadyToTransitionToGame;

//LobbyGameMode.cpp
void ALobbyGameMode::PrepareTransitionToGame() const
{
    FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator();
    for (; It; ++It)
    {
        ALobbyPlayerController* PC = Cast<ALobbyPlayerController>(*It);
        if (PC)
        {
            PC->Client_PrepareTransitionToGame(CALLBACK based on AckReadyToTransition);
        }
    }
}

void ALobbyGameMode::AckReadyToTransition(const int32 PublicID)
{
    PlayersReadyToTransitionToGame.Add(PublicID);
    if (PlayersReadyToTransitionToGame.Num() == ExpectedPlayerCount)
    {
        GetWorld()->ServerTravel(MapData.PathToMap);
    }
}

Basically, I want the client controller to call the callback when it’s done, so the server can register that and start the transition only after receiving enough ack signals. Is something like this doable? If so, how?

If I’m taking the wrong approach, what would be a better/cleaner way to solve the problem As a server, I want to know when every client has successfully processed an RPC that they must succeed processing