-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Possible race condition in WFULambdaRunnable #11
Comments
Interesting. That said I don't think the race condition you propose should happen. Line 17 increments a class scoped static uint64, not a member variable (that should really be changed to a thread safe counter or two threads could potentially share the same name). Also looking through the epic codebase for Windows thread creation (WindowsRunnableThread.h#L186 and WindowsRunnableThread.cpp#L68) it appears they use a sync event to ensure init gets called first and then only a thread kill signal (WindowsRunnableThread.h#L149) can cause a FRunnable::Stop call. Even if a stop call does happen, it only changes the FThreadSafeBool Finished state which is a member of WFULambdaRunnable not FRunnableThread. The WFULambdaRunnable member variables only get otherwise affected by the ~WFULambdaRunnable() destructor which gets exclusively called on Exit(). Looking at it now, it does seem like a leak is possible if the thread gets a Stop() call, since Exit() it doesn't get called in that instance (yay assumptions). This leaves the ~WFULambdaRunnable() destructor which thanks to WFULambdaRunnable.cpp#L24 line stops the destructor from doing anything useful at all (facepalm). Anyway despite all that I agree that init/destruct should be FCriticalSection scoped. Perhaps the better question is though, do we need anything apart from creating the lambda thread and cleanup? |
First of all: I made lot of mistakes in issue description. I'm really sorry, I did it late at night. Now i'v just edited the content so it could be more precise. Most important thing I was was wrong about is
You've right, I made mistake. It should be line 16, which is assigment of non-static member
That seems to be true, but mechanism you've described ensure only that
That's good question. If we could answer it positively maybe there is no need to store |
I was thinking about what wanted out of a Lambda thread and why I use the architecture rather than Thinking about use cases, having something with basic options and good defaults is probably the best way forward. FLambdaRunnable* FLambdaRunnable::RunOnBackThread(TFunction< void()> RunCallback, TFunction< void()> EarlyKillCallback = nullptr, EThreadPriority InThreadPri = TPri_Normal); something like that is what I'm leaning towards. Maybe an overload or a second convenience function with FLambdaRunnable* FLambdaRunnable::RunStoppableOnBackThread(TFunction< void(FThreadSafeBool& bShouldFinish)> RunCallback, TFunction< void()> EarlyKillCallback = nullptr, EThreadPriority InThreadPri = TPri_Normal); the boolean could be polled in the thread for graceful stop requests (useful if using run loops), we would feed in a reference to a threadsafe boolean member variable to make it work. Advantage of having it fed in vs context captured is that this would be self contained inside the First lambda gives you an option to handle early kill callbacks and do a quick cleanup in that event, but if your lambda doesn't need it you can ignore it, while the second variance also supports graceful loop exits. e.g. use cases FLambdaRunnable::RunOnBackThread([]
{
//simple lambda all data allocs may be stack allocated or handled elsewhere, no cleanup reqs
}); and FLambdaRunnable::RunOnBackThread([this]
{
//referencing scoped vars maybe in a long running op/loop
},
{
//got an early kill, let's cleanup as our op may have forcibly been killed
}); A pointer to thread should still be needed internally so we can request thread kills in cases where we need to quit immediately. FLambdaRunnable should then have both a clean Game thread lambdas would still use the taskgraph system since the gamethread should never stall (and task graph functions cannot take longer than 2-3 seconds or they stall the engine so this is appropriate). These are mainly used to communicate back from background threads. No change in behavior for that function, maybe just a rename. FGraphEventRef FLambdaRunnable::RunOnGameThread(TFunction< void()> RunCallback); Thoughts? |
In file WFULambdaRunnable.cpp from commit 5208dd878aaf51ba2520b26c1f2124bf6402f699 in line 16 takes place creation of thread.
Freshly created thread could end before execution of this line is done.
In such a case:
Stop
Exit
from line 56.Stop
Exit
destroys heap allocatedthis
object.1716, which is accessing memberThread
of just destroyedthis
object, that could lead to memory corruption.Possible solution is to use FCriticalSection to ensure atomic execution of
WFULambdaRunnable
constructor as well asStop
Exit
method.The text was updated successfully, but these errors were encountered: