process: hold token to avoid XPC service termination

When under memory pressure, XNU can decide to terminate the XPC helper
unless there is a pending response. We force it to have a pending response
by providing a NOP callback which will only be triggered after QEMU
terminates.

Fixes #5489
This commit is contained in:
osy
2023-07-30 22:59:50 -07:00
parent a5d67ef960
commit d5a9811f0c
3 changed files with 36 additions and 2 deletions
+24 -2
View File
@@ -20,8 +20,9 @@
@interface QEMUHelper ()
@property NSMutableArray<NSURL *> *urls;
@property NSTask *childTask;
@property (nonatomic) NSMutableArray<NSURL *> *urls;
@property (nonatomic, nullable) NSTask *childTask;
@property (nonatomic, nullable) tokenCallback_t activeToken;
@end
@@ -136,9 +137,11 @@
task.terminationHandler = ^(NSTask *task) {
_self.childTask = nil;
[_self.connection.remoteObjectProxy processHasExited:task.terminationStatus message:nil];
[_self invalidateToken];
};
if (![task launchAndReturnError:&err]) {
NSLog(@"Error starting QEMU: %@", err);
[self invalidateToken];
completion(NO, err.localizedDescription);
} else {
self.childTask = task;
@@ -149,6 +152,25 @@
- (void)terminate {
[self.childTask terminate];
self.childTask = nil;
[self invalidateToken];
}
- (void)assertActiveWithToken:(tokenCallback_t)token {
@synchronized (self) {
if (self.activeToken) {
self.activeToken(NO);
}
self.activeToken = token;
}
}
- (void)invalidateToken {
@synchronized (self) {
if (self.activeToken) {
self.activeToken(YES);
}
self.activeToken = nil;
}
}
@end
+8
View File
@@ -16,6 +16,8 @@
#import <Foundation/Foundation.h>
typedef void (^tokenCallback_t)(BOOL);
NS_ASSUME_NONNULL_BEGIN
// The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service.
@@ -29,6 +31,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startQemu:(NSString *)binName standardOutput:(NSFileHandle *)standardOutput standardError:(NSFileHandle *)standardError libraryBookmark:(NSData *)libBookmark argv:(NSArray<NSString *> *)argv completion:(void(^)(BOOL,NSString *))completion;
- (void)terminate;
/// Helper holds on to the token to keep this XPC service active
///
/// If this is not called after `startQemu`, XNU may terminate this helper as idle.
/// - Parameter token: Token to hold, result may be discarded.
- (void)assertActiveWithToken:(tokenCallback_t)token;
@end
NS_ASSUME_NONNULL_END
+4
View File
@@ -234,6 +234,10 @@ static int defaultEntry(UTMProcess *self, int argc, const char *argv[], const ch
NSFileHandle *standardError = self.standardError.fileHandleForWriting;
[_connection.remoteObjectProxy setEnvironment:self.environment];
[_connection.remoteObjectProxy setCurrentDirectoryPath:self.currentDirectoryUrl.path];
// this is needed to prevent XNU from terminating an idle XPC helper
[_connection.remoteObjectProxy assertActiveWithToken:^(BOOL ignored) {
// do nothing
}];
[[_connection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
if (error.domain == NSCocoaErrorDomain && error.code == NSXPCConnectionInvalid) {
// inhibit this error since we always see it on quit