Преглед изворни кода

manager: improved connection handling

QEMU, QMP manager, and SPICE manager each needs to connect. To simplify things
we set a 30s timeout on each one with 1s per try. They will independently try
to connect and if any fail, then an error message will be shown.

This process should be improved for better failure handling.
osy пре 5 година
родитељ
комит
1e52966662

+ 1 - 1
Managers/UTMInputOutput.h

@@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
 @protocol UTMInputOutput <NSObject>
 @protocol UTMInputOutput <NSObject>
 
 
 - (BOOL)startWithError:(NSError **)err;
 - (BOOL)startWithError:(NSError **)err;
-- (void)connectWithCompletion: (void(^)(BOOL, NSError* _Nullable)) block;
+- (void)connectWithCompletion:(void(^)(BOOL, NSString * _Nullable))block;
 - (void)disconnect;
 - (void)disconnect;
 - (void)setDebugMode: (BOOL)debugMode;
 - (void)setDebugMode: (BOOL)debugMode;
 - (UTMScreenshot* _Nullable)screenshot;
 - (UTMScreenshot* _Nullable)screenshot;

+ 1 - 1
Managers/UTMPortAllocator.m

@@ -32,7 +32,7 @@ static NSInteger firstZeroBit(UInt64 x) {
     return (t < 64) ? t : -1;
     return (t < 64) ? t : -1;
 }
 }
 
 
-static BOOL isPortAvailable(NSInteger port) {
+BOOL isPortAvailable(NSInteger port) {
     struct sockaddr_in addr = {0};
     struct sockaddr_in addr = {0};
     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) {
     if (sockfd < 0) {

+ 1 - 16
Managers/UTMQemu.m

@@ -153,8 +153,6 @@
 }
 }
 
 
 - (void)startQemuRemote:(nonnull NSString *)name completion:(void(^)(BOOL,NSString *))completion {
 - (void)startQemuRemote:(nonnull NSString *)name completion:(void(^)(BOOL,NSString *))completion {
-    dispatch_semaphore_t lock = dispatch_semaphore_create(0);
-    __block NSString *taskIdentifier;
     NSError *error;
     NSError *error;
     NSData *libBookmark = [self.libraryURL bookmarkDataWithOptions:0
     NSData *libBookmark = [self.libraryURL bookmarkDataWithOptions:0
                                     includingResourceValuesForKeys:nil
                                     includingResourceValuesForKeys:nil
@@ -166,20 +164,7 @@
     }
     }
     [[_connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
     [[_connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
         completion(NO, error.localizedDescription);
         completion(NO, error.localizedDescription);
-        dispatch_semaphore_signal(lock);
-    }] startQemu:name libraryBookmark:libBookmark argv:self.argv onStarted:^(NSString *identifier) {
-        UTMLog(@"started %@!", identifier);
-        taskIdentifier = identifier;
-        dispatch_semaphore_signal(lock);
-    }];
-    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
-    if (!taskIdentifier) {
-        completion(NO, NSLocalizedString(@"Failed to start QEMU process.", @"UTMQemu"));
-    } else {
-        [[_connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
-            completion(NO, error.localizedDescription);
-        }] registerExitHandlerForIdentifier:taskIdentifier handler:completion];
-    }
+    }] startQemu:name libraryBookmark:libBookmark argv:self.argv onExit:completion];
 }
 }
 
 
 - (void)start:(nonnull NSString *)name completion:(void(^)(BOOL,NSString *))completion {
 - (void)start:(nonnull NSString *)name completion:(void(^)(BOOL,NSString *))completion {

+ 18 - 11
Managers/UTMSpiceIO.m

@@ -22,12 +22,13 @@
 #import "UTMViewState.h"
 #import "UTMViewState.h"
 #import "CocoaSpice.h"
 #import "CocoaSpice.h"
 
 
+extern BOOL isPortAvailable(NSInteger port); // from UTMPortAllocator
 extern NSString *const kUTMErrorDomain;
 extern NSString *const kUTMErrorDomain;
-static const int kMaxConnectionTries = 10; // qemu needs to start spice server first
+static const int kMaxConnectionTries = 30; // qemu needs to start spice server first
 static const int64_t kRetryWait = (int64_t)1*NSEC_PER_SEC;
 static const int64_t kRetryWait = (int64_t)1*NSEC_PER_SEC;
 
 
 typedef void (^doConnect_t)(void);
 typedef void (^doConnect_t)(void);
-typedef void (^connectionCallback_t)(BOOL success, NSError * _Nullable err);
+typedef void (^connectionCallback_t)(BOOL success, NSString * _Nullable msg);
 
 
 @interface UTMSpiceIO ()
 @interface UTMSpiceIO ()
 
 
@@ -102,21 +103,28 @@ typedef void (^connectionCallback_t)(BOOL success, NSError * _Nullable err);
     return YES;
     return YES;
 }
 }
 
 
-- (void)connectWithCompletion:(void(^)(BOOL, NSError*))block {
+- (void)connectWithCompletion:(void(^)(BOOL, NSString * _Nullable))block {
     __block int tries = kMaxConnectionTries;
     __block int tries = kMaxConnectionTries;
     __block __weak doConnect_t weakDoConnect;
     __block __weak doConnect_t weakDoConnect;
     __weak UTMSpiceIO *weakSelf = self;
     __weak UTMSpiceIO *weakSelf = self;
     self.doConnect = ^{
     self.doConnect = ^{
         if (tries-- > 0) {
         if (tries-- > 0) {
-            if ([weakSelf.spiceConnection connect]) {
-                weakSelf.connectionCallback = block;
+            if (!isPortAvailable(weakSelf.port)) { // port is in use, try connecting
+                if ([weakSelf.spiceConnection connect]) {
+                    weakSelf.connectionCallback = block;
+                    weakSelf.doConnect = nil;
+                    return;
+                }
             } else {
             } else {
-                dispatch_after(kRetryWait, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), weakDoConnect);
+                UTMLog(@"SPICE port not in use yet, retries left: %d", tries);
+                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kRetryWait), dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), weakDoConnect);
+                return;
             }
             }
-        } else { // base case, no more tries
-            NSError *err = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to connect to SPICE server.", "UTMSpiceIO")}];
-            block(NO, err);
         }
         }
+        // if we get here, error is unrecoverable
+        block(NO, NSLocalizedString(@"Failed to connect to SPICE server.", "UTMSpiceIO"));
+        weakSelf.connectionCallback = nil;
+        weakSelf.doConnect = nil;
     };
     };
     weakDoConnect = self.doConnect;
     weakDoConnect = self.doConnect;
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), self.doConnect);
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), self.doConnect);
@@ -167,8 +175,7 @@ typedef void (^connectionCallback_t)(BOOL success, NSError * _Nullable err);
 - (void)spiceError:(CSConnection *)connection err:(NSString *)msg {
 - (void)spiceError:(CSConnection *)connection err:(NSString *)msg {
     NSAssert(connection == self.spiceConnection, @"Unknown connection");
     NSAssert(connection == self.spiceConnection, @"Unknown connection");
     if (self.connectionCallback) {
     if (self.connectionCallback) {
-        NSError *err = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: msg}];
-        self.connectionCallback(NO, err);
+        self.connectionCallback(NO, msg);
         self.connectionCallback = nil;
         self.connectionCallback = nil;
     }
     }
 }
 }

+ 1 - 1
Managers/UTMTerminalIO.m

@@ -36,7 +36,7 @@
     return [_terminal connectWithError: err];
     return [_terminal connectWithError: err];
 }
 }
 
 
-- (void)connectWithCompletion: (void(^)(BOOL, NSError*)) block {
+- (void)connectWithCompletion: (void(^)(BOOL, NSString * _Nullable msg)) block {
     // there's no connection to be made, so just return YES
     // there's no connection to be made, so just return YES
     block(YES, nil);
     block(YES, nil);
 }
 }

+ 3 - 3
Managers/UTMVirtualMachine.m

@@ -33,7 +33,7 @@
 #import "UTMPortAllocator.h"
 #import "UTMPortAllocator.h"
 #import "qapi-events.h"
 #import "qapi-events.h"
 
 
-const int kQMPMaxConnectionTries = 10; // qemu needs to start spice server first
+const int kQMPMaxConnectionTries = 30; // qemu needs to start spice server first
 const int64_t kStopTimeout = (int64_t)30*NSEC_PER_SEC;
 const int64_t kStopTimeout = (int64_t)30*NSEC_PER_SEC;
 
 
 NSString *const kUTMErrorDomain = @"com.osy86.utm";
 NSString *const kUTMErrorDomain = @"com.osy86.utm";
@@ -291,9 +291,9 @@ error:
         }
         }
         dispatch_semaphore_signal(self->_qemu_exit_sema);
         dispatch_semaphore_signal(self->_qemu_exit_sema);
     }];
     }];
-    [self->_ioService connectWithCompletion:^(BOOL success, NSError * _Nullable error) {
+    [self->_ioService connectWithCompletion:^(BOOL success, NSString * _Nullable msg) {
         if (!success) {
         if (!success) {
-            [self errorTriggered:NSLocalizedString(@"Failed to connect to display server.", @"UTMVirtualMachine")];
+            [self errorTriggered:msg];
         } else {
         } else {
             [self changeState:kVMStarted];
             [self changeState:kVMStarted];
             [self restoreViewState];
             [self restoreViewState];

+ 6 - 61
QEMUHelper/QEMUHelper.m

@@ -20,8 +20,6 @@
 @interface QEMUHelper ()
 @interface QEMUHelper ()
 
 
 @property NSMutableArray<NSURL *> *urls;
 @property NSMutableArray<NSURL *> *urls;
-@property NSMutableDictionary<NSString *, NSTask *> *processes;
-@property NSMutableDictionary<NSString *, void(^)(BOOL, NSString *)> *exitHandlers;
 
 
 @end
 @end
 
 
@@ -30,7 +28,6 @@
 - (instancetype)init {
 - (instancetype)init {
     if (self = [super init]) {
     if (self = [super init]) {
         self.urls = [NSMutableArray array];
         self.urls = [NSMutableArray array];
-        self.processes = [NSMutableDictionary dictionary];
     }
     }
     return self;
     return self;
 }
 }
@@ -73,11 +70,11 @@
     completion(YES, bookmark, url.path);
     completion(YES, bookmark, url.path);
 }
 }
 
 
-- (void)startQemu:(NSString *)binName libraryBookmark:(NSData *)libBookmark  argv:(NSArray<NSString *> *)argv onStarted:(void(^)(NSString * _Nullable))onStarted {
+- (void)startQemu:(NSString *)binName libraryBookmark:(NSData *)libBookmark argv:(NSArray<NSString *> *)argv onExit:(void(^)(BOOL,NSString *))onExit {
     NSURL *qemuURL = [[NSBundle mainBundle] URLForAuxiliaryExecutable:binName];
     NSURL *qemuURL = [[NSBundle mainBundle] URLForAuxiliaryExecutable:binName];
     if (!qemuURL || ![[NSFileManager defaultManager] fileExistsAtPath:qemuURL.path]) {
     if (!qemuURL || ![[NSFileManager defaultManager] fileExistsAtPath:qemuURL.path]) {
         NSLog(@"Cannot find executable for %@", binName);
         NSLog(@"Cannot find executable for %@", binName);
-        onStarted(nil);
+        onExit(NO, NSLocalizedString(@"Cannot find QEMU executable.", @"QEMUHelper"));
         return;
         return;
     }
     }
     
     
@@ -89,74 +86,22 @@
                                                      error:&err];
                                                      error:&err];
     if (!libraryPath || ![[NSFileManager defaultManager] fileExistsAtPath:libraryPath.path]) {
     if (!libraryPath || ![[NSFileManager defaultManager] fileExistsAtPath:libraryPath.path]) {
         NSLog(@"Cannot resolve library path: %@", err);
         NSLog(@"Cannot resolve library path: %@", err);
-        onStarted(nil);
+        onExit(NO, NSLocalizedString(@"Cannot find QEMU support libraries.", @"QEMUHelper"));
         return;
         return;
     }
     }
     
     
     NSTask *task = [NSTask new];
     NSTask *task = [NSTask new];
-    NSString *identifier = [self storeProcess:task];
     task.executableURL = qemuURL;
     task.executableURL = qemuURL;
     task.arguments = argv;
     task.arguments = argv;
     task.environment = @{@"DYLD_LIBRARY_PATH": libraryPath.path};
     task.environment = @{@"DYLD_LIBRARY_PATH": libraryPath.path};
     task.qualityOfService = NSQualityOfServiceUserInitiated;
     task.qualityOfService = NSQualityOfServiceUserInitiated;
     task.terminationHandler = ^(NSTask *task) {
     task.terminationHandler = ^(NSTask *task) {
-        [self exitProcessForIdentifier:identifier];
+        BOOL normalExit = task.terminationReason == NSTaskTerminationReasonExit && task.terminationStatus == 0;
+        onExit(normalExit, nil); // TODO: get last error line
     };
     };
     if (![task launchAndReturnError:&err]) {
     if (![task launchAndReturnError:&err]) {
         NSLog(@"Error starting QEMU: %@", err);
         NSLog(@"Error starting QEMU: %@", err);
-        [self exitProcessForIdentifier:identifier];
-        onStarted(nil);
-    } else {
-        onStarted(identifier);
-    }
-}
-
-- (void)registerExitHandlerForIdentifier:(NSString *)identifier handler:(void(^)(BOOL,NSString *))handler {
-    if (![self storeExitHandlerForIdentifier:identifier handler:handler]) {
-        handler(NO, NSLocalizedString(@"QEMU already exited.", @"QEMUHelper"));
-    }
-}
-
-#pragma mark - Helpers
-
-- (NSString *)storeProcess:(NSTask *)process {
-    @synchronized (self.processes) {
-        NSString *identifier = [NSUUID UUID].UUIDString;
-        self.processes[identifier] = process;
-        return identifier;
-    }
-}
-
-- (BOOL)storeExitHandlerForIdentifier:(NSString *)identifier handler:(void(^)(BOOL,NSString *))handler {
-    @synchronized (self.processes) {
-        if (self.processes[identifier]) {
-            self.exitHandlers[identifier] = handler;
-            return YES;
-        } else {
-            return NO;
-        }
-    }
-}
-
-- (void)exitProcessForIdentifier:(NSString *)identifier {
-    NSTask *task;
-    void (^exitHandler)(BOOL, NSString *);
-    BOOL normalExit = NO;
-    @synchronized (self.processes) {
-        task = self.processes[identifier];
-        [self.processes removeObjectForKey:identifier];
-        exitHandler = self.exitHandlers[identifier];
-        [self.exitHandlers removeObjectForKey:identifier];
-    }
-    if (task) {
-        if (task.running) {
-            [task terminate];
-        } else {
-            normalExit = task.terminationReason == NSTaskTerminationReasonExit && task.terminationStatus == 0;
-        }
-    }
-    if (exitHandler) {
-        exitHandler(normalExit, nil); // TODO: get last error line
+        onExit(NO, NSLocalizedString(@"Error starting QEMU.", @"QEMUHelper"));
     }
     }
 }
 }
 
 

+ 1 - 2
QEMUHelper/QEMUHelperProtocol.h

@@ -22,8 +22,7 @@ NS_ASSUME_NONNULL_BEGIN
 @protocol QEMUHelperProtocol
 @protocol QEMUHelperProtocol
 
 
 - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
 - (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
-- (void)startQemu:(NSString *)binName libraryBookmark:(NSData *)libBookmark argv:(NSArray<NSString *> *)argv onStarted:(void(^)(NSString * _Nullable))onStarted;
-- (void)registerExitHandlerForIdentifier:(NSString *)identifier handler:(void(^)(BOOL,NSString *))handler;
+- (void)startQemu:(NSString *)binName libraryBookmark:(NSData *)libBookmark argv:(NSArray<NSString *> *)argv onExit:(void(^)(BOOL,NSString *))onExit;
     
     
 @end
 @end