objective-cmacosxpc

XPC client wait for service load


My installer load XPC service and XPC client that attempt to call remote xpc method on that service.

However, the service my be loaded arbitrarily and the client may get invalid connection since the service hasn't loaded yet. So far I haven't found any way get service-load indication, so I'm calling the retry method recursively on the connection invalidationHandler. Is this the correct approach ? is there any wait-for-service event I can wait for ?

+(void) callXpcWithRetry {
    NSXPCConnection* hubConnection = [[NSXPCConnection alloc] initWithMachServiceName:@"com.bla.myservice" options:0];
    hubConnection.remoteObjectInterface = getInterface();
    [hubConnection setInvalidationHandler:^{
        // No one is listening. connection cannot be established
        NSLog(@"Connection to keystore hub service invalidated .. retry in 5");

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            [ServiceDelegate registerBiometricKeyStoreRetry];
        });
    }];
    
    [hubConnection resume];
    id<myXpcProtocol> hub = [hubConnection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
    }];
    
    [hub xpcProtocolMethodForUser:NSUserName()];
}

Solution

  • You could just try to connect to it as below. This is real quick.

    // Connect to the service, this also checks to see if it is available
    mach_port_t connect_to_service ( const char * service_name )
    {
        mach_port_t bs_port, service_port;
        kern_return_t err;
    
        task_get_bootstrap_port ( mach_task_self (), & bs_port );
        err = bootstrap_look_up ( bs_port, service_name, & service_port );
    
        if ( err == KERN_SUCCESS )
        {
            return service_port;
    
        }
        else
        {
            return MACH_PORT_NULL;
    
        }
    }
    

    Here is the full test I used.

    #import <Foundation/Foundation.h>
    #import <bootstrap.h>
    
    // Connect to the service, this also checks to see if it is available
    mach_port_t connect_to_service ( const char * service_name )
    {
        mach_port_t bs_port, service_port;
        kern_return_t err;
    
        task_get_bootstrap_port ( mach_task_self (), & bs_port );
        err = bootstrap_look_up ( bs_port, service_name, & service_port );
    
        if ( err == KERN_SUCCESS )
        {
            return service_port;
    
        }
        else
        {
            return MACH_PORT_NULL;
    
        }
    }
    
    // Start the service
    start_service ( const char * service_name, dispatch_semaphore_t s )
    {
        // Simulate delay
        [NSThread sleepForTimeInterval:10];
    
        mach_port_t service_port = MACH_PORT_NULL;
        kern_return_t err;
    
        err = bootstrap_check_in ( bootstrap_port, service_name, & service_port );
        NSLog ( @"%s port %d err %d", service_name, service_port, err );
    
        NSLog ( @"Server up and running - signal semaphore" );
        dispatch_semaphore_signal ( s );
    
        // Simulate running server
        while ( YES )
        {
            [NSThread sleepForTimeInterval:60];
        }
    }
    
    int main(int argc, const char * argv[])
    {
        @autoreleasepool
        {
            // insert code here...
            NSLog(@"Hello, World!");
    
            // Configuration
            char * service_name = "com.hopla.test";
            mach_port_t port;
    
            // Check it
            NSLog ( @"Connecting to %s that is down", service_name );
            port = connect_to_service ( service_name );
            NSLog ( @"%s available : %@", service_name, port == MACH_PORT_NULL ? @"NO" : @"YES" );
    
            // The semaphore fires when the server is up, but is not used here
            dispatch_semaphore_t s = dispatch_semaphore_create ( 0 );
    
            // Starting service on background thread
            dispatch_async( dispatch_queue_create( "bak",
                                  dispatch_queue_attr_make_with_qos_class( DISPATCH_QUEUE_CONCURRENT,
                                                      QOS_CLASS_DEFAULT,
                                                      DISPATCH_QUEUE_PRIORITY_DEFAULT ) ), ^ {
    
                NSLog ( @"Starting %s", service_name );
                start_service ( service_name, s );
            } );
    
            // Repeatedly checks until the service is up
            // *** do something like in this loop ***
            while ( YES )
            {
                NSLog ( @"Connecting to %s that may be up", service_name );
                port = connect_to_service ( service_name );
                NSLog ( @"%s available : %@", service_name, port == MACH_PORT_NULL ? @"NO" : @"YES" );
    
                if ( port == MACH_PORT_NULL )
                {
                    [NSThread sleepForTimeInterval:5];
                }
                else
                {
                    break;
                }
            }
    
            // Final test - should be up
            port = connect_to_service ( service_name );
            NSLog ( @"%s fin available : %@", service_name, port == MACH_PORT_NULL ? @"NO" : @"YES" );
        }
    
        return 0;
    }
    

    I think you should do something as I do in the test (in main method and marked with ***). On some background thread, check repeatedly but with a decent loop if the server is up. Then you can signal a semaphore or use a completion or do whatever needs to be done once the server is up. Elsewhere you can wait on the semaphore or whatever is required.