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

Reimplement ASRectTable using unordered_map to avoid obscure NSMapTable exception. #719

Merged
merged 3 commits into from
Dec 22, 2017

Conversation

appleguy
Copy link
Member

@appleguy appleguy commented Dec 18, 2017

The new class is called ASRectMap, which patterns alongside ASIntegerMap in both name and implementation.

Here's the exception, which occurs about 50 times a day in my app, when invoked from the only current caller (ASLayout):

"*** -[NSMapTable initWithKeyPointerFunctions:valuePointerFunctions:capacity:] Requested configuration not supported."

After some pretty detailed investigation, including study of open-source reimplementations
of Foundation, the best lead I've found on the NSMapTable exception is that
some NSPointerFunction types are not fully supported. Strangely, the ones being used
do seem to work fine almost all of the time.

The main concern is the Struct memory type, which is not officially re-declared in
NSMapTable, and as such the documentation claims that there may exist some
combinations of NSPointerFunction that are not supported. One substantiation for this is that crashes are disproportionately more common since 11.1.2, suggesting there might be a framework change to the concrete subclass in how it handles these flags. Unfortunately, I have not been able to reproduce, but I have ruled out many possibilities such as devices low on memory.

Because the exception is occurring frequently enough to be a concern (in the hundreds
to low thousands, though only 50 a day) - I decided to replace NSMapTable entirely
in order to ensure full correctness.

This does lose some features of the original - namely, strong retaining of keys. However, this feature was unused and the class is private. Since the previous implementation is operating in a grey area of NSMapTable's API, switching to unordered_map seemed like the right tradeoff.

@appleguy appleguy self-assigned this Dec 18, 2017
@ghost
Copy link

ghost commented Dec 18, 2017

🚫 CI failed with log

…le exception.

The new class is called ASRectMap, which patterns alongside ASIntegerMap in both name and implementation.

After some pretty detailed investigation, including study of open-source reimplementations
of Foundation, the best lead I've found on the NSMapTable exception is that
some NSPointerFunction types are not fully supported. Strangely, the ones being used
do seem to work fine almost all of the time.

The main concern is the Struct memory type, which is not officially re-declared in
NSMapTable, and as such the documentation claims that there may exist some
combinations of NSPointerFunction that are not supported.

Because the exception is occurring frequently enough to be a concern (in the hundreds
to low thousands, though only 50 a day) - I decided to replace NSMapTable entirely
in order to ensure full correctness.

"*** -[NSMapTable initWithKeyPointerFunctions:valuePointerFunctions:capacity:] Requested configuration not supported."
@ghost
Copy link

ghost commented Dec 18, 2017

🚫 CI failed with log

@appleguy
Copy link
Member Author

This builds fine on my local machine, and the tests execute. I think the build server has some kind of caching problem where the file rename is not being picked up correctly. Any suggestions?

Copy link
Contributor

@maicki maicki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Straight forward implementation. Looks good to me!

#import <unordered_map>

@implementation ASRectMap {
std::unordered_map<void *, CGRect> _map;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may should add a comment that we use a void * key here as arc would not be able to determine the ownership qualification.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we not use __unsafe_unretained id as the key type here?

Copy link
Contributor

@maicki maicki Dec 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I don't think we can use __unsafe_unretained id as key. The unordered_map template cannot figure out the hash for the __unsafe_unretained id key. We would have to write our own key like:

struct ASRectMapMapKey {
    __unsafe_unretained id obj;
    
    bool operator==(const ASRectMapMapKey &other) const
    {
        return [obj isEqual:other.obj];
    }
};

namespace std {
    template<> struct hash<ASRectMapMapKey>
    {
        size_t operator()(const ASRectMapMapKey &k) const
        {
            return [k.obj hash];
        }
    };
}

And we could use it via:

std::unordered_map<ASRectMapMapKey, CGRect> map;
map.insert({{@"key"}, CGRectMake(0, 0, 100, 100)});
NSLog(@"%@", NSStringFromRect(map.at({@"key"})));


// { ptr1->rect1 ptr2->rect2 ptr3->rect3 }
NSMutableString *str = [NSMutableString string];
for (auto it = _map.begin(); it != _map.end(); it++) {
Copy link
Contributor

@maicki maicki Dec 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also use of a bit of a smaller code:

for ( auto const& entry : _map ) {
    [str appendFormat:@" %@->%@", entry->first, NSStringFromCGRect(entry->second)];
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this method dangerous, now that the keys are stored unretained? Could we be calling -description on a garbage pointer? We could fall back to %p.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @Adlai-Holler. Let's pay extra attention here.

Copy link
Member

@Adlai-Holler Adlai-Holler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! A few notes and then let's go.

#import <unordered_map>

@implementation ASRectMap {
std::unordered_map<void *, CGRect> _map;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we not use __unsafe_unretained id as the key type here?


// { ptr1->rect1 ptr2->rect2 ptr3->rect3 }
NSMutableString *str = [NSMutableString string];
for (auto it = _map.begin(); it != _map.end(); it++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this method dangerous, now that the keys are stored unretained? Could we be calling -description on a garbage pointer? We could fall back to %p.

}
[result addObject:@{ @"ASRectMap": str }];

return result;
Copy link
Member

@Adlai-Holler Adlai-Holler Dec 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name will already be in the description by default, so I think what we want here is @{ (id)kCFNull : str } which will just dump the list of pointers inside e.g.: <ASRectMap: 0xFFFFFFFF; "a->b, c->d">

Edit: NM I see you used MakeWithoutObject so we exclude the pointer from the description a la other collection classes 👍


// { ptr1->rect1 ptr2->rect2 ptr3->rect3 }
NSMutableString *str = [NSMutableString string];
for (auto it = _map.begin(); it != _map.end(); it++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @Adlai-Holler. Let's pay extra attention here.

// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update this file license per Danger's comment.

//
//  ASRectMap.mm
//  Texture
//
//  Copyright (c) 2017-present, Pinterest, Inc.  All rights reserved.
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//

@ghost
Copy link

ghost commented Dec 19, 2017

🚫 CI failed with log

@appleguy
Copy link
Member Author

Thanks for all the feedback, will do some updates! Any ideas on why the build system is failing?

@ghost
Copy link

ghost commented Dec 19, 2017

🚫 CI failed with log

@appleguy
Copy link
Member Author

OK, ready for another review - if there are any ways to improve the safety of the accesses, let me know (I didn't quite understand if there is).

I considered using __unsafe_unretained id as the argument type for the methods, but this wouldn't affect any behavior, and would just be a form of API documentation — I can make that change if it seems better.

Main thing for now is getting the build to pass, which since I don't have admin access to the system, I need help to do.

@nguyenhuy
Copy link
Member

OK, tests are all green now. Let's have an approval from @Adlai-Holler before merging.

Copy link
Member

@Adlai-Holler Adlai-Holler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is in order! Nice improvement ⚡️

@appleguy
Copy link
Member Author

Thanks everyone!

@appleguy appleguy merged commit 131619d into master Dec 22, 2017
@appleguy appleguy deleted the ASRectTable branch December 22, 2017 00:17
bernieperez pushed a commit to AtomTickets/Texture that referenced this pull request Apr 25, 2018
…le exception. (TextureGroup#719)

* Reimplement ASRectTable using unordered_map to avoid obscure NSMapTable exception.

The new class is called ASRectMap, which patterns alongside ASIntegerMap in both name and implementation.

After some pretty detailed investigation, including study of open-source reimplementations
of Foundation, the best lead I've found on the NSMapTable exception is that
some NSPointerFunction types are not fully supported. Strangely, the ones being used
do seem to work fine almost all of the time.

The main concern is the Struct memory type, which is not officially re-declared in
NSMapTable, and as such the documentation claims that there may exist some
combinations of NSPointerFunction that are not supported.

Because the exception is occurring frequently enough to be a concern (in the hundreds
to low thousands, though only 50 a day) - I decided to replace NSMapTable entirely
in order to ensure full correctness.

"*** -[NSMapTable initWithKeyPointerFunctions:valuePointerFunctions:capacity:] Requested configuration not supported."

* Fix Xcode project
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.

4 participants