Instance:Destroy() Only Replicates Parent = nil Change

The expected behavior of Instance:Destroy() is “Sets the Parent property to nil, locks the Parent property, disconnects all connections and calls Destroy() on all children.” according to the wiki. If called on a server, when using the client, the first part follows as expected with the parent being changed, but the second parts won’t follow. This means that connections aren’t disconnected, and the instances can be reparented. The follow code can be used to illustrate this with a baseplate file, with or without experimental mode enabled.

Server code (in Workspace or ServerScriptService):

local DestroyOnServer = Instance.new("RemoteEvent")
DestroyOnServer.Name = "DestroyOnServer"
DestroyOnServer.Parent = game.ReplicatedStorage

local Baseplate = game.Workspace:WaitForChild("Baseplate")
DestroyOnServer.OnServerEvent:Connect(function()
	Baseplate:Destroy()
	wait(0.25)
	Baseplate.Parent = game.Workspace
end)

Client code (in PlayerGui or StarterPlayerScripts):

wait(0.5)
local Baseplate = game.Workspace:WaitForChild("Baseplate")
game.ReplicatedStorage:WaitForChild("DestroyOnServer"):FireServer()
wait(0.5)
Baseplate.Parent = game.Workspace

if Baseplate.Parent == game.Workspace then
	print("Reparent complete")
end
50 Likes

I encountered this issue recently, in an issue I was having with some object wrappers. Eventually it ended up working for me, but for some reason it had this buggy behaviour at one point.

4 Likes

This is still the behavior, which seems like a pretty big problem as the only way to automatically remove event connections is through Destroy() and those connections can cause memory leaks otherwise.

This behavior can be demonstrated through this snippet courtesy of @Halalaluyafail3:

Server

wait(3)
workspace.Baseplate:Destroy()

Client

local b = workspace.Baseplate
local c = b.ChildAdded:Connect(function()end)
wait(5)
print(b.Parent)
print(c.Connected)

Output:
image

Manually keeping track of connections and somehow determining that an object has been destroyed on the server from the context of the client is both impractical and a major nuisance.

If this is an intended feature then we probably need some sort of event to indicate that an object has been destroyed on the server, so that the client can also call Destroy() on it.

17 Likes

This bug has still not been squashed. As of right now, this can cause memory leaks without any obvious reason as to why. It appears that parts with connections may still have them disconnected if they GC, but if a reference to the part exists anywhere (even within its own connection) it will stay in memory indefinitely.

10 Likes

I got some discussion moving on this, a ticket has been created for it.

No guarantees that it’s possible, since it would be a behavior change that could break code. We would have to investigate exactly how much code it would break before considering going through with it.

23 Likes

I’d say the expected behavior is that Destroy should always set parent to nil of all descendants and disconnect events. The fact that this isn’t the case probably breaks more code than if it worked as expected. Lots of memory leaks out in the wild right now because we thought this was the behavior already.

57 Likes

I would strongly agree with this. If you check this thread about detecting when instances are destroyed Instance.Destroyed Event - Feature Requests / Engine Features - DevForum | Roblox it’s quite clear that a large number of people - and up until now, myself - are using Parent == nil expecting it to be a reliable way to detect the destruction of an instance.

If this issue isn’t going to be fixed, we really do need an actual method for detecting when an instance is destroyed, as having to rely on inconsistent and undocumented behaviour to perform such a basic and integral check is not acceptable.

50 Likes

Agreed with the above two posts. I can’t imagine any games use this purposefully, and I absolutely think we’d see an across-the-board improvement in memory usage if this was fixed.

9 Likes

Fully support fixing this bug!!

Just enable it in studio early for a couple weeks to make sure nobody relies on this :slight_smile:

16 Likes

I’d love to see a solution if not a perfect one. After finding out about this I realized it actually has a big impact on… Maybe 70% of the server sided code I’ve ever written :flushed:

I always assume when I call :Destroy() any connections on that object are deleted regardless of it being on the server or client.

I knew that instances were not destroyed on the client when it was originally done on the server but I never realized this would cause a memory leak, and, its really counter intuitive as to how that could cause memory leaks.

I think the best way to fix this issue is likely to simply not lock the instance when its destroyed over the network. For better compatibility, descendants can have their events disconnected but not their parents changed. The effect is server-destroyed instances have all events disconnected on the client but nothing else changes.

I know there’s more to the :Destroy() call than just disconnecting events (I think there’s like some weird magic going on because destroyed instances that didn’t have event connections take up literally like 100x less memory)

That would mean the server has to send when a specific instance gets destroyed (which I mean, makes it easier for a Destroyed event to exist too :wink:)

This would fix the memory leak issue around this without likely causing many issues. The only difference would be that events would stop working.

I would not think that a pattern like that is used at all because it would be harder to use than not, your game would have to rely on the existence of an event for an object (which is in nil, so, not a button or user input thing, not a part, etc) :Destroy()ed from the server or an object :Destroy()ed from the server that has been reparented to the workspace. (The first being a really specific situation that I would think is already questionable for someone to end up using and the second being less unlikely but still pretty unlikely)

(If breakage is a concern adding a temporary fast flag is always an option so it can be quickly reverted)

Lastly, an example of this effecting me is my game’s entity code. It probably suffers from this because the server notifies the client of an entity creation (but not deletions) and tells the client what module to load and the instance for the entity (as well as any data for the entity state). A bunch of the different entities in my game connect to remote events and connect to various property changes and things under the entity’s instance. When the entity is destroyed on the server, the connections would stay active on the client because I assume they’ll be disconnected if it gets deleted.

I can easily fix this by telling the client when an entity is destroyed and having the client perform the same call, but, it’s still less than ideal, and, its not at all clear to probably anyone that they should be doing this. (It’s not mentioned on the devhub for the Destroy documentation and I never realized this problem)

4 Likes

Just to update, I haven’t been following the details, but there is someone actively working on this problem right now, so there may be progress on it one way or the other soonish (either some solution for it or a decision that there’s no feasible fix that doesn’t break too many scripts).

25 Likes

Seems like the issue is still present.

The code to solve this has actually started shipping but it turned out to be a fairly complicated issue to solve properly so it’s taking some time. Instance.Destroying which shipped recently but isn’t enabled yet is part of the solution.

14 Likes

Will Instance.Destroying (when it is enabled) fire if a instance has been destroyed physically and not through code? For e.g it falling off the world and kaboomed once it’s height reaches Workspace.FallenPartsDestroyHeight.

It’ll fire regardless of where the Destroy came from (the network, Lua code, or C++ engine code).

And engine code always uses Destroy where appropriate. Though it should be noted that there are some cases where Destroy is not used, mostly in the case of Studio commands (for instance the Delete key in Studio does not Destroy things because you could not undo their removal if it did).

7 Likes

Seems like, through this post, a new event has been added for instances.

Instance.Destroying
This fires when the instance is destroyed, and not parented to nil

https://developer.roblox.com/en-us/api-reference/event/Instance/Destroying

edit: doesnt seem functional yet :frowning:

Hi all,

We’re going through and closing tickets, and just wanted to note this is fixed here:

Thank you for your report. These sort of reports can take a while to fix, but make a big difference. Please keep reporting stuff. It really matters. :sparkling_heart:

3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.