From 264887413e982c4094d8b1360cab4a97e91b6fa7 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 23 Jan 2016 13:35:04 -0800 Subject: [PATCH] Add @synchronized around UIImage draw to prevent concurrently decoding the same instance. This is a workaround necessary due to an iOS 9 bug. I'm not gating it to iOS 9 only so as to not exaggerate iOS version-specific behavior differences in the framework, since a majority are on iOS 9 anyway. If we can confirm the bug is fixed in a later iOS version, then it will be gated. Issue tracked in greater detail here: https://github.com/facebook/AsyncDisplayKit/issues/1068 --- AsyncDisplayKit/ASImageNode.mm | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 3191b53ab7..c9024c7510 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -235,7 +235,21 @@ UIRectFill({ .size = backingSize }); } - [image drawInRect:imageDrawRect]; + // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on + // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. + // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier, + // as well as iOS games, and a small number of ASDK apps that provide the same image reference + // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes + // that may get the same pointer for a given UI asset image, etc. + // FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and + // only if the object already exists in the set we should create a semaphore to signal waiting threads + // upon removal of the object from the set when the operation completes. + // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer. + // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068 + + @synchronized(image) { + [image drawInRect:imageDrawRect]; + } if (isCancelled()) { UIGraphicsEndImageContext();