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

Unlock before cleanup and calling out to subclass hooks for animated images. #1087

Merged
merged 1 commit into from
Sep 12, 2018

Conversation

maicki
Copy link
Contributor

@maicki maicki commented Aug 29, 2018

  • Unlock before cleanup
  • Unlock before calling out to animatedImageSet:previousAnimatedImage: to prevent a deadlock.

@maicki maicki changed the title Unlock before calling out to subclass hooks for animated images. Unlock before cleanup and calling out to subclass hooks for animated images. Aug 29, 2018
@maicki maicki requested a review from nguyenhuy August 30, 2018 14:49
[self animatedImageSet:_animatedImage previousAnimatedImage:previousAnimatedImage];
{
// Unlock for cleanup and calling subclass hook
ASUnlockScope(self);
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 actually safe? We don't necessarily know what the calling scope is doing and therefore unlocking here could be dangerous? I know it's very unlikely (I can only think of convoluted examples)…

I'm not sure of a better solution though… If we do end up deciding this, we should at least add a comment that unlocking a lock someone else locked might cause us a bad time in the future.

Copy link
Member

@nguyenhuy nguyenhuy Aug 30, 2018

Choose a reason for hiding this comment

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

Agree. Another thing is that with a recursive lock, the lock may have been acquired multiple times and so unlocking once here is not enought.

Ideally, we have to make this method _u_ but it can be painful and may not be possible.

Another idea is dispatching before calling -animatedImageSet:previousAnimatedImage: and deallocating the previous image.


self.contents = nil;
[self setCoverImage:nil];
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we wrap the above lines in a shouldCleanup block?

Copy link
Member

Choose a reason for hiding this comment

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

Hm, can you explain why -setCoverImage: needs to be called unlocked?

Somewhat unrelated: that method acquires the lock right away, so shouldn't we just call -_locked_setCoverImage: directly here?

@@ -173,7 +173,6 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
*
* @discussion This method is for subclasses to override so they can know if an animated image
* has been set on the node.
* @warning this method is called with the node's lock held.
Copy link
Member

Choose a reason for hiding this comment

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

Overall, I think it's great to remove this caveat.

[self animatedImageSet:_animatedImage previousAnimatedImage:previousAnimatedImage];
{
// Unlock for cleanup and calling subclass hook
ASUnlockScope(self);
Copy link
Member

@nguyenhuy nguyenhuy Aug 30, 2018

Choose a reason for hiding this comment

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

Agree. Another thing is that with a recursive lock, the lock may have been acquired multiple times and so unlocking once here is not enought.

Ideally, we have to make this method _u_ but it can be painful and may not be possible.

Another idea is dispatching before calling -animatedImageSet:previousAnimatedImage: and deallocating the previous image.

Copy link
Member

@nguyenhuy nguyenhuy left a comment

Choose a reason for hiding this comment

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

Opps, didn't mean to approve.

@maicki maicki force-pushed the MSUnlockForAnimatedImageSubclassHook branch from 9918f20 to 0be1bc5 Compare September 2, 2018 16:42
@maicki
Copy link
Contributor Author

maicki commented Sep 4, 2018

Unfortunately this is all not so trivial to solve. If we would like to solve this without dispatching to the next runloop it will take a lot of effort to refactor every (AS) image node and the fact we have to call a delegate subclass hook needs to be considered every time we change the code.

After looking into it more deeply I think there are a couple of ways we could approach this:

  1. Refactor ASImageNode / ASNetworkImageNode to add some way to call the subclass hook without the lock held. As mentioned above this would need some massive refactor and also always some knowledge needed so calling the subclass hook will be called eventually.
  2. Create an internal serial queue that would call to the subclass method, like a delegate (or move the method out as an internal delegate method in the first place and dispatch the call to the queue)
  3. Dispatch the subclass call to the main queue / thread (this would go against the current trend though as it seems we are trying to get rid of calling delegate / subclass hooks on main)
  4. Dispatch the call to the current queue (dispatch_get_current_queue). dispatch_get_current_queue is deprecated at the moment and there are a lot of online material that definetly speak against it.
  5. Use performSelector:afterDelay:... This would be one way, but as we cannot pass two objects in this methods, we have to create a helper method. This could be factored out into an NSObject category though to reusable schedule a block via performSelector:
  6. Use CFRunLoopPerformBlock (There would also be -[NSRunLoop performBlock:] but that's iOS 10+). This would work too to schedule a block on the next run loop cycle and this without having to introduce any more infrastructure around it as we can scheduel a block.

I eventually went with version 6 for now you can see the history too for also implementing 5. Happy for any thoughts or better ideas cc @nguyenhuy @garrettmoon @appleguy @Adlai-Holler

});
// Don't need to wakeup the runloop as the current is already running
// CFRunLoopWakeUp(runLoop); // Should not be necessary

// Animated image can take while to dealloc, do it off the main queue
if (previousAnimatedImage != nil) {
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to deallocate the previous image after calling out to subclass, otherwise it will not be deallocated in background if the deallocation occurs before the next run loop.

@maicki
Copy link
Contributor Author

maicki commented Sep 5, 2018

@nguyenhuy You wanna give that another look please

@maicki maicki force-pushed the MSUnlockForAnimatedImageSubclassHook branch from 3858dda to 75da400 Compare September 8, 2018 16:20
@maicki maicki force-pushed the MSUnlockForAnimatedImageSubclassHook branch from 75da400 to cde0eba Compare September 8, 2018 16:21
@maicki maicki merged commit a798cf9 into master Sep 12, 2018
@maicki maicki deleted the MSUnlockForAnimatedImageSubclassHook branch September 15, 2018 15:33
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.

3 participants