Skip to content

Instantly share code, notes, and snippets.

@Adlai-Holler
Last active April 23, 2024 08:48
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Adlai-Holler/ea7e3e98333b3b84f13d19b169ccf989 to your computer and use it in GitHub Desktop.
Save Adlai-Holler/ea7e3e98333b3b84f13d19b169ccf989 to your computer and use it in GitHub Desktop.
A simple UIKit deadlock detector, written in Objective-C.
#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
@karsawu
Copy link

karsawu commented Jan 6, 2023

good job. i will try it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment