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

[Minor Breaking API] Make deallocation queues more reliable #651

Merged
merged 6 commits into from
Nov 2, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler)
- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon)
- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler)
- Fixed cases where main-thread-deallocation doesn't work as expected [Adlai Holler](https://github.com/Adlai-Holler)
Copy link
Member

@nguyenhuy nguyenhuy Nov 2, 2017

Choose a reason for hiding this comment

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

Please add a quick note mentioning the API changes.


## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)
Expand Down
4 changes: 2 additions & 2 deletions Source/ASCollectionView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@ - (void)dealloc
[self setAsyncDataSource:nil];

// Data controller & range controller may own a ton of nodes, let's deallocate those off-main.
ASPerformBackgroundDeallocation(_dataController);
ASPerformBackgroundDeallocation(_rangeController);
ASPerformBackgroundDeallocation(&_dataController);
ASPerformBackgroundDeallocation(&_rangeController);
}

#pragma mark -
Expand Down
2 changes: 1 addition & 1 deletion Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ - (void)_scheduleIvarsForMainDeallocation
// is still deallocated on a background thread
object_setIvar(self, ivar, nil);

ASPerformMainThreadDeallocation(value);
ASPerformMainThreadDeallocation(&value);
} else {
as_log_debug(ASMainThreadDeallocationLog(), "%@: Not trampolining ivar '%s' value %@.", self, ivar_getName(ivar), value);
}
Expand Down
2 changes: 1 addition & 1 deletion Source/ASDisplayNodeExtras.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
#endif

/// For deallocation of objects on the main thread across multiple run loops.
extern void ASPerformMainThreadDeallocation(_Nullable id object);
extern void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull objectPtr);
Copy link
Member

Choose a reason for hiding this comment

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

Didn't know this is legit. Learning every day!


// Because inline methods can't be extern'd and need to be part of the translation unit of code
// that compiles with them to actually inline, we both declare and define these in the header.
Expand Down
13 changes: 9 additions & 4 deletions Source/ASDisplayNodeExtras.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
#import <queue>
#import <AsyncDisplayKit/ASRunLoopQueue.h>

extern void ASPerformMainThreadDeallocation(_Nullable id object)
{
extern void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull objectPtr) {
/**
* UIKit components must be deallocated on the main thread. We use this shared
* run loop queue to gradually deallocate them across many turns of the main run loop.
Expand All @@ -35,8 +34,14 @@ extern void ASPerformMainThreadDeallocation(_Nullable id object)
queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil];
queue.batchSize = 10;
});
if (object != nil) {
[queue enqueue:object];

if (objectPtr != NULL && *objectPtr != nil) {
// Lock queue while enqueuing and releasing, so that there's no risk
// that the queue will release before we get a chance to release.
[queue lock];
[queue enqueue:*objectPtr]; // Retain, +1
*objectPtr = nil; // Release, +0
[queue unlock]; // (After queue drains), release, -1
}
}

Expand Down
2 changes: 1 addition & 1 deletion Source/ASImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ - (void)_locked_setImage:(UIImage *)image
BOOL shouldReleaseImageOnBackgroundThread = oldImageSize.width > kMinReleaseImageOnBackgroundSize.width
|| oldImageSize.height > kMinReleaseImageOnBackgroundSize.height;
if (shouldReleaseImageOnBackgroundThread) {
ASPerformBackgroundDeallocation(oldImage);
ASPerformBackgroundDeallocation(&oldImage);
}
}

Expand Down
4 changes: 2 additions & 2 deletions Source/ASMultiplexImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,10 @@ - (void)_clearImage
CGSize imageSize = image.size;
BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width ||
imageSize.height > kMinReleaseImageOnBackgroundSize.height;
[self _setImage:nil];
if (shouldReleaseImageOnBackgroundThread) {
ASPerformBackgroundDeallocation(image);
ASPerformBackgroundDeallocation(&image);
}
[self _setImage:nil];
}

#pragma mark -
Expand Down
6 changes: 3 additions & 3 deletions Source/ASRunLoopQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
NS_ASSUME_NONNULL_BEGIN

AS_SUBCLASSING_RESTRICTED
@interface ASRunLoopQueue<ObjectType> : NSObject
@interface ASRunLoopQueue<ObjectType> : NSObject <NSLocking>

/**
* Create a new queue with the given run loop and handler.
Expand Down Expand Up @@ -51,11 +51,11 @@ AS_SUBCLASSING_RESTRICTED
AS_SUBCLASSING_RESTRICTED
@interface ASDeallocQueue : NSObject

+ (instancetype)sharedDeallocationQueue;
@property (class, atomic, readonly) ASDeallocQueue *sharedDeallocationQueue;

- (void)test_drain;

- (void)releaseObjectInBackground:(id)object;
- (void)releaseObjectInBackground:(id __strong _Nullable * _Nonnull)objectPtr;

@end

Expand Down
24 changes: 19 additions & 5 deletions Source/ASRunLoopQueue.mm
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ @implementation ASDeallocQueue {
ASDN::RecursiveMutex _queueLock;
}

+ (instancetype)sharedDeallocationQueue
+ (ASDeallocQueue *)sharedDeallocationQueue
{
static ASDeallocQueue *deallocQueue = nil;
static dispatch_once_t onceToken;
Expand All @@ -55,16 +55,18 @@ + (instancetype)sharedDeallocationQueue
return deallocQueue;
}

- (void)releaseObjectInBackground:(id)object
- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr
{
// Disable background deallocation on iOS 8 and below to avoid crashes related to UIAXDelegateClearer (#2767).
if (!AS_AT_LEAST_IOS9) {
return;
}

_queueLock.lock();
_queue.push_back(object);
_queueLock.unlock();
if (objectPtr != NULL && *objectPtr != nil) {
ASDN::MutexLocker l(_queueLock);
_queue.push_back(*objectPtr);
*objectPtr = nil;
}
}

- (void)threadMain
Expand Down Expand Up @@ -454,4 +456,16 @@ - (BOOL)isEmpty
return _internalQueue.count == 0;
}

#pragma mark - NSLocking

- (void)lock
{
_internalQueueLock.lock();
}

- (void)unlock
{
_internalQueueLock.unlock();
}

@end
4 changes: 2 additions & 2 deletions Source/ASTableView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,8 @@ - (void)dealloc
[self setAsyncDataSource:nil];

// Data controller & range controller may own a ton of nodes, let's deallocate those off-main
ASPerformBackgroundDeallocation(_dataController);
ASPerformBackgroundDeallocation(_rangeController);
ASPerformBackgroundDeallocation(&_dataController);
ASPerformBackgroundDeallocation(&_rangeController);
}

#pragma mark -
Expand Down
2 changes: 1 addition & 1 deletion Source/ASViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ - (void)_initializeInstance

- (void)dealloc
{
ASPerformBackgroundDeallocation(_node);
ASPerformBackgroundDeallocation(&_node);
}

- (void)loadView
Expand Down
2 changes: 1 addition & 1 deletion Source/Private/ASInternalHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ void ASPerformBlockOnMainThread(void (^block)(void));
void ASPerformBlockOnBackgroundThread(void (^block)(void)); // DISPATCH_QUEUE_PRIORITY_DEFAULT

/// For deallocation of objects on a background thread without GCD overhead / thread explosion
void ASPerformBackgroundDeallocation(id object);
void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object);

CGFloat ASScreenScale(void);

Expand Down
2 changes: 1 addition & 1 deletion Source/Private/ASInternalHelpers.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ void ASPerformBlockOnBackgroundThread(void (^block)(void))
}
}

void ASPerformBackgroundDeallocation(id object)
void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object)
{
[[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object];
}
Expand Down