-
Notifications
You must be signed in to change notification settings - Fork 813
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
Fix leak calling [NSThread currentThread] when underlying thread created outside NSThread API #1872
Fix leak calling [NSThread currentThread] when underlying thread created outside NSThread API #1872
Conversation
99b69fd
to
40a8535
Compare
…ted outside NSThread API
40a8535
to
ed56adb
Compare
Also in CI build 😄 |
Frameworks/Foundation/NSThread.mm
Outdated
// Underlying thread created outside the NSThread APIs | ||
|
||
// So create a new instance | ||
currentThread = [[NSThread alloc] init]; | ||
[currentThread _associateWithCurrentThread]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it seems like this could be wrapped up into the current thread machinery. At file scope, a thread_local StrongId<NSThread>
, which _associateWith...
and _threadObjectFrom...
could refer to. That way, its lifetime would be managed by the same thing that keeps track of its current-threadiness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I'll include that + some cleanups in the next commit - I can see there's e.g. an unused pthread tls key in that file too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DHowett-MSFT I don't think non-POD file scope thread_locals are supported by this version of clang (c2?).
e.g. this is fine at file scope
struct Blah {
Blah() {
abort();
}
~Blah() {
abort();
}
};
thread_local Blah blah;
But even if thread_local worked the lazy approach avoids the static initialization/destruction overhead for every thread created outside the NS* APIs.
I also haven't confirmed but I think this means there's a leak from thread_local use in NSProgress: https://github.com/Microsoft/WinObjC/blob/develop/Frameworks/Foundation/NSProgress.mm#L50
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find and somewhat worrisome. Yes, with NSProgress.mm, there is a leak. s_currentProgressStack could become a thread local static inside _getProgressStackForCurrentThread much like you have done here.
|
||
// Ensure the reference grabbed above is | ||
// the only one remaining after thread exit | ||
EXPECT_EQ(1, [current retainCount]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I understand how this is correct. alloc init should add a reference and currentThread retain should add one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the body of the lambda we're not creating the NSThread via alloc init. We just get a non-owning reference by the call to |currentThread|. The call to |currentThread| does happen to create an owning reference (because it's called from a thread not created via the NSThread API), but this is relinquished on thread exit via the thread_local StrongId destructor.
I'll add some comments to the test case when I add in @DHowett-MSFT's suggested changes - likely sometime tonight. It might also be clearer to convert that test to ARC - but I had some difficulty attempting this initially.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rajsesh-msft there's a few more asserts in the unit test which should improve things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would really like to see the changes suggested by @DHowett-MSFT
43c6fb9
to
e4d5228
Compare
StrongId with a few cleanups and removal of pthread tls function use. Additional asserts in unit test.
e4d5228
to
d91e792
Compare
Frameworks/Foundation/NSThread.mm
Outdated
static std::mutex s_mainThreadMutex; | ||
static StrongId<NSThread> s_mainThread; | ||
static BOOL s_isMultiThreaded = NO; | ||
|
||
static NSThread* setOrGetCurrentThread(NSThread* thread) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the private functions should be _set* per coding conventions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rajsesh-msft I'll open a pull with the NSProgress change
Frameworks/Foundation/NSThread.mm
Outdated
// Underlying thread created outside the NSThread APIs | ||
|
||
// So create a new instance | ||
currentThread = [[NSThread alloc] init]; | ||
[currentThread _associateWithCurrentThread]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find and somewhat worrisome. Yes, with NSProgress.mm, there is a leak. s_currentProgressStack could become a thread local static inside _getProgressStackForCurrentThread much like you have done here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be great to get the nits addressed, but looks good otherwise.
Submitted these for CI build again. |
It looks like these never completed build, but they did submit status updates... @pradipd do you know what happened? |
Very timely @DHowett-MSFT. Take a look at #1937 that @pradipd just opened. |
facepalm |
…ng NSThread.isMainThread (but guard against case where main thread also does not exist)
@pradipd thanks for the test fix! I took the opportunity to push another change. No change in behavior - but this avoids creating a new thread-lifetime NSThread object unnecessarily (isMainThread is called frequently via UIView RunSynchronouslyOnMainThread) |
Back in CI build. |
Back in CI build again. Sorry @ehren! |
Thanks @ehren! |
This is an alternative for the naive leak fix in #1834 (which introduces a use after free).
I took a look at supporting the cdecl destructor argument of pthread_key_create but it looks like any fixes there would complicate the implementation of pthread_setspecific/pthread_getspecific. So this commit just calls the Fls functions directly from NSThread.