Replication race condition when reparenting a model while a client is connecting

We recently implemented lobbies where we teleport a group of players into a mission. Upon the first player connecting, we parent a Folder to the Workspace. Only the first player will see the contents of this folder, even though it exists in the Workspace.

Adding a wait(5) before our parenting (to allow other clients to connect) fixes this race condition.

Here is what the first player to connect sees, notice the missing gate model:


This is what the second player (and third, fourth, …) see (no gates):

Another example, only the first player can see the cart. Player1:


We’ve printed (using the server log) the parent of the folder and it exists in the workspace. We parent it immediately after the first player connects, and other players are connecting simultaneously.

6 Likes

We investigated further and added a gui on the client and added a gui that polls workspace:FindFirstChild("MissionObjects")

On the client that does not see the objects:

On the client that does see the objects:

So we have confirmed it does not replicate to one or more clients.

1 Like

To lend credence to this as being more than some weird Lua-side quirk in how the above game is programmed, I can voice three prior times I experienced this bug:

About a year ago, we encountered a similar bug in Robloxian Highschool. After investigation into the server DataModel state, the client DataModel state, and the state of the actual scripts involved, we concluded that replication of reparenting a model must be breaking. If I recall correctly, we were reparenting a model from Workspace to ReplicatedStorage.

This bug was very inconsistent: some servers had it, most did not. In the RHS situation, we reparented the model on startup. If this bug is triggered by parenting a model while a client is connecting, then it explains why it happened rarely in RHS’s case: we weren’t parenting it on client connection, so a client had to connect at the right time by coincidence to trigger this bug. We were also unable to reproduce this in Studio, likely largely owing to the fact that we mostly tested with one player in Studio.

I have seen this bug in one other RedManta game and one personal project but I forget the details of both of those situations. I was not able to pin down an accurate repro in any of these situations, but I was able to isolate the bug to an internal replication issue with parenting objects near startup in all of them. The difficulty in reproing in all these cases likely came from it being a race condition on client connect, not on server startup, meaning that the parenting and client connection had to coincide to trigger this bug.

I wanted to post this bug in the past, but I always lacked a solid repro, a good theory as to the cause, and any solid place to test as a repro. Any time this happened, it happened rarely and we couldn’t leave the bug in the game as a repro. These anecdotes aren’t much better, but they certainly suggest that it’s an internal engine issue and not a Lua-side bug in all of these games.

3 Likes

There is a known issue with inconsistent non-Parent reference property ordering if it updates during a join (where the client can observe a temporary intermediate state that never existed on the server, before being corrected), but we have not observed missing objects before. If there is a place id that reproduces this issue regularly, or ideally if there is a stripped down place file that reproduces this issue, it would significantly help our ability to diagnose and fix.

1 Like

Is any of the work triggered from scripts in the PlayerGui, or is the work triggered by events fired when the player gui is copied?

1 Like

We are able to reproduce this issue pretty consistently with our lobby queueing, but it is not minimal.

When the script loads, we reparent our Missions folder in the Workspace to ReplicatedStorage. This is because our map contains multiple scenarios.

When the first player joins, we reparent a folder from ReplicatedStorage into Workspace. The player arrives with JoinData telling us which Mission they are playing, and we load this one by parenting it back into the Workspace.

Sometimes, the players who join after the first player will not see the parented folder (their client does not see it). We teleport them using separate TeleportToPrivateServer calls at the same time.

We have a consistent repro (made it happen 1 minute ago) but it is not a simple repro and requires running our entire game.

Is the place that reproduces this issue published? If so, could you send me the place id? With the id, I can start reading through scripts, which will help me try to create a small repro place.

Oh wow, I think I just got a repro with this script:

f = workspace.Folder
f.Parent = game.ReplicatedStorage
local first = true
game.Players.PlayerAdded:Connect(function ()
	if first then
		first = false
		f.Parent = workspace
	end
end)
4 Likes

Let me know if your repro is sufficient, otherwise we can look into our place! It’s published but the entire process of getting ingame involves a lot of steps as it’s our development environment.

We were definitely able to find a culprit for this behavior, and it is very likely to be the same issue you are seeing. Please let me know if you are also seeing these other behaviors:

  • when it happens, the folder is still in ReplicatedStorage for all clients that join after the first
  • as long as the root folder does not have any other changes (like name changes) players that join continue to get the folder in the old location
  • if you change some property of the folder (like its name) and then join with another player, the newly joining player sees the contents in workspace correctly
1 Like

We cannot locate the folder on the client where it is missing. We tried looking in the old location but don’t see it there either.

That being said, we do renaming / reparenting beforehand as a part of our setup so this might be interfering.

(We’re able to reproduce this very consistently though)

There is at least one way that I know of where the whole tree can become “lost” to the client. If the parent right before the first PlayerAdded (or whatever signal you are listening to) is something that is removed from the game or moved to a server-only container then that would cause the whole tree to be lost

f = workspace.Folder
Instance.new("Folder", game.ReplicatedStorage)
f.Parent = game.ReplicatedStorage.Folder
local first = true
game.Players.PlayerAdded:Connect(function ()
	if first then
		first = false
		f.Parent = workspace
		game.ReplicatedStorage.Folder.Parent = nil
	end
end)

We destroy the old parent right before the first PlayerAdded fires. Exactly this.

Awesome. We have a fix in review, it will go through the QA pipeline and we will tentatively be able to turn on the fix some time next week.

The fix was enabled earlier today, let me know if you are still seeing the issue

1 Like

We will be shipping this update today/tomorrow and will let you know if we still encounter it! Will remove our wait(5).