Skip to content

Commit

Permalink
Concurrent text contexts
Browse files Browse the repository at this point in the history
Build a test that fires up 2000 threads to instantiate `ASTextKitContext` both with and without the global lock. Both pass down to iOS10 in the simulator. See #1455 for more.
  • Loading branch information
Greg Bolsinga committed Nov 25, 2019
1 parent cbc5104 commit a854002
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 13 deletions.
6 changes: 5 additions & 1 deletion AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; };
3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.mm */; };
3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; };
4080D66C2350384400CDC199 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; };
407B8BAE2310E2ED00CB979E /* ASLayoutSpecUtilitiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 407B8BAD2310E2ED00CB979E /* ASLayoutSpecUtilitiesTests.mm */; };
4080D66C2350384400CDC199 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; };
471D04B1224CB98600649215 /* ASImageNodeBackingSizeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 471D04B0224CB98600649215 /* ASImageNodeBackingSizeTests.mm */; };
4E9127691F64157600499623 /* ASRunLoopQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.mm */; };
509E68601B3AED8E009B9150 /* ASScrollDirection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.mm */; };
Expand Down Expand Up @@ -456,6 +456,7 @@
CCF1FF5E20C4785000AAD8FC /* ASLocking.h in Headers */ = {isa = PBXBuildFile; fileRef = CCF1FF5D20C4785000AAD8FC /* ASLocking.h */; settings = {ATTRIBUTES = (Public, ); }; };
D933F041224AD17F00FF495E /* ASTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D933F040224AD17F00FF495E /* ASTransactionTests.mm */; };
D99F9158232990F30083CC8E /* ASImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D99F9157232990F30083CC8E /* ASImageNodeTests.m */; };
D9F3F4D4238C64FA00DB1978 /* ASContextKitContextConcurrencyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D960DCA0238C64BC00232DCD /* ASContextKitContextConcurrencyTests.m */; };
DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.mm */; };
Expand Down Expand Up @@ -999,6 +1000,7 @@
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = "<group>"; };
D933F040224AD17F00FF495E /* ASTransactionTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTransactionTests.mm; sourceTree = "<group>"; };
D960DCA0238C64BC00232DCD /* ASContextKitContextConcurrencyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASContextKitContextConcurrencyTests.m; sourceTree = "<group>"; };
D99F9157232990F30083CC8E /* ASImageNodeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASImageNodeTests.m; sourceTree = "<group>"; };
DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = _ASTransitionContext.h; path = ../_ASTransitionContext.h; sourceTree = "<group>"; };
DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = _ASTransitionContext.mm; path = ../_ASTransitionContext.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1369,6 +1371,7 @@
BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.mm */,
3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */,
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.mm */,
D960DCA0238C64BC00232DCD /* ASContextKitContextConcurrencyTests.m */,
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm */,
AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */,
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */,
Expand Down Expand Up @@ -2366,6 +2369,7 @@
AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.mm in Sources */,
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
F325E490217460B100AC93A4 /* ASTextNode2Tests.mm in Sources */,
D9F3F4D4238C64FA00DB1978 /* ASContextKitContextConcurrencyTests.m in Sources */,
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm in Sources */,
CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */,
CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions Source/TextKit/ASTextKitContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ AS_SUBCLASSING_RESTRICTED
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize;

/**
Exposed for testing purposes only.
*/
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
tintColor:(UIColor *)tintColor
lineBreakMode:(NSLineBreakMode)lineBreakMode
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize
enableGlobalLock:(BOOL)enableGlobalLock;

/**
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
TextKit components may cause crashes.
Expand Down
40 changes: 28 additions & 12 deletions Source/TextKit/ASTextKitContext.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#import <AsyncDisplayKit/ASLayoutManager.h>
#import <AsyncDisplayKit/ASThread.h>

static AS::Mutex globalTextMutex;

@implementation ASTextKitContext
{
// All TextKit operations (even non-mutative ones) must be executed serially.
Expand All @@ -30,19 +32,33 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize
{
static bool useGlobalTextMutex;
static dispatch_once_t onceToken;
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
dispatch_once(&onceToken, ^{
useGlobalTextMutex = !ASActivateExperimentalFeature(ASExperimentalRemoveTextKitInitialisingLock);
});
return [self initWithAttributedString:attributedString
tintColor:tintColor
lineBreakMode:lineBreakMode
maximumNumberOfLines:maximumNumberOfLines
exclusionPaths:exclusionPaths
constrainedSize:constrainedSize
enableGlobalLock:useGlobalTextMutex];
}

- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
tintColor:(UIColor *)tintColor
lineBreakMode:(NSLineBreakMode)lineBreakMode
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize
enableGlobalLock:(BOOL)enableGlobalLock
{
if (self = [super init]) {
static AS::Mutex *mutex = NULL;
static dispatch_once_t onceToken;
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
dispatch_once(&onceToken, ^{
if (!ASActivateExperimentalFeature(ASExperimentalRemoveTextKitInitialisingLock)) {
mutex = new AS::Mutex();
}
});
if (mutex != NULL) {
mutex->lock();
if (enableGlobalLock) {
globalTextMutex.lock();
}

__instanceLock__ = std::make_shared<AS::Mutex>();
Expand Down Expand Up @@ -78,8 +94,8 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
_textContainer.exclusionPaths = exclusionPaths;
[_layoutManager addTextContainer:_textContainer];

if (mutex != NULL) {
mutex->unlock();
if (enableGlobalLock) {
globalTextMutex.unlock();
}
}
return self;
Expand Down
56 changes: 56 additions & 0 deletions Tests/ASContextKitContextConcurrencyTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// ASTextKitContextConcurrencyTests.m
// AsyncDisplayKitTests
//
// Created by Greg Bolsinga on 11/25/19.
// Copyright © 2019 Pinterest. All rights reserved.
//

#import "ASTestCase.h"
#import <AsyncDisplayKit/ASTextKitContext.h>

static const NSUInteger ASTextContextConcurrentCount = 2000;

static void *createContext(void *arg)
{
BOOL enableGlobalLock = *(BOOL *)arg;

ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:[[NSAttributedString alloc] initWithString:@""]
tintColor:nil
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:100
exclusionPaths:nil
constrainedSize:CGSizeZero
enableGlobalLock:enableGlobalLock];
return (__bridge void *)(context);
}

@interface ASTextKitContextConcurrencyTests : ASTestCase

@end

@implementation ASTextKitContextConcurrencyTests

- (void)_instantiateTextContextConcurrencyCount:(NSUInteger)concurrencyCount enableGlobalLock:(BOOL)enableGlobalLock
{
pthread_t threads[concurrencyCount];
for (NSUInteger i = 0; i < concurrencyCount; i++) {
pthread_create(&threads[i], NULL, createContext, &enableGlobalLock);
}

for (NSUInteger i = 0; i < concurrencyCount; i++) {
pthread_join(threads[i], NULL);
}
}

- (void)testTextContextConcurrency_disableGlobalLock
{
[self _instantiateTextContextConcurrencyCount:ASTextContextConcurrentCount enableGlobalLock:NO];
}

- (void)testTextContextConcurrency_default
{
[self _instantiateTextContextConcurrencyCount:ASTextContextConcurrentCount enableGlobalLock:YES];
}

@end

0 comments on commit a854002

Please sign in to comment.