Running the following code in specifically Immediate signal mode will throw an error about the parent being locked.
local Part = Instance.new("Part")
Part.AncestryChanged:Connect(function(_, p)
if not p then
Part:Destroy()
end
end)
Part.Parent = workspace
Part:Destroy()
This code sample may not be perfect because why am I destroying an instance thats already being destroyed, but the issue is more with tables that track Instances, this code sample may better show the issue:
function Object.Destroy(self: Object)
local private = ObjectBindings[self]
local inst = self.Instance::Instance
if not private or not inst then return end
local impl = Implementors[self]
Implementors[self] = nil
impl.Objects[inst] = nil
ObjectBindings[self] = nil
InstanceBindings[inst] = nil
if self.Component then
self.Component:Destroy()
end
clearMemoryState(private.Weak)
if impl.Destroying then impl.Destroying(self) end
clearMemoryState(private.Weak)
end
This function is triggered internally when a collectionservice tag is disposed of, destroying an object or parenting to nil unallocates a tag, so it triggers a full destruction.
There’s no way to tell if this was triggered by a Destroy call, so we have to assume its not. It will throw the error if it was destroyed elsewhere.
Expected behavior
If an instance is already being destroyed, then future Destroy calls should not try to move the parent, even if Destroy was called mid-Destroy (through an event).
The parent should only be set to nil once.
This error also does not arrise in deferred signalling mode for obvious reasons.
Workaround (works for me)
I found wrapping the destroy call in a task.defer
fixes the issue in immediate mode, obviously I cant identify which signalling mode is being used, but it would be nice to read this for frameworks, though this is not for this bug report