BindToClose - How exactly does it work?

(Note: There has been a topic with the same question in 2021 but I still need clarification. )

The Question:
I would like to know if anything that is run outside BindToClose is shutdown even though something within BindToClose is still running. The documentation does not emphasize this point, but it clearly says the server waits for the function within BindToClose.

If so I am facing the following problem:
Lets assume we have a function “._FinalSave(plr)” which is called when a player leaves and takes about 20 seconds to finish.
Now the last two players in the server leave almost at the same time. We could call the last player’s _FinalSave within BindToClose but not the second last, because at the time we wouldn’t know the last player would leave right after.
So the FinalSave of the player leaving first is not “protected” by bind to close and could be shutdown.
I considered queueing but ultimately there is no way around this other than calling any unfinished FinalSave again inside BindToClose - which seems incredibly complicated the way I organized my code.

Concluding, I would like a definite answer wether everything outside BindToClose is doomed the second the last player leaves or kept as long as BinToClose is still busy.

Does anyone know more about this?

Thanks :smiley:
(To clarify, as there wassome misunderstanding in the topic I referenced in the beginning, I do NOT care about extending this final grace period of 30 seconds. I only care about wether BindToClose keeps alive everything or just the stuff inside it)

3 Likes

not sure if i understood what you’re asking but hopefully this helps.

Everything else will still be available if your function needs it.

There is one main thread of the Luau VM, and Roblox ties it to the ScriptContext service. All of your other code threads, whether it be scripts, connections, or BindToClose are sub-threads - basically, there’s no way to close them without tearing down the entire Luau context.

So instead of doing that, Roblox won’t bother closing down other game threads, and instead just waits for BindToClose to finish, at which point it’ll close the entire Luau context all at once.

You can take advantage of this for your player data saving. This behaviour means that code in your connections for Players.PlayerRemoving will still run alongside the code in your BindToClose callback, and AFAIK this behaviour is consistent for any reason a player leaves (close or crash, etc.). Therefore, you don’t ever need to save data in your BindToClose callback, you just need to give your PlayerRemoving threads enough time to finish saving the player data.

In a live game, you’ll want to give it the full 30 seconds, but in studio, maybe give it about 3-5.

local RunService = game:GetService("RunService")

game:BindToClose(function(reason: Enum.CloseReason)
    task.wait(RunService:IsStudio() and 3 or 30) --3 in studio, 30 in game
end)
1 Like

That’s exactly what I wanted to know!
Thank you very much :smiley:

Kind of lost me there .. I think this is what you’re asking:

Nothing outside BindToClose is safe once shutdown starts. To make sure _FinalSave always runs, you’ll need to store pending player saves in a shared queue. Process and await that queue inside the BindToClose callback.

1 Like

Ok, now we have two somewhat contradictive answers and that’s exactly what I am trying to figure out. The sub-thread argument seems very reasonable to me, but I can’t find any definite answer in the docs.

Processing the save inside BindToClose would be a solution, but in my case would mean tons of work to properly handle saves that are in progress when BindToClose is called. Some parts of my save process may either be lengthy, not cancellable, non-idempotent or all of that.

It’s just a function that is called before server is shutting off for whatever reason. Since it’s a callback, not a signal (ex: game.BindToClose:Connect(), yes you can bind multiple functions, but they all run in same thread, unlike with signals, which are called in seperate thread), it can yield, useful to extend lifetime of a server before it shuts down, the server does have a wait time of 30 seconds for all binded functions to finish running.

Commonly used with DataStores to save every player data before server closes unexpectedly

1 Like

I know that :smiley:
The question is wether all other threads will continue as long as the BindToClose-Thread is active

Yes, because the server hasn’t shutdown yet, which is after all binded functions finished or 30s

1 Like

We are now at 2 for 1 :sweat_smile:

On the one hand it makes sense that other threads continue.
On the other hand it makes no sense at all, as we have access to BindToClose. If every thread were to stay active until all binded functions finished or 30 seconds are up, Roblox could simply give us a limit of 30 seconds - instead of access to BindToClose

Btw, I don’t mean to be combative if it sounds like that.

they are doing that, your binds cannot take longer than 30 seconds

and BindToClose receives a callback, how else would you know server is shutting down so you could fix / do something?

1 Like

Let me clarify:

Other running threads are not closed before BindToClose finishes. Threads that are dead will remain dead, as they would even if BindToClose was not “running”.

Luau code will function as before, you can still call your functions, etc. and currently running work on other threads will not terminate.

The Roblox C++ code will wait 30 seconds, or until all callbacks finish, before calling lua_close on the main thread, which tears down everything.

3 Likes

I will take this as the final answer :smiley:
But does my confusion make sense to you?
It seems redundant to have this BindToClose as a feature. It would make more sense for the docs to say “when the last player leaves, threads will continue for a maximium of 30 seconds”.

1 Like

I think the main goal here is Roblox keeping it open for the smallest amount of time possible. It’s easier for them to say “if you want to yield at close, you’ve got to tell us, and give us something to run” rather than just say “we’ll blindly run for 30 seconds more”.

It means memory and resources get freed quicker, which is likely why it’s done this way :wink:
(don’t quote me on that, it’s an educated guess)

1 Like

Think xHeptc hit this on the head. Totally how that works.

2 Likes

I can sleep now :joy: Still wish the docs were a little more precise. Mabye I just need to read up on luaus threading

Thank you all

1 Like

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