-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Conversation
🚫 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."
883abb7
to
351d9e4
Compare
🚫 CI failed with log |
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? |
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.
Cool. Straight forward implementation. Looks good to me!
#import <unordered_map> | ||
|
||
@implementation ASRectMap { | ||
std::unordered_map<void *, CGRect> _map; |
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.
We may should add a comment that we use a void *
key here as arc would not be able to determine the ownership qualification.
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.
Could we not use __unsafe_unretained id
as the key type 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.
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++) { |
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.
You could also use of a bit of a smaller code:
for ( auto const& entry : _map ) {
[str appendFormat:@" %@->%@", entry->first, NSStringFromCGRect(entry->second)];
}
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.
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
.
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.
Agree with @Adlai-Holler. Let's pay extra attention 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.
Nice! A few notes and then let's go.
#import <unordered_map> | ||
|
||
@implementation ASRectMap { | ||
std::unordered_map<void *, CGRect> _map; |
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.
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++) { |
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.
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; |
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.
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++) { |
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.
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 | ||
// |
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.
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
//
🚫 CI failed with log |
Thanks for all the feedback, will do some updates! Any ideas on why the build system is failing? |
🚫 CI failed with log |
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. |
OK, tests are all green now. Let's have an approval from @Adlai-Holler before merging. |
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.
Everything is in order! Nice improvement ⚡️
Thanks everyone! |
…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
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.