diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m index b9aa8b2..883274a 100644 --- a/GCDWebServer/Core/GCDWebServer.m +++ b/GCDWebServer/Core/GCDWebServer.m @@ -95,6 +95,21 @@ static void _SignalHandler(int signal) { #endif +#if !TARGET_OS_IPHONE || defined(__GCDWEBSERVER_ENABLE_TESTING__) + +// This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously +// https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html +// The main queue works with the application’s run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop +// TODO: Ensure all scheduled blocks on the main queue are also executed +static void _ExecuteMainThreadRunLoopSources() { + SInt32 result; + do { + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); + } while (result == kCFRunLoopRunHandledSource); +} + +#endif + @interface GCDWebServerHandler () { @private GCDWebServerMatchBlock _matchBlock; @@ -129,9 +144,10 @@ static void _SignalHandler(int signal) { dispatch_queue_t _syncQueue; dispatch_semaphore_t _sourceSemaphore; NSMutableArray* _handlers; - NSInteger _activeConnections; // Accessed only with _syncQueue - BOOL _connected; - CFRunLoopTimerRef _connectedTimer; + NSInteger _activeConnections; // Accessed through _syncQueue only + BOOL _connected; // Accessed on main thread only + BOOL _disconnecting; // Accessed on main thread only + CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only NSDictionary* _options; NSString* _serverName; @@ -175,10 +191,13 @@ static void _SignalHandler(int signal) { GCDWebServerInitializeFunctions(); } -static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { +static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) { + DCHECK([NSThread isMainThread]); + GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info; @autoreleasepool { - [(ARC_BRIDGE GCDWebServer*)info _didDisconnect]; + [server _didDisconnect]; } + server->_disconnecting = NO; } - (instancetype)init { @@ -187,8 +206,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { _sourceSemaphore = dispatch_semaphore_create(0); _handlers = [[NSMutableArray alloc] init]; CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; - _connectedTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _ConnectedTimerCallBack, &context); - CFRunLoopAddTimer(CFRunLoopGetMain(), _connectedTimer, kCFRunLoopCommonModes); + _disconnectTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _DisconnectTimerCallBack, &context); + CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes); #if TARGET_OS_IPHONE _backgroundTask = UIBackgroundTaskInvalid; #endif @@ -201,8 +220,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { DCHECK(_activeConnections == 0); DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source - CFRunLoopTimerInvalidate(_connectedTimer); - CFRelease(_connectedTimer); + CFRunLoopTimerInvalidate(_disconnectTimer); + CFRelease(_disconnectTimer); ARC_RELEASE(_handlers); ARC_DISPATCH_RELEASE(_sourceSemaphore); ARC_DISPATCH_RELEASE(_syncQueue); @@ -252,8 +271,9 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { DCHECK(_activeConnections >= 0); if (_activeConnections == 0) { dispatch_async(dispatch_get_main_queue(), ^{ - if (_disconnectDelay > 0.0) { - CFRunLoopTimerSetNextFireDate(_connectedTimer, HUGE_VAL); + if (_disconnecting) { + CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL); + _disconnecting = NO; } if (_connected == NO) { [self _didConnect]; @@ -307,7 +327,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) { if (_activeConnections == 0) { dispatch_async(dispatch_get_main_queue(), ^{ if ((_disconnectDelay > 0.0) && (_source != NULL)) { - CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay); + CFRunLoopTimerSetNextFireDate(_disconnectTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay); + _disconnecting = YES; } else { [self _didDisconnect]; } @@ -531,6 +552,14 @@ static inline NSString* _EncodeBase64(NSString* string) { ARC_RELEASE(_authenticationDigestAccounts); _authenticationDigestAccounts = nil; + dispatch_async(dispatch_get_main_queue(), ^{ + if (_disconnecting) { + CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL); + _disconnecting = NO; + [self _didDisconnect]; + } + }); + LOG_INFO(@"%@ stopped", [self class]); if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -674,11 +703,7 @@ static inline NSString* _EncodeBase64(NSString* string) { [self stop]; success = YES; } - while (1) { - if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true) == kCFRunLoopRunTimedOut) { // Ensure pending scheduled callbacks have been executed - break; - } - } + _ExecuteMainThreadRunLoopSources(); signal(SIGINT, intHandler); signal(SIGTERM, termHandler); } @@ -971,6 +996,7 @@ static void _LogResult(NSString* format, ...) { NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs NSInteger result = -1; if ([self startWithOptions:options error:NULL]) { + _ExecuteMainThreadRunLoopSources(); result = 0; NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL]; @@ -1073,15 +1099,12 @@ static void _LogResult(NSString* format, ...) { ++result; } } + _ExecuteMainThreadRunLoopSources(); } [self stop]; - while (1) { - if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true) == kCFRunLoopRunTimedOut) { // Ensure pending scheduled callbacks have been executed - break; - } - } + _ExecuteMainThreadRunLoopSources(); } return result; }