Summary
fort_round_manager
does not behave like its documentation advertises. Registered callbacks are not being cancelled on round end. Depending on the setup the callback will leak and run indefinitely and accumulate with more rounds, or it will freeze the entire server!
The APIs explicitly state that the callbacks are cancelled automatically.
# Upon the round ending, all callbacks will be canceled
SubscribeRoundStarted<public>(Callback: type { __()<suspends>: void }): cancelable
Additionally there’s no mention that the round end subscription is also cancelled after the round ends. While this sounds trivial, it should be explicitly stated, as otherwise it might seem that the callback is still preserved for the next round!
Please select what you are reporting on:
Verse
What Type of Bug are you experiencing?
Stability
Steps to Reproduce
- Create a custom scene graph component like one below.
- Place an entity into the world and add the component to it.
- Start a round.
- Stop a round after a few logs.
Variant A will leak and keep the async callback running. Starting and stopping another round will add another leaking callback. Eventually it will hit an internal log that says File path did not pass sanitization check.
Variant B will freeze the server when ending the first round.
A workaround is to collect the subscriptions and manually cancel them when the end round event is delivered.
using { /Fortnite.com/Game }
using { /UnrealEngine.com/Temporary/SceneGraph }
using { /Verse.org/Simulation }
vz_test_fort_round_manager_component := class<final><final_super>(component) {
var Subscriptions: []cancelable = array {}
OnBeginSimulation<override>(): void = {
(super:)OnBeginSimulation()
Print("----- OnBeginSimulation -----")
if (RoundManager := Entity.GetFortRoundManager[]) {
Print("Found fort round manager")
set Subscriptions = array {
# ISSUE A:
RoundManager.SubscribeRoundStarted(OnRound_LEAKING)
# ISSUE B:
#RoundManager.SubscribeRoundStarted(OnRound_FREEZING_SERVER)
# WORKAROUND FOR BOTH: SUBSCRIBE TO ROUND END EVENT TO FIX THE ISSUE MANUALLY!
#RoundManager.SubscribeRoundEnded(OnEndRound)
}
} else {
Print("Could not find fort round manager")
}
}
# HOW TO REPRODUCE:
# - start a round
# - end the round
# - the round will end but the `OnRound_LEAKING` will keep running
# - if you start and end a few more rounds, eventually an internal message will pop up that says:
# "File path did not pass sanitization check."
OnRound_LEAKING()<suspends>: void = {
defer {
Print("Defer called on cancellation in 'OnRound_LEAKING'!")
}
Print("OnRound_LEAKING")
var Count: int = 0
loop {
# USING `Sleep` WILL LEAK!
Sleep(0.0)
set Count = Count + 1
Print("[A] Count: {Count}")
}
}
# HOW TO REPRODUCE:
# - start a round
# - end the round
# - server will freeze and eventually print lots similar to the logs down below
OnRound_FREEZING_SERVER()<suspends>: void = {
defer {
Print("Defer called on cancellation in 'OnRound_FREEZING_SERVER'!")
}
Print("OnRound_FREEZING_SERVER")
var Count: int = 0
loop {
# USING `TickEvents` WILL FREEZE THE SERVER!
TickEvents.PrePhysics.Await()
set Count = Count + 1
Print("[B] Count: {Count}")
}
}
OnEndRound(): void = {
# WORKAROUND - CANCEL EVERYTHING MANUALLY!!!
for (Subscription: Subscriptions) {
Subscription.Cancel()
}
set Subscriptions = array {}
}
}
Logs when the server freezes:
[2025.02.17-12.59.53:182][267]LogNet: Warning: UNetConnection::Tick: Connection TIMED OUT. Closing connection.. Elapsed: 30.01, Real: 30.01, Good: 30.00, DriverTime: 891.86, Threshold: 30.00, [UNetConnection] RemoteAddr: 9.141.9.117:15015, Name: IpConnection_13, Driver: Name:IpNetDriver_13 Def:BeaconNetDriver IpNetDriver_13, IsServer: NO, PC: NULL, Owner: ValkyrieBeaconClient_13, UniqueId: MCP:115ef624b3904f1b8abd6c427436c58b
[2025.02.17-12.59.53:182][267]LogNet: Error: UEngine::BroadcastNetworkFailure: FailureType = ConnectionTimeout, ErrorString = UNetConnection::Tick: Connection TIMED OUT. Closing connection.. Elapsed: 30.01, Real: 30.01, Good: 30.00, DriverTime: 891.86, Threshold: 30.00, [UNetConnection] RemoteAddr: 9.141.9.117:15015, Name: IpConnection_13, Driver: Name:IpNetDriver_13 Def:BeaconNetDriver IpNetDriver_13, IsServer: NO, PC: NULL, Owner: ValkyrieBeaconClient_13, UniqueId: MCP:115ef624b3904f1b8abd6c427436c58b, Driver = Name:IpNetDriver_13 Def:BeaconNetDriver IpNetDriver_13
[2025.02.17-12.59.53:182][267]LogValkyrieRequestManagerEditor: Warning: Encountered Network Error!
[2025.02.17-12.59.53:182][267]LogValkyrieRequestManagerEditor: Error: Failed to handle connection attempt (Network error occurred when connecting to a server after matchmaking completed. Error: UNetConnection::Tick: Connection TIMED OUT. Closing connection.. Elapsed: 30.01, Real: 30.01, Good: 30.00, DriverTime: 891.86, Threshold: 30.00, [UNetConnection] RemoteAddr: 9.141.9.117:15015, Name: IpConnection_13, Driver: Name:IpNetDriver_13 Def:BeaconNetDriver IpNetDriver_13, IsServer: NO, PC: NULL, Owner: ValkyrieBeaconClient_13, Uniqu
Expected Result
- No issues should happen.
- Variant A and B should both just cancel automatically on round end.
- The docs should explicitly state that
SubscribeRoundEnded
is also cancelling the subscription and not preserving the callback after the round ends!
Observed Result
- Variant A leaks the callback and keeps running during the remaining simulation.
- Variant B freezes the server on round end.
Platform(s)
PC