views:

226

answers:

2

I'm playing with Objective-C Distributed Objects and I'm having some problems understanding how memory management works under the system. The example given below illustrates my problem:

Protocol.h

#import <Foundation/Foundation.h>

@protocol DOServer
- (byref id)createTarget;
@end

Server.m

#import <Foundation/Foundation.h>
#import "Protocol.h"


@interface DOTarget : NSObject
@end


@interface DOServer : NSObject < DOServer >
@end


@implementation DOTarget

- (id)init
{
    if ((self = [super init]))
    {
        NSLog(@"Target created");
    }
    return self;
}

- (void)dealloc
{
    NSLog(@"Target destroyed");
    [super dealloc];
}

@end

@implementation DOServer

- (byref id)createTarget
{
    return [[[DOTarget alloc] init] autorelease];
}

@end


int main()
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    DOServer *server = [[DOServer alloc] init];

    NSConnection *connection  = [[NSConnection new] autorelease];
    [connection setRootObject:server];
    if ([connection registerName:@"test-server"] == NO)
    {
        NSLog(@"Failed to vend server object");
    }
    else
    {
        while (YES)
        {
            NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
            [[NSRunLoop currentRunLoop] runUntilDate:
                 [NSDate dateWithTimeIntervalSinceNow:0.1f]];
            [innerPool drain];
        }
    }

    [pool drain];
    return 0;
}

Client.m

#import <Foundation/Foundation.h>
#import "Protocol.h"

int main()
{
    unsigned i = 0;
    for (; i < 3; i ++)
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        id server = [NSConnection rootProxyForConnectionWithRegisteredName:@"test-server"
                                                                      host:nil];
        [server setProtocolForProxy:@protocol(DOServer)];
        NSLog(@"Created target: %@", [server createTarget]);

        [[NSRunLoop currentRunLoop] runUntilDate:
             [NSDate dateWithTimeIntervalSinceNow:1.0]];
        [pool drain];
    }
    return 0;
}

The issue is that any remote objects created by the root proxy are not released when their proxy counterparts in the client go out of scope. According to the documentation:

When an object’s remote proxy is deallocated, a message is sent back to the receiver to notify it that the local object is no longer shared over the connection.

I would therefore expect that as each DOTarget goes out of scope (each time around the loop) it's remote counterpart would be dellocated, since there is no other reference to it being held on the remote side of the connection.

In reality this does not happen: the temporary objects are only deallocate when the client application quits, or more accurately, when the connection is invalidated. I can force the temporary objects on the remote side to be deallocated by explicitly invalidating the NSConnection object I'm using each time around the loop and creating a new one but somehow this just feels wrong.

Is this the correct behaviour from DO? Should all temporary objects live as long as the connection that created them? Are connections therefore to be treated as temporary objects which should be opened and closed with each series of requests against the server?

Any insights would be appreciated.

A: 

The autorelease pool on your server is never drained, therefore your autoreleased objects never go "out of scope". You will always have an extra reference to them. Set up your server similar to the test you set up in your client (having the run loop dump out every second or so) and drain and establish a new inner pool each time. You will then see your expected results.

Jason Coco
@Jason Coco: are you sure about that? I seem to get the same results...perhaps my server code is still wrong? Check out the updated code...
jkp
Also, I distilled the problem down from a real Cocoa application, so in that instance it was all running on the main thread inside -[NSApplication run] where the pool is drained on each runloop iteration.
jkp
@jkp - Yeah, that is really strange. Perhaps they don't get cleaned up until the connection is released, or perhaps the connection does some kind of caching with them?
Jason Coco
@Jason Coco: Oh well, In the case of the code I'm writing I can use temporary connections for the duration of the "transaction" with the server. Its a shame though, it really doesn't feel right. Feels like you should be able to have a connection open to the server for the duration really: especially when you look at classes such as GTMTransientRootProxy (http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/GTMTransientRootProxy.h) which are supposed to help with error handling over long-running connections.
jkp
I agree, there should only be one connection for the duration.
manifest
A: 

Try not calling "autorelease" at all. Just allow the "createTarget" to return without retaining and assume that the object will be released when the proxy gets released on the client. When the Connection object does it's magic and returns a proxy to the client, it retains the server-local object in it's "localObjects" attribute. This way when the client's proxy goes out of scope, the Connection will release the local object, no need for autoreleasing.

I'm not really sure I'm correct but it seems like a valid explanation and while it seems a little strange to not call autorelease, this is DO which is a bit different. Even though the server is actually creating the object, it does not own it because the client created it (although remotely).

  • The client owns the object, because it created it
  • The client is responsible for retain/release of the proxy
  • The proxy and the object on the server's life-cycle are one and the same
  • All memory management should be done by the same entity that initiates allocation (in this case the client)
  • Never use "convenience" methods when creating objects remotely as this will call an autorelease

It's rather pathetic how scarce any explanation of how DO memory should be handled when created by the client.

manifest
@manifest: thanks for the input - have you managed to produce a working version of the example code above? Surely based on what you are saying the server-side object would end up with yet another retain count?
jkp
I'll see if I can try it but it will probably take awhile for me to get to it
manifest