New Player and Character Destroy Behavior

Hello Creators!

Today, we’re adding a new opt-in behavior that automatically destroys Player and Character Instances on the server when the player leaves or respawns.

We will be rolling this feature out in three stages:

1. Today: PlayerCharacterDestroyBehavior is set to Default, which behaves the same as Disabled, the new behavior is now opt-in.
2. Early 2024: Default behavior is updated to behave the same as Enabled, the new behavior is now opt-out.
3. Mid 2024: We will remove the PlayerCharacterDestroyBehavior property and it will no longer be possible to opt out.

Current Behavior

Currently, when a player disconnects from an experience, the parent of the Player Instance is set to nil without the Player Instance being destroyed. This is also the case for character Models. When the character despawns, the Model is not destroyed but rather parented to nil.

Problems With This Current Behavior

The problem with this behavior is that over the lifetime of an Experience server, players may leave, join, and respawn thousands of times. If any references to these player or character Instances are maintained in your code, such as references to connections that haven’t been disconnected, these Instances will not be garbage collected and a memory leak might occur causing your experience’s servers to crash.

Since connecting to player or character events is extremely common, there’s a high likelihood that your project is impacted by this issue.

If you are interested in learning more about this, we recommend you read @stravant excellent write up: PSA: Connections Can Memory Leak Instances.

New Opt-In Behavior

To help resolve this issue, we are introducing a new property to Workspace - PlayerCharacterDestroyBehavior. You can enable this property from the Explorer Window.

When this property is set to Enabled, the following behavior occurs:

  • When a player leaves the experience, the Destroy method is called on the Player Instance. This task is deferred and occurs after bound event connections are invoked, including PlayerRemoving.
  • When a player’s character is removed, the Destroy method is called on the Model Instance. This task is deferred and occurs after bound event connections are invoked, including CharacterRemoving.

This means any event connections bound to Player/Character Instances and their descendants are disconnected when they are no longer required. This is an important mitigation that will prevent the accumulation of server side leaks in your experience.
Keep in mind that this Destroy call is performed automatically only on the server and not on the client, but replication rules still apply.

What to Look Out For

Although we do not anticipate any impact to conventional script patterns, there are potential scenarios that are “broken” by this behavior.

Generally speaking, any code that expects to access the children of a player or character after they have been removed will be impacted. Additionally, any code that attempts to re-parent a destroyed child instance or character model will also fail as the Destroy method locks the Parent property of an Instance.

Note that only code that executes after the PlayerRemoving/CharacterAdded signals have finished being invoked is impacted, for example code that is delayed using task.wait or task.delay.
It is however still safe to call WaitForChild on children that are present at the invoke time; such calls can also be replaced with FindFirstChild.

Example:

-- Broken by PlayerCharacterDestroyBehavior = Enabled

Players.PlayerRemoving:Connect(function(player)
    task.wait(1) -- Because we are deferring, this code will not work
    local points = player.leaderstats.Points.Value
    print(`player.Name left with ${points} points!`)
end)

-- Still works, even with PlayerCharacterDestroyBehavior = Enabled
Players.PlayerRemoving:Connect(function(player)
    local points = player.leaderstats.Points.Value
    print(`player.Name left with ${points} points!`)
end)

Please note, PlayerCharacterDestroyBehavior is currently opt-in, meaning the Default property behaves the same as Disabled.

Next Steps

We recommend creators take the following steps:

  1. Evaluate your code for any deferred operations on Player or Character Instances after the player has left or the character has respawned. If you have any of these operations, consider updating them.
  2. Set PlayerCharacterDestroyBehavior to Enabled and playtest your experience to see if you can identify any issues.
  3. If all looks good, publish your experience and let us know if you have any problems.

We encourage you to try out the new behavior to improve the performance of your experience. Please share any questions or feedback in the comments below, as we’re eager to hear about your experience. Thank you!

291 Likes

This topic was automatically opened after 10 minutes.

I have been thinking about this memory leak a lot lately, and here are my questions:

  • Can I fix such memory leaks caused by PlayerAdded and PlayerRemoving events by opting in to this change?
  • Will there be a reduction in server memory usage?
    • (for my games, it tends to start lower when I shut down all servers, and then increases up to a certain level)
35 Likes

This is quite a nice change, but…

Does this mean that code like this will be broken, when saving after a player leaves (either all the time or if UpdateAsync fails?)


I think this is kind of conventional? anyway,
It is easy to fix, but this could affect a lot of experiences, and if those experiences aren’t maintained, well, broken datastore system…?

EDIT

Awesome :smiley:

52 Likes

wow im so happy, so cool in my opinion

28 Likes

this is how it should have behaved in the first place. cool update

34 Likes

When this property is set to Enabled, and a player leaves an experience while they are spawned in, does Destroy also get called on their Character, if it exists?

26 Likes

Player itself is still accessible and if you happen to have stored instance references to child instances, those will be valid as well (but without any children).
Properties, including UserId are still valid and will contain original values, even after taking a delay.

37 Likes

So to clarify, player children won’t be accessible but the player instance and properties (userid, etc) itself will be accessible (assuming for a bit before its gone)?

19 Likes

Yes.
And ‘a bit’ is ‘until existing strong references remain’.

There is still a chance that custom code will hold onto some instances for a long time; our hope in that case is that server will at least have an opportunity to collect hundreds of other child instances, freeing most memory.

25 Likes

Will the destroy signal/event be pinged when the character is being destroyed this new way.

17 Likes

Yes, Destroying signal is still triggered just before the Instance is actually destroyed.

21 Likes

So all this time, every respawned character was being buried in a metaphorical graveyard under nil, instead of a full purge?

21 Likes

I was already doing this manually in my games, but having it happen auto-magically is a nice change. I’ve suspected that a lot of the server crashes I see complained about here were related to this but unless someone has been coding since the old days; it’s hard to explain good memory management here. :face_with_raised_eyebrow:

17 Likes

I would suspect that yes it would because of those asynchronous calls you have in it. It looks like when the player leaves, the server has one shot of doing the work and anything that delays (task wait, async, etc.) will be ignored and the player object deleted if I’m reading the technical info correctly here. :face_with_raised_eyebrow:

14 Likes

Wow, didn’t even know this was a problem. I always thought that they were :Destroy()'d. Good to know I have TONS of memory leaks that need fixing.

19 Likes

What’s funny about this is just yesterday I noticed that the parent of the character gets set to nil, so I made a system to manually destroy them. lol

Nice update, hope this will fix memory leaks!

13 Likes

Does this change apply to the Player:LoadCharacter method? My game has a custom character loader and I currently destroy the old character manually.

14 Likes

It’s so nice to see that work is finally going into correcting this stone age issue and that the behaviour will become default. There’s virtually no reason at all for players and characters not to get destroyed when they’re no longer in use and this missing contract caused a lot of obscure and hidden bugs with developer code if not applying heavy workarounds.

I’ll be enabling this immediately and experimenting across all my projects upon their next updates. Players and characters are some of the most memory-hogging features in my experiences and I would not be surprised to learn that some of my memory consumption over time is related to both.

16 Likes

THANK YOU. As someone who used to experience server crashes, this is great news.

Although, is there any news about the memory leak that occurs when models (or in my case, vehicles) are destroyed (even when there are no variables holding the object)? Here’s a post about it:

19 Likes