From 113aed809190a5648ba1c2b82e46b6bcfd79bb3d Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 11 Jun 2017 18:53:20 -0500 Subject: [PATCH] Add a Flag to Disable Main Thread Assertions #trivial (#348) * Add a thread-flag for disabling main thread assertions * Fix the license header --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++ Source/ASDisplayNode.mm | 5 ++- Source/Base/ASAssert.h | 22 ++++++++++- Source/Base/ASAssert.m | 45 +++++++++++++++++++++++ Tests/ASDisplayNodeTests.mm | 19 ++++++++++ 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 Source/Base/ASAssert.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 79d34a727..8a41e85a1 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -369,6 +369,7 @@ CCA282CD1E9EB73E0037E8B7 /* ASTipNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CB1E9EB73E0037E8B7 /* ASTipNode.m */; }; CCA282D01E9EBF6C0037E8B7 /* ASTipsWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */; }; CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */; }; + CCA5F62E1EECC2A80060C137 /* ASAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA5F62D1EECC2A80060C137 /* ASAssert.m */; }; CCA5F62C1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA5F62B1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.m */; }; CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -826,6 +827,7 @@ CCA282CB1E9EB73E0037E8B7 /* ASTipNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTipNode.m; sourceTree = ""; }; CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipsWindow.h; sourceTree = ""; }; CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTipsWindow.m; sourceTree = ""; }; + CCA5F62D1EECC2A80060C137 /* ASAssert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASAssert.m; sourceTree = ""; }; CCA5F62A1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSInvocation+ASTestHelpers.h"; sourceTree = ""; }; CCA5F62B1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSInvocation+ASTestHelpers.m"; sourceTree = ""; }; CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = ""; }; @@ -1368,6 +1370,7 @@ isa = PBXGroup; children = ( 058D0A43195D058D00B7D73C /* ASAssert.h */, + CCA5F62D1EECC2A80060C137 /* ASAssert.m */, 0516FA3A1A15563400B4EBED /* ASAvailability.h */, 058D0A44195D058D00B7D73C /* ASBaseDefines.h */, 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */, @@ -2088,6 +2091,7 @@ buildActionMask = 2147483647; files = ( DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */, + CCA5F62E1EECC2A80060C137 /* ASAssert.m in Sources */, 9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */, 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */, diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 141086103..2929227b2 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3224,7 +3224,10 @@ - (NSString *)description - (NSString *)debugDescription { - return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); + ASPushMainThreadAssertionsDisabled(); + auto result = ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); + ASPopMainThreadAssertionsDisabled(); + return result; } // This should only be called for debugging. It's not thread safe and it doesn't assert. diff --git a/Source/Base/ASAssert.h b/Source/Base/ASAssert.h index 47022c5ee..ddf87d882 100644 --- a/Source/Base/ASAssert.h +++ b/Source/Base/ASAssert.h @@ -19,6 +19,7 @@ #import #import +#import #define ASDISPLAYNODE_ASSERTIONS_ENABLED (!defined(NS_BLOCK_ASSERTIONS)) @@ -42,8 +43,8 @@ #define ASDisplayNodeAssertNotInstantiable() ASDisplayNodeAssert(NO, nil, @"This class is not instantiable."); #define ASDisplayNodeAssertNotSupported() ASDisplayNodeAssert(NO, nil, @"This method is not supported by class %@", [self class]); -#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssert(0 != pthread_main_np(), @"This method must be called on the main thread") -#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssert(0 != pthread_main_np(), @"This function must be called on the main thread") +#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssert(ASMainThreadAssertionsAreDisabled() || 0 != pthread_main_np(), @"This method must be called on the main thread") +#define ASDisplayNodeCAssertMainThread() ASDisplayNodeCAssert(ASMainThreadAssertionsAreDisabled() || 0 != pthread_main_np(), @"This function must be called on the main thread") #define ASDisplayNodeAssertNotMainThread() ASDisplayNodeAssert(0 == pthread_main_np(), @"This method must be called off the main thread") #define ASDisplayNodeCAssertNotMainThread() ASDisplayNodeCAssert(0 == pthread_main_np(), @"This function must be called off the main thread") @@ -69,6 +70,23 @@ #define ASDisplayNodeErrorDomain @"ASDisplayNodeErrorDomain" #define ASDisplayNodeNonFatalErrorCode 1 +/** + * In debug methods, it can be useful to disable main thread assertions to get valuable information, + * even if it means violating threading requirements. These functions are used in -debugDescription and let + * threads decide to suppress/re-enable main thread assertions. + */ +#pragma mark - Main Thread Assertions Disabling + +ASDISPLAYNODE_EXTERN_C_BEGIN +BOOL ASMainThreadAssertionsAreDisabled(); + +void ASPushMainThreadAssertionsDisabled(); + +void ASPopMainThreadAssertionsDisabled(); +ASDISPLAYNODE_EXTERN_C_END + +#pragma mark - Non-Fatal Assertions + /// Returns YES if assertion passed, NO otherwise. #define ASDisplayNodeAssertNonFatal(condition, desc, ...) ({ \ BOOL __evaluated = condition; \ diff --git a/Source/Base/ASAssert.m b/Source/Base/ASAssert.m new file mode 100644 index 000000000..b52f2f4b3 --- /dev/null +++ b/Source/Base/ASAssert.m @@ -0,0 +1,45 @@ +// +// ASAssert.m +// 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 +// + +#import +#import + +// pthread_key_create must be called before the key can be used. This function does that. +static pthread_key_t ASMainThreadAssertionsDisabledKey() +{ + static pthread_key_t ASMainThreadAssertionsDisabledKey; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + pthread_key_create(&ASMainThreadAssertionsDisabledKey, NULL); + }); + return ASMainThreadAssertionsDisabledKey; +} + +BOOL ASMainThreadAssertionsAreDisabled() { + return (size_t)pthread_getspecific(ASMainThreadAssertionsDisabledKey()) > 0; +} + +void ASPushMainThreadAssertionsDisabled() { + pthread_key_t key = ASMainThreadAssertionsDisabledKey(); + size_t oldValue = (size_t)pthread_getspecific(key); + pthread_setspecific(key, (void *)(oldValue + 1)); +} + +void ASPopMainThreadAssertionsDisabled() { + pthread_key_t key = ASMainThreadAssertionsDisabledKey(); + size_t oldValue = (size_t)pthread_getspecific(key); + if (oldValue > 0) { + pthread_setspecific(key, (void *)(oldValue - 1)); + } else { + ASDisplayNodeCFailAssert(@"Attempt to pop thread assertion-disabling without corresponding push."); + } +} diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index c4f9dca69..7fc00759a 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -2260,4 +2260,23 @@ - (void)testThatConvertPointGoesToWindowWhenPassedNil_layerBacked ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); } +- (void)testThatItIsAllowedToRetrieveDebugDescriptionIncludingVCOffMainThread +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; + dispatch_group_t g = dispatch_group_create(); + dispatch_group_enter(g); + __block NSString *debugDescription; + [NSThread detachNewThreadWithBlock:^{ + debugDescription = [node debugDescription]; + dispatch_group_leave(g); + }]; + dispatch_group_wait(g, DISPATCH_TIME_FOREVER); + // Ensure the debug description contains the VC string. + // Have to split into two lines because XCTAssert macro can't handle the stringWithFormat:. + BOOL hasVC = [debugDescription containsString:[NSString stringWithFormat:@"%p", vc]]; + XCTAssert(hasVC); +} + @end