Browse Source

Ensure connected state is updated immediately after calling -stop

Pierre-Olivier Latour 11 years ago
parent
commit
40ea252ad6
1 changed files with 45 additions and 22 deletions
  1. 45 22
      GCDWebServer/Core/GCDWebServer.m

+ 45 - 22
GCDWebServer/Core/GCDWebServer.m

@@ -95,6 +95,21 @@ static void _SignalHandler(int signal) {
 
 
 #endif
 #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 () {
 @interface GCDWebServerHandler () {
 @private
 @private
   GCDWebServerMatchBlock _matchBlock;
   GCDWebServerMatchBlock _matchBlock;
@@ -129,9 +144,10 @@ static void _SignalHandler(int signal) {
   dispatch_queue_t _syncQueue;
   dispatch_queue_t _syncQueue;
   dispatch_semaphore_t _sourceSemaphore;
   dispatch_semaphore_t _sourceSemaphore;
   NSMutableArray* _handlers;
   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;
   NSDictionary* _options;
   NSString* _serverName;
   NSString* _serverName;
@@ -175,10 +191,13 @@ static void _SignalHandler(int signal) {
   GCDWebServerInitializeFunctions();
   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 {
   @autoreleasepool {
-    [(ARC_BRIDGE GCDWebServer*)info _didDisconnect];
+    [server _didDisconnect];
   }
   }
+  server->_disconnecting = NO;
 }
 }
 
 
 - (instancetype)init {
 - (instancetype)init {
@@ -187,8 +206,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
     _sourceSemaphore = dispatch_semaphore_create(0);
     _sourceSemaphore = dispatch_semaphore_create(0);
     _handlers = [[NSMutableArray alloc] init];
     _handlers = [[NSMutableArray alloc] init];
     CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
     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
 #if TARGET_OS_IPHONE
     _backgroundTask = UIBackgroundTaskInvalid;
     _backgroundTask = UIBackgroundTaskInvalid;
 #endif
 #endif
@@ -201,8 +220,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
   DCHECK(_activeConnections == 0);
   DCHECK(_activeConnections == 0);
   DCHECK(_options == nil);  // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
   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_RELEASE(_handlers);
   ARC_DISPATCH_RELEASE(_sourceSemaphore);
   ARC_DISPATCH_RELEASE(_sourceSemaphore);
   ARC_DISPATCH_RELEASE(_syncQueue);
   ARC_DISPATCH_RELEASE(_syncQueue);
@@ -252,8 +271,9 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
     DCHECK(_activeConnections >= 0);
     DCHECK(_activeConnections >= 0);
     if (_activeConnections == 0) {
     if (_activeConnections == 0) {
       dispatch_async(dispatch_get_main_queue(), ^{
       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) {
         if (_connected == NO) {
           [self _didConnect];
           [self _didConnect];
@@ -307,7 +327,8 @@ static void _ConnectedTimerCallBack(CFRunLoopTimerRef timer, void* info) {
     if (_activeConnections == 0) {
     if (_activeConnections == 0) {
       dispatch_async(dispatch_get_main_queue(), ^{
       dispatch_async(dispatch_get_main_queue(), ^{
         if ((_disconnectDelay > 0.0) && (_source != NULL)) {
         if ((_disconnectDelay > 0.0) && (_source != NULL)) {
-          CFRunLoopTimerSetNextFireDate(_connectedTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay);
+          CFRunLoopTimerSetNextFireDate(_disconnectTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay);
+          _disconnecting = YES;
         } else {
         } else {
           [self _didDisconnect];
           [self _didDisconnect];
         }
         }
@@ -531,6 +552,14 @@ static inline NSString* _EncodeBase64(NSString* string) {
   ARC_RELEASE(_authenticationDigestAccounts);
   ARC_RELEASE(_authenticationDigestAccounts);
   _authenticationDigestAccounts = nil;
   _authenticationDigestAccounts = nil;
   
   
+  dispatch_async(dispatch_get_main_queue(), ^{
+    if (_disconnecting) {
+      CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL);
+      _disconnecting = NO;
+      [self _didDisconnect];
+    }
+  });
+  
   LOG_INFO(@"%@ stopped", [self class]);
   LOG_INFO(@"%@ stopped", [self class]);
   if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
   if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
     dispatch_async(dispatch_get_main_queue(), ^{
     dispatch_async(dispatch_get_main_queue(), ^{
@@ -674,11 +703,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
       [self stop];
       [self stop];
       success = YES;
       success = YES;
     }
     }
-    while (1) {
-      if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true) == kCFRunLoopRunTimedOut) {  // Ensure pending scheduled callbacks have been executed
-        break;
-      }
-    }
+    _ExecuteMainThreadRunLoopSources();
     signal(SIGINT, intHandler);
     signal(SIGINT, intHandler);
     signal(SIGTERM, termHandler);
     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
   NSArray* ignoredHeaders = @[@"Date", @"Etag"];  // Dates are always different by definition and ETags depend on file system node IDs
   NSInteger result = -1;
   NSInteger result = -1;
   if ([self startWithOptions:options error:NULL]) {
   if ([self startWithOptions:options error:NULL]) {
+    _ExecuteMainThreadRunLoopSources();
     
     
     result = 0;
     result = 0;
     NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
     NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
@@ -1073,15 +1099,12 @@ static void _LogResult(NSString* format, ...) {
           ++result;
           ++result;
         }
         }
       }
       }
+      _ExecuteMainThreadRunLoopSources();
     }
     }
     
     
     [self stop];
     [self stop];
     
     
-    while (1) {
-      if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true) == kCFRunLoopRunTimedOut) {  // Ensure pending scheduled callbacks have been executed
-        break;
-      }
-    }
+    _ExecuteMainThreadRunLoopSources();
   }
   }
   return result;
   return result;
 }
 }