LoadCharacter 100% server crash

game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		char:GetPropertyChangedSignal'Parent':Connect(function()
			if not char.Parent then plr:LoadCharacter()end
		end)
	end)
	plr.Chatted:Connect(function(msg)
		plr:LoadCharacter()
	end)
end)

If you put this inside of a server script in an empty baseplate and disable Players.CharacterAutoLoads then send two messages in the Roblox chat, the game will emit “StartingProcessException” and exit

This happens in studio and in live games

5 Likes

I’ll take a look into fixing this crash. What functionality were you trying to achieve here? Are you blocked from what you want to do?

2 Likes

My spawner module spawns characters after they die after X seconds
In my game right now I have the respawn time set to 0

This is the snippet that respawns characters:

_G.died:bind(function(plr,char)
	wait()--necessary to avoid https://devforum.roblox.com/t/loadcharacter-100-server-crash/358804 with external LoadCharacter calls
	if md.respawntime~=1/0 then
		if md.respawntime>0 then wait(md.respawntime)end
		if plr.Parent and plr.Character==char then
			pcall(plr.LoadCharacter,plr)
		end
	end
end)

_G.died is basically a bindable event that fires when a character ‘ought to die’
It fires when the humanoid reaches 0 health (when Humanoid.Died fires), but also in other scenarios such as when the character is parented to nil (which occurs when the character is destroyed by reaching workspace.FallenPartsDestroyHeight)

So since it fires in the second scenario mentioned, it fires when LoadCharacter is also called externally, which means if the wait() is removed, the crash occurs

For example, I call LoadCharacter in my admin commands:

cmds.respawn={
	level=2,
	args={'?plrs',},
	func=function(auth,plrs)
		for _,plr in ipairs(plrs or{auth,})do
			pcall(plr.LoadCharacter,plr)
		end
	end,
}

So the two solutions I could think of without your patch are that I either add a wait() or I call my own wrapped LoadCharacter function which would just have a debounce to ensure LoadCharacter isn’t nested called:

local spawn do
	local spawning={}
	spawn=function(plr)
		if spawning[plr]then return end
		spawning[plr]=true
		pcall(plr.LoadCharacter,plr)
		spawning[plr]=nil
	end
end

But this second solution (while the best), requires me to change my code everywhere LoadCharacter is used
If you don’t decide to patch this crash, could you let me know so then I will switch to the second solution permanently?
Also, do you know when (if at all) LoadCharacter errors? The reason I pcall my calls is because some point in the past LoadCharacter would error; is this fixed?

@RuizuKun_Dev You can see its not called ‘again and again and again’ by adding a print inside that if statement and observing it only prints once
I don’t understand “Why aren’t you using .AncestryChanged and call :LoadCharacter() somewhere else” this is a bug report and I’m showing how it’s possible to crash Roblox
And I’m calling it when the player chats so you can have control over when the player crashes, how would you have written a repro for this?

I believe this may be related to my bug report here. (The second time the character is loaded, .CharacterAdded is fired two times because two characters are created. One is parented to nil).

Essentially, the crash takes place the second time because the first character is created, the .CharacterAdded event is fired, and char is parented to nil. This triggers your anonymous function containing the if not char.Parent then plr:LoadCharacter()end logic. Since the parent of char is nil, the character is reloaded. This causes an infinite recursion because the same logic occurs again, causing the crash.

Does this sound correct to you? I am currently attempting to prove this in a dev environment and I’ll update this post with my results.

Edit: perhaps not exactly.

The first POC is causing the crash because of what I said above (the logic runs when the character parent is nil, and this recursively happens), however I am not sure if this is because of my bug or not.

Anyhow, observe:

game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		char:GetPropertyChangedSignal'Parent':Connect(function()
			if not char.Parent then wait() plr:LoadCharacter()end
		end)
	end)
	plr.Chatted:Connect(function(msg)
		plr:LoadCharacter()
	end)
end)

…will not crash, instead the character will be infinitely respawned (wait() added to logic).

Can you confirm on your end with all of your implementations @acreol?

Your code snippet with the wait() added will infinitely respawn (that’s why in my game’s _G.died:bind I check to make sure plr.Character==char before calling LoadCharacter after a possible yield)

But why do you believe there is an infinite recursion happening? If you try adding a print inside of the if statement as I pointed out in my reply to @RuizuKun_Dev, it’s only outputted once:

game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		char:GetPropertyChangedSignal'Parent':Connect(function()
			if not char.Parent then @@print'a'@@ plr:LoadCharacter()end
		end)
	end)
	plr.Chatted:Connect(function(msg)
		plr:LoadCharacter()
	end)
end)

(Alternatively if you want to keep a calls variable and stop calling LoadCharacter once it exceeds some finite number, the crash still occurs)

I am basing my statement on my observation of the control flow and outcome of adding a delay. The parent property is changed and if not char.Parent will always be true, so Player:LoadCharacter will be recursively called. It is only printed once because it crashes before it can print it again (recursively calling Player:LoadCharacter doesn’t trigger the exception, so it is probably something more internal which the character recursively being re-loaded without delay causes, based on my conjecture). It will, however, keep printing if your logic is modified to print'a' wait() plr:LoadCharacter (and infinitely at that).

Am I missing something in my logic?

I’m not sure if we should hypothesize on how LoadCharacter were to behave if the ‘internal crash’ were fixed, but let’s do it anyways xd:

This code demonstrates that LoadCharacter parents the old character to nil (and not destroys) and then adds a new character
game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		print'added'
		char:GetPropertyChangedSignal'Parent':Connect(function()
			if not char.Parent then
				print'removed'
				wait()
				print(pcall(function()char.Parent=workspace end)and'parented to nil'or'destroyed')
			end
		end)
	end)
	plr.Chatted:Connect(function(msg)
		plr:LoadCharacter()
	end)
end)

With this being said, I believe that there are three possibilities on how a nested LoadCharacter call behaves (given this bug report is patched so no internal crashing occurs):

1. the nested call is ignored as my debounce is implemented and no infinite loop occurs
local spawn do
	local spawning={}
	spawn=function(plr)
		if spawning[plr]then return end
		spawning[plr]=true
		pcall(plr.LoadCharacter,plr)
		spawning[plr]=nil
	end
end

game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		char:GetPropertyChangedSignal'Parent':Connect(function()
			if not char.Parent then spawn(plr)end
		end)
	end)
	plr.Chatted:Connect(function(msg)
		spawn(plr)
	end)
end)
2. the nested call succeeds the parent and the parent call is canceled (which would have the same behavior as the following)
game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(msg)
		plr.Character.Parent=nil--parent call remove behavior
		pcall(plr.LoadCharacter,plr)--nested call fulfills loadcharacter
	end)
end)
3. the nested call succeeds the parent and the parent call is not canceled (which would have similar behavior to the following)
game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(msg)
		plr.Character.Parent=nil--parent call remove behavior
		pcall(plr.LoadCharacter,plr)--nested call fulfills loadcharacter
		--parent call create and add character would behavior similar to this:
		local char=createnewplayercharacter()--how?
		plr.Character=char
		getevent(plr.CharacterAdded):Fire()--how?
		char.Parent=workspace
	end)
end)

I’m not sure about the parent call second step, do you know more about it?

But in all three scenarios no recursion happens

Or did I completely misunderstand what you said, and you only claim that with wait() infinite recursion happens? (which I agree with because then the code can connect to Parent signal always before a LoadCharacter call happens)

1 Like

From what I can tell, you have mostly understood me, I believe I have understood you too.

Addressing the rest of your post, it will be interesting to see once the issue is fixed. I’ll probably loop back to you afterwards to prevent bloating this thread while the issue still exists (especially since it’s just conjecture on my behalf based on my observations).

Cheers.

1 Like

It appears that if LoadCharacter is called at the exact moment it changed the parent of the just-created character, the server crashes from a logic error. However, calling LoadCharacter directly from CharacterAdded results in an infinity loop.

local player = game.Players:GetPlayers()[1] or game.Players.PlayerAdded:Wait()

-- Wait for the first character spawn.
player.CharacterAdded:Wait()

-- Connect the weirdness.
player.CharacterAdded:Connect(function(char)
	-- Wait for the wrong time. This is fired almost-immediately after CharacterAdded.
	char:GetPropertyChangedSignal'Parent':Wait()
	
	-- The currently-running LoadCharacter call is still parenting the current character. Oops.
	player:LoadCharacter()
end)

wait(8)

-- Trigger the weirdness.
player:LoadCharacter()

Hi all, @acreol’s example is behaving “normally” as it is written–It is not written correctly, and there is a
piece of this that also relates to @byc14’s issue. Please note these changes to his example:

game:GetService'Players'.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
      --[[ Note: char.Parent can be nil here--wait() if non-nil Parent expected ]]
		char:GetPropertyChangedSignal'Parent':Connect(function()
			if not char.Parent then wait() plr:LoadCharacter()end
		end)
	end)
	plr.Chatted:Connect(function(msg)
	--[[ Note: Example assumes  game.Players.CharacterAutoLoads = false
	    so on Player-Add there is no Character, so LoadCharacter() works.
	    *But* LoadCharacter() *triggers* a CharacterAdded Event, so once you
	    already have a Character, the PropertyChangedSignal will ALSO
	    fire from a LoadCharacter() call because the previous Character gets
	    parented to nil, which then in the PropertyChanged handler calls
	    LoadCharacter() again!--You get the picture.
	    So once you have a Character, just Destroy()ing it will trigger your
	    PropertyChanged signal and call LoadCharacter() for you.
	    Also Note: I *do not* suggest that LoadCharacter() be called from
	    a Character.Parent PropertyChanged handler!
	]]
		if plr.Character  then		--------
			plr.Character:Destroy()	--------
		else						--------
			plr:LoadCharacter()
		end						--------
	end)
end)

For this issue, we’re going to be fixing the crash, but there will still be warnings/errors if you try to start a character load when you already have one in progress. So after doing LoadCharacter(), another one on the same player should not take place until at least after the CharacterAppearanceLoaded event fires

2 Likes

Why will a warning be outputted?

Because if you have started a player’s spawning process with LoadCharacter(), you shouldn’t be calling it again until the process is finished. Ideally the function would handle being called when it’s mid-process, but right now it’s not designed to handle that

1 Like

a fix has gone live for this crash

6 Likes

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