objective-cmacosdistributed-objectsnsconnection

NSConnection - how to properly do "unvending" of an object?


For Mac OSX, I'm trying to use an NSConnection to proxy access of an object from one application instance to another on the same host. The relevant code is below. I can provide more if needed. Assume when I say "server", I'm referring to the application that actually "vends" an object with an NSConnection. And "client" is the other instance of the same application that gets a proxy to it.

Everything works, except for two issues.

  1. When an application acting as a server attempts to tear down the object it is vending, any client connected through a proxy still remains. That is, even after I call my stopLocalServer function below, any client app that previously connected and got a proxy object is still able to send messages and invoke code on the server app. I would have expected the client to throw an exception when passing a message after the server calls NSConnection:invalidate. How do I forcibly disconnect any client without requiring the server process to exit?

  2. In the startClientConnection code below, if the server never vended the object in the first place with the expected registered name, then the call on the client to NSConnection:connectionWithRegisteredName:host will immediately return nil. This is good. But if the server had started vending an object via the startLocalServer code below, then later stops vending it with stopLocalServer, subsequent client attempts to connect will hang (block forever) until the server application process exits. The call to NSConnection:connectionWithRegisteredName returns a non-nil object, but the call to [_clientConnection rootProxy] hangs forever until the server application actually exits.

I suspect that I'm not properly tearing down the original NSConnection object or I'm missing something basic here.

Here is some relevant code for the platform that my user interface code sits on top of:

-(void)startLocalServer:(NSString*)str
{
    [self stopLocalServer];  // clean up any previous instance that might be running
    _serverConnection = [NSConnection new];
    [_serverConnection setRootObject:self];
    [_serverConnection registerName:str];
}
-(void)stopLocalServer
{
    [_serverConnection registerName:nil];
    [_serverConnection setRootObject:nil];
    [_serverConnection invalidate];
    _serverConnection = nil;
}

-(void)startClientConnection:(NSString*)str
{
    [self stopClientConnection];  // tear down any previous connection

    _clientConnection = [NSConnection connectionWithRegisteredName:str host:nil];

    if ((_clientConnection == nil) || (_clientConnection.valid == NO))
    {
        LogEvent(@"ERROR - _clientConnection is nil or invalid!");
    }
    else
    {
        _proxy = [_clientConnection rootProxy];
    }
}

-(void)stopClientConnection
{
    _proxy = nil;
    [_clientConnection invalidate];
    _clientConnection = nil;
}

Solution

  • Answering my own question. I'll still hold out for a better answer, or if anyone can do a better job explaining the reasoning about why this is needed.

    I believe the stopLocalServer function needs to call [[_serverConnection receivePort] invalidate] such that the port created with the connection is closed. Just adding that line to the original stopLocalServer function solves my problem. That prevents further connection attempts and messages from succeeding.

    More appropriately, the application call can just own the port that the NSConnection uses. So this becomes a better solution for starting and stopping a distributed object listener:

    -(void)startLocalServer:(NSString*)str
    {
        [self stopLocalServer];  // clean up any previous instance that might be running
        _port = [NSPort port];   // _port is of type NSPort*
        _serverConnection = [NSConnection connectionWithReceivePort:_port sendPort:nil];
        [_serverConnection setRootObject:self];
        [_serverConnection registerName:str];
    }
    
    
    -(void)stopLocalServer
    {
        [_serverConnection registerName:nil];
        [_serverConnection setRootObject:nil];
        [_serverConnection invalidate];
        _serverConnection = nil;
    
        [_port invalidate];
        _port = nil;
    }
    

    That seems to solve both #1 and #2 above.