Re: Frustration trying to get NSConnection/NSPort working (feels like it should be simpler ...)
Re: Frustration trying to get NSConnection/NSPort working (feels like it should be simpler ...)
- Subject: Re: Frustration trying to get NSConnection/NSPort working (feels like it should be simpler ...)
- From: Neil Clayton <email@hidden>
- Date: Fri, 16 Feb 2007 14:35:00 +1300
Thanks for the tips. Warning: long post.
Actually, I was not holding onto the reference at all. My original
intention had been to get a proxy, use it, then have it released -
all within the space of the single event. The idea behind this was
that I'd then not have to concern myself so much over statefull
connection handling, error checking etc.
As it turns out, for whatever reason, that strategy just doesn't
really work.
I put together a basic client/server system, with the server being
able to handle multiple clients (just storing their references in an
NSArray). The server also had a connectionDied handler, and would as
much as possible remove dead proxies from the list. All good.
If on the client side I cache the proxy (not the connection, but I
assume the proxy has a reference to the connection, so it's retained
implicitly) then everything would work. I could start a "long
running" process on the server. Kill the client, rejoin the client -
and (after it reconnected to the server, of course) it'd pick up
notification messages. This seemed reasonable, simple, and what I
wanted. I had to setup the server connection using runInNewThread
and enableMultipleThreads, but that was the only change I really made.
However, If I instead got a connection, got the proxy, used it, and
let them be autoreleased, things just didn't work. The server got
confused. The client could not reconnect. It was very odd behavior,
especially since all I was doing was changing client code. The
server would correctly see the "connection death" (essentially, I'd
be making a new connection per method call, and that connection would
be killed after the event completed and was autoreleased) - but there
was still something weird going on that made it not work. It's been
a few hours since I looked at it, and my brain is currently
NSFriedConnection.
But I did see that retaining the proxy made a big difference, so off
I went and tried this in my existing code.
Better, but now when the server attempts to either a) use a value
from the client (for example, an argument on a remote method) or b)
call the client - the server hangs. As does the client. Looks like an
NSRunLoop deadlock (but I can't prove that).
Let me see if I can post some sensible code snippets:
The client is doing this (it's called from a CoreData object
awakeFromFoo method):
- (void) reconnectToServer {
id<ExporterFactoryImpl> factory = [ExporterFactory defaultFactory];
id<ExporterServer> server = [factory getServerFor:self];
BOOL newService = NO;
if(server == nil) {
// No server running, let ask one to be started for us
server = [factory spawnNewServerFor:self];
if(server == nil) {
[NSException raise:@"CantStart" format:@"Unable to start a server"];
}
newService = YES;
}
[self log:@"Setting client reference on server %@", server];
[server setClient:self];
[self log:@"Reconnection complete"];
// If it's a new task, get it perform some work.
if(newService) {
[self log:@"Requesting that server do some work"];
[server startProcessing:[[ExportParameters alloc]
initWithCompressionJob:self]];
[self log:@"Request sent"];
}
}
So that you can see what I'm doing inside the factory:
- (id<ExporterServer>) getServerFor:(CompressionJob*)forJob {
NSString *portName = [self serverPortNameForJob:forJob]; // simply
returns the managed object ID as a string
if(NO == [self messagePortExists:portName timeout:2]) { // use name
services to see if the ports alive/around
[self log:@"Cant find server with port %@, returning nil", portName];
return nil;
}
return [self proxy:portName];
}
- (id<ExporterServer>) proxy:(NSString*)portName {
// Is it in the cache?
if([proxyCache objectForKey:portName] != nil) {
[self log:@"Returning cached proxy for port %@", portName];
return [proxyCache objectForKey:portName];
}
NSConnection *connection = [NSConnection
connectionWithRegisteredName:portName host:nil];
if(connection == nil) {
[self log:@"Cant get connection to %@, returning nil", portName];
return nil;
}
id proxy = [connection rootProxy];
[proxy setProtocolForProxy:@protocol(ExporterServer)];
// Add this to our cache
[proxyCache setObject:proxy forKey:portName];
[self log:@"Caching proxy %@ (rc:%d)", proxy, [proxy retainCount]];
return proxy;
}
So, the proxy is retained, since it's contained by a
NSMutableDictionary.
Also, I see this on the console:
2007-02-16 13:15:00.052 Squish[14094] [Main] [NSExporterFactory] -
Caching proxy <SquishServer: 0x31fc40> (rc:2)
So, we have a retained proxy. All good.
Now, continuing with the theme of what we see on the console:
2007-02-16 13:15:00.788 SquishServer[14095] [Thread 1] [SquishServer]
- Server has received client callback as "Compress Stuff, /Users/neil/
Desktop/iShowU-Capture.mov"
2007-02-16 13:15:00.789 Squish[14094] [Main] [CompressionJob] -
Reconnection complete
2007-02-16 13:15:00.789 Squish[14094] [Main] [CompressionJob] -
Requesting that server do some work
2007-02-16 13:15:00.789 Squish[14094] [Main] [CompressionJob] -
Request sent
2007-02-16 13:15:00.808 SquishServer[14095] [Thread 1] [SquishServer]
- Entering start processing method
2007-02-16 13:15:00.815 SquishServer[14095] [Thread 1] [SquishServer]
- Starting export of movie, using <NSDistantObject: 0xa295ea18>
The server has been setup using:
NSConnection *connection = [NSConnection defaultConnection];
[connection setRootObject:delegate];
[connection runInNewThread];
[connection enableMultipleThreads];
if(![connection registerName:[self identifier]]) {
[NSException raise:@"CantPublish" format:@"Cannot publish self as a
server, with name %@", [self identifier]];
}
and so you can see that it receives a call to setup the client
callback (in this case, there is just one - not multiple as in my
test code). This call back is retained by the server. I am assuming
that the 'runInNewThread' call takes care of running an NSRunLoop for
the NSConnection, if one is required.
Now, by all accounts it appears that the client has correctly sent
its messages to the server. The server has the client callback, and
it also receives the message for startProcessing. However; if I on
the server either try to NSLog() the "parameters" to that method, or
try to call/use the client callback - both server and client hang.
By hand, I'm assuming that they are deadlocking in their NSRunLoops
for some reason.
I tell you, it's got me stumped.
The only thing I can think of relates to threading:
2007-02-16 13:15:00.808 SquishServer[14095] [Thread 1] [SquishServer]
- Entering start processing method
2007-02-16 13:15:00.815 SquishServer[14095] [Thread 1] [SquishServer]
- Starting export of movie, using <NSDistantObject: 0xa295ea18>
If the server was not running an event loop for Thread1, then it'd
probably fail. I am of course assuming that I dont' have to do that
- since I requested that the connection itself run itself via
multiple threads. I disabled the call (runInNewThead) and it made
no difference.
For completeness I've posted the code up via divshare (a temporary
link only). It's the server and client + factory parts of the code
(not a complete project), so that I don't end up make this huge post
even larger! http://www.divshare.com/download/131247-ffc
(NSFriedConnection.zip).
---
Regards,
Neil Clayton
On 16 Feb 2007, at 04:17, Michael Babin wrote:
On Feb 15, 2007, at 8:21 AM, Michael Babin wrote:
On Feb 14, 2007, at 5:47 PM, Neil Clayton wrote:
The client, after spawning the server and waiting to make sure
the message port exists, gets a reference to the server via:
NSConnection *connection = [NSConnection
connectionWithRegisteredName:portName host:nil];
id proxy = [connection rootProxy];
[proxy setProtocolForProxy:@protocol(ExporterServer)];
return proxy;
It then does the following (on the main thread):
[server setClient:self];
[server startProcessing:[[ExportParameters alloc]
initWithCompressionJob:self]];
You didn't provide all of the code involved (although a very good
summary), so you may be handling this correctly in the missing
part, but are you retaining the connection? Or are you releasing
it (you shouldn't until you're finished using it).
Actually, what I should have said here (to be clearer) is +
[NSConnection connectionWithRegisteredName:host:] may/probably
returns an autoreleased object. If you're holding on to this
reference past the current event loop iteration, you are retaining
it, correct? You're not by any chance releasing it (without a
matching retain), are you?
The stack trace in your debugger looks like some object is being
autoreleased that has already been released (and dealloced). See
<http://developer.apple.com/technotes/tn2004/tn2124.html>,
particularly the part about NSZombieEnabled to help track this
problem down.
--
Michael Babin email@hidden
Order N Development, LLC http://www.orderndev.com/
Goldfish Aquarium for Mac: http://www.lifeglobe.com/product/
index.php?pltid=2&ref=OrderNDev
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden