Last active
April 23, 2024 08:48
-
-
Save Adlai-Holler/ea7e3e98333b3b84f13d19b169ccf989 to your computer and use it in GitHub Desktop.
A simple UIKit deadlock detector, written in Objective-C.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
#import <pthread.h> | |
#import <stdatomic.h> | |
@interface AHDeadlockDetector () | |
/** | |
* The current detection thread, if one is running. We store this weakly because | |
* if the thread has exited, why keep it around? In practice however, we nil | |
* this explicitly when we suspend detection. | |
*/ | |
@property (nonatomic, weak) NSThread *detectorThread; | |
@end | |
static NSTimeInterval kWatchdogInterval; | |
static pthread_t gMainThread; | |
@implementation AHDeadlockDetector | |
/// Private | |
+ (AHDeadlockDetector *)sharedDetector | |
{ | |
static AHDeadlockDetector *sharedDetector; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
NSAssert(NSThread.isMainThread, nil); | |
kWatchdogInterval = 10; // <-- Change this per-device if you want. | |
gMainThread = pthread_self(); | |
sharedDetector = [[AHDeadlockDetector alloc] init]; | |
}); | |
return sharedDetector; | |
} | |
/// This is the only public method: | |
+ (void)enable | |
{ | |
// Creating the shared detector instance is enough. It will control | |
// beginning and canceling deadlock detection on its own. | |
[self sharedDetector]; | |
} | |
/// Publicly unavailable | |
- (instancetype)init | |
{ | |
self = [super init]; | |
if (self != nil) { | |
NSAssert(NSThread.isMainThread, nil); | |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; | |
UIApplication *app = [UIApplication sharedApplication]; | |
[nc addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:app]; | |
[nc addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:app]; | |
if (app.applicationState == UIApplicationStateActive) { | |
[self beginDeadlockDetection]; | |
} | |
NSLog(@"Installed deadlock detector."); | |
} | |
return self; | |
} | |
#pragma mark - UIApplication Notification Listeners | |
- (void)applicationWillResignActive | |
{ | |
[self cancelDeadlockDetection]; | |
} | |
- (void)applicationDidBecomeActive | |
{ | |
[self beginDeadlockDetection]; | |
} | |
#pragma mark - Internal Methods | |
- (void)beginDeadlockDetection | |
{ | |
NSAssert(NSThread.isMainThread, nil); | |
if (self.detectorThread != nil) { | |
NSAssert(NO, @"Attempt to begin deadlock detection while already detecting."); | |
return; | |
} | |
NSThread *thread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(detectionThreadLoop:) object:nil]; | |
thread.name = @"Deadlock Detection Thread"; | |
self.detectorThread = thread; | |
[thread start]; | |
} | |
- (void)cancelDeadlockDetection | |
{ | |
NSAssert(NSThread.isMainThread, nil); | |
if (self.detectorThread == nil) { | |
NSAssert(NO, @"Attempt to cancel deadlock detection while not detecting."); | |
return; | |
} | |
[self.detectorThread cancel]; | |
self.detectorThread = nil; | |
} | |
#pragma mark - Detection Thread Methods | |
/** | |
* The detection thread loop. Pings the main thread, sleeps, checks that | |
* the main thread responded, repeats until canceled. | |
* | |
* Because this method may run concurrently – if a prior detection thread | |
* hasn't found out it was canceled before a new detection thread is spawned – | |
* this is implemented as a class method to reduce statefulness. | |
*/ | |
+ (void)detectionThreadLoop:(id)obj | |
{ | |
NSThread *thread = NSThread.currentThread; | |
while (thread.cancelled == NO) { | |
__block atomic_bool didRespond = ATOMIC_VAR_INIT(NO); | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
atomic_store(&didRespond, YES); | |
}); | |
[NSThread sleepForTimeInterval:kWatchdogInterval]; | |
if (thread.cancelled) { | |
return; | |
} | |
if (atomic_load(&didRespond) == NO) { | |
[self handleDeadlock]; | |
} | |
} | |
} | |
/// Called on a deadlock detection thread. | |
+ (void)handleDeadlock | |
{ | |
/** | |
* Interrupt the main thread with a SIGABRT so that the crash is aggregated from the main thread. | |
*/ | |
// TODO: Add whatever pre-death code you want to run (off main) here. | |
pthread_kill(gMainThread, SIGABRT); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
good job. i will try it.