Skip to content
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

Merged
merged 7 commits into from
Mar 3, 2017

Conversation

ehren
Copy link
Contributor

@ehren ehren commented Jan 30, 2017

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.

@ehren ehren force-pushed the external-currentthread-leak branch from 40a8535 to ed56adb Compare January 30, 2017 01:56
@DHowett-MSFT
Copy link

Also in CI build 😄

// Underlying thread created outside the NSThread APIs

// So create a new instance
currentThread = [[NSThread alloc] init];
[currentThread _associateWithCurrentThread];

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

Copy link
Contributor

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]);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

Copy link
Contributor

@rajsesh rajsesh left a 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

@ehren ehren force-pushed the external-currentthread-leak branch from 43c6fb9 to e4d5228 Compare February 1, 2017 01:04
StrongId with a few cleanups and removal of pthread tls function use.
Additional asserts in unit test.
@ehren ehren force-pushed the external-currentthread-leak branch from e4d5228 to d91e792 Compare February 1, 2017 06:34
static std::mutex s_mainThreadMutex;
static StrongId<NSThread> s_mainThread;
static BOOL s_isMultiThreaded = NO;

static NSThread* setOrGetCurrentThread(NSThread* thread) {
Copy link
Contributor

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.

Copy link
Contributor Author

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

// Underlying thread created outside the NSThread APIs

// So create a new instance
currentThread = [[NSThread alloc] init];
[currentThread _associateWithCurrentThread];
Copy link
Contributor

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.

Copy link
Contributor

@rajsesh rajsesh left a 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.

@DHowett-MSFT
Copy link

Submitted these for CI build again.

@DHowett-MSFT
Copy link

It looks like these never completed build, but they did submit status updates... @pradipd do you know what happened?
I'll re-queue them in the mean time, but it was a week ago...

@rajsesh
Copy link
Contributor

rajsesh commented Feb 9, 2017

Very timely @DHowett-MSFT. Take a look at #1937 that @pradipd just opened.

@DHowett-MSFT
Copy link

facepalm
I saw @pradipd's pull request, but didn't put two and two together. Thank you!

…ng NSThread.isMainThread (but guard against case where main thread also does not exist)
@ehren
Copy link
Contributor Author

ehren commented Feb 13, 2017

@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)

@rajsesh
Copy link
Contributor

rajsesh commented Feb 14, 2017

Back in CI build.

@DHowett-MSFT
Copy link

Back in CI build again. Sorry @ehren!

@DHowett-MSFT DHowett-MSFT merged commit 970bfa9 into microsoft:develop Mar 3, 2017
@DHowett-MSFT
Copy link

Thanks @ehren!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants