/** Implementation of connection object for remote object messaging Copyright (C) 1994-2017 Free Software Foundation, Inc. Created by: Andrew Kachites McCallum Date: July 1994 Minor rewrite for OPENSTEP by: Richard Frith-Macdonald Date: August 1997 Major rewrite for MACOSX by: Richard Frith-Macdonald Date: 2000 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. NSConnection class reference $Date$ $Revision$ */ #import "common.h" #if !defined (__GNU_LIBOBJC__) # include #endif #define GS_NSConnection_IVARS \ BOOL _isValid; \ BOOL _independentQueueing; \ BOOL _authenticateIn; \ BOOL _authenticateOut; \ BOOL _multipleThreads; \ BOOL _shuttingDown; \ BOOL _useKeepalive; \ BOOL _keepaliveWait; \ NSPort *_receivePort; \ NSPort *_sendPort; \ unsigned _requestDepth; \ unsigned _messageCount; \ unsigned _reqOutCount; \ unsigned _reqInCount; \ unsigned _repOutCount; \ unsigned _repInCount; \ GSIMapTable _localObjects; \ GSIMapTable _localTargets; \ GSIMapTable _remoteProxies; \ GSIMapTable _replyMap; \ NSTimeInterval _replyTimeout; \ NSTimeInterval _requestTimeout; \ NSMutableArray *_requestModes; \ NSMutableArray *_runLoops; \ NSMutableArray *_requestQueue; \ id _delegate; \ NSRecursiveLock *_refGate; \ NSMutableArray *_cachedDecoders; \ NSMutableArray *_cachedEncoders; \ NSString *_remoteName; \ NSString *_registeredName; \ NSPortNameServer *_nameServer; \ int _lastKeepalive #define EXPOSE_NSDistantObject_IVARS 1 #ifdef HAVE_MALLOC_H #if !defined(__OpenBSD__) #include #endif #endif #ifdef HAVE_ALLOCA_H #include #endif #import "Foundation/NSEnumerator.h" /* Skip past an argument and also any offset information before the next. */ static inline const char * skip_argspec(const char *ptr) { if (ptr != NULL) { ptr = NSGetSizeAndAlignment(ptr, NULL, NULL); if (*ptr == '+') ptr++; while (isdigit(*ptr)) ptr++; } return ptr; } /* * Setup for inline operation of pointer map tables. */ #define GSI_MAP_KTYPES GSUNION_PTR | GSUNION_OBJ | GSUNION_NSINT #define GSI_MAP_VTYPES GSUNION_PTR | GSUNION_OBJ #define GSI_MAP_RETAIN_KEY(M, X) #define GSI_MAP_RELEASE_KEY(M, X) #define GSI_MAP_RETAIN_VAL(M, X) #define GSI_MAP_RELEASE_VAL(M, X) #define GSI_MAP_HASH(M, X) ((X).nsu ^ ((X).nsu >> 3)) #define GSI_MAP_EQUAL(M, X,Y) ((X).ptr == (Y).ptr) #define GSI_MAP_NOCLEAN 1 #include "GNUstepBase/GSIMap.h" #define _IN_CONNECTION_M #import "Foundation/NSConnection.h" #undef _IN_CONNECTION_M #import "Foundation/NSPortCoder.h" #import "GNUstepBase/DistributedObjects.h" #import "Foundation/NSHashTable.h" #import "Foundation/NSMapTable.h" #import "Foundation/NSData.h" #import "Foundation/NSRunLoop.h" #import "Foundation/NSArray.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSValue.h" #import "Foundation/NSDate.h" #import "Foundation/NSException.h" #import "Foundation/NSLock.h" #import "Foundation/NSThread.h" #import "Foundation/NSPort.h" #import "Foundation/NSPortMessage.h" #import "Foundation/NSPortNameServer.h" #import "Foundation/NSNotification.h" #import "GSInvocation.h" #import "GSPortPrivate.h" #import "GSPrivate.h" static inline NSRunLoop * GSRunLoopForThread(NSThread *aThread) { GSRunLoopThreadInfo *info = GSRunLoopInfoForThread(aThread); if (info == nil || info->loop == nil) { if (aThread == nil || aThread == GSCurrentThread()) { return [NSRunLoop currentRunLoop]; } return nil; } return info->loop; } @interface NSPortCoder (Private) - (NSMutableArray*) _components; @end @interface NSPortMessage (Private) - (NSMutableArray*) _components; @end @interface NSConnection (GNUstepExtensions) - (void) finalize; - (void) forwardInvocation: (NSInvocation *)inv forProxy: (NSDistantObject*)object; - (const char *) typeForSelector: (SEL)sel remoteTarget: (unsigned)target; @end #define GS_F_LOCK(X) \ {NSDebugFLLog(@"GSConnection",@"Lock %@",X);[X lock];} #define GS_F_UNLOCK(X) \ {NSDebugFLLog(@"GSConnection",@"Unlock %@",X);[X unlock];} #define GS_M_LOCK(X) \ {NSDebugMLLog(@"GSConnection",@"Lock %@",X);[X lock];} #define GSM_UNLOCK(X) \ {NSDebugMLLog(@"GSConnection",@"Unlock %@",X);[X unlock];} /* * Cache various class pointers. */ static id dummyObject; static Class connectionClass; static Class dateClass; static Class distantObjectClass; static Class sendCoderClass; static Class recvCoderClass; static Class runLoopClass; static NSString* stringFromMsgType(int type) { switch (type) { case METHOD_REQUEST: return @"method request"; case METHOD_REPLY: return @"method reply"; case ROOTPROXY_REQUEST: return @"root proxy request"; case ROOTPROXY_REPLY: return @"root proxy reply"; case CONNECTION_SHUTDOWN: return @"connection shutdown"; case METHODTYPE_REQUEST: return @"methodtype request"; case METHODTYPE_REPLY: return @"methodtype reply"; case PROXY_RELEASE: return @"proxy release"; case PROXY_RETAIN: return @"proxy retain"; case RETAIN_REPLY: return @"retain replay"; default: return @"unknown operation type!"; } } /* * CachedLocalObject is a trivial class to keep track of local * proxies which have been removed from their connections and * need to persist a while in case another process needs them. */ @interface CachedLocalObject : NSObject { NSDistantObject *obj; int time; } - (BOOL) countdown; - (NSDistantObject*) obj; + (id) newWithObject: (NSDistantObject*)o time: (int)t; @end @implementation CachedLocalObject + (id) newWithObject: (NSDistantObject*)o time: (int)t { CachedLocalObject *item; item = (CachedLocalObject*)NSAllocateObject(self, 0, NSDefaultMallocZone()); item->obj = RETAIN(o); item->time = t; return item; } - (void) dealloc { RELEASE(obj); [super dealloc]; } - (BOOL) countdown { if (time-- > 0) return YES; return NO; } - (NSDistantObject*) obj { return obj; } @end /** */ #define GSInternal NSConnectionInternal #include "GSInternal.h" GS_PRIVATE_INTERNAL(NSConnection) #define IisValid (internal->_isValid) #define IindependentQueueing (internal->_independentQueueing) #define IauthenticateIn (internal->_authenticateIn) #define IauthenticateOut (internal->_authenticateOut) #define ImultipleThreads (internal->_multipleThreads) #define IshuttingDown (internal->_shuttingDown) #define IuseKeepalive (internal->_useKeepalive) #define IkeepaliveWait (internal->_keepaliveWait) #define IreceivePort (internal->_receivePort) #define IsendPort (internal->_sendPort) #define IrequestDepth (internal->_requestDepth) #define ImessageCount (internal->_messageCount) #define IreqOutCount (internal->_reqOutCount) #define IreqInCount (internal->_reqInCount) #define IrepOutCount (internal->_repOutCount) #define IrepInCount (internal->_repInCount) #define IlocalObjects (internal->_localObjects) #define IlocalTargets (internal->_localTargets) #define IremoteProxies (internal->_remoteProxies) #define IreplyMap (internal->_replyMap) #define IreplyTimeout (internal->_replyTimeout) #define IrequestTimeout (internal->_requestTimeout) #define IrequestModes (internal->_requestModes) #define IrunLoops (internal->_runLoops) #define IrequestQueue (internal->_requestQueue) #define Idelegate (internal->_delegate) #define IrefGate (internal->_refGate) #define IcachedDecoders (internal->_cachedDecoders) #define IcachedEncoders (internal->_cachedEncoders) #define IremoteName (internal->_remoteName) #define IregisteredName (internal->_registeredName) #define InameServer (internal->_nameServer) #define IlastKeepalive (internal->_lastKeepalive) /** */ @interface NSConnection(Private) - (void) handlePortMessage: (NSPortMessage*)msg; - (void) _runInNewThread; + (int) setDebug: (int)val; - (void) _enableKeepalive; - (void) addLocalObject: (NSDistantObject*)anObj; - (void) removeLocalObject: (NSDistantObject*)anObj; - (void) _doneInReply: (NSPortCoder*)c; - (void) _doneInRmc: (NSPortCoder*) NS_CONSUMED c; - (void) _failInRmc: (NSPortCoder*)c; - (void) _failOutRmc: (NSPortCoder*)c; - (NSPortCoder*) _getReplyRmc: (int)sn for: (const char*)request; - (NSPortCoder*) _newInRmc: (NSMutableArray*)components; - (NSPortCoder*) _newOutRmc: (int)sequence generate: (int*)sno reply: (BOOL)f; - (void) _portIsInvalid: (NSNotification*)notification; - (void) _sendOutRmc: (NSPortCoder*) NS_CONSUMED c type: (int)msgid sequence: (int)sno; - (void) _service_forwardForProxy: (NSPortCoder*)rmc; - (void) _service_release: (NSPortCoder*)rmc; - (void) _service_retain: (NSPortCoder*)rmc; - (void) _service_rootObject: (NSPortCoder*)rmc; - (void) _service_shutdown: (NSPortCoder*)rmc; - (void) _service_typeForSelector: (NSPortCoder*)rmc; - (void) _shutdown; + (void) _threadWillExit: (NSNotification*)notification; @end /* class defaults */ static NSTimer *timer = nil; static BOOL cacheCoders = NO; static int debug_connection = 0; static NSHashTable *connection_table; static NSRecursiveLock *connection_table_gate = nil; /* * Locate an existing connection with the specified send and receive ports. * nil ports act as wildcards and return the first match. */ static NSConnection* existingConnection(NSPort *receivePort, NSPort *sendPort) { NSHashEnumerator enumerator; NSConnection *c; GS_F_LOCK(connection_table_gate); enumerator = NSEnumerateHashTable(connection_table); while ((c = (NSConnection*)NSNextHashEnumeratorItem(&enumerator)) != nil) { if ((sendPort == nil || [sendPort isEqual: [c sendPort]]) && (receivePort == nil || [receivePort isEqual: [c receivePort]])) { /* * We don't want this connection to be destroyed by another thread * between now and when it's returned from this function and used! */ IF_NO_ARC([[c retain] autorelease];) break; } } NSEndHashTableEnumeration(&enumerator); GS_F_UNLOCK(connection_table_gate); return c; } static NSMapTable *root_object_map; static NSLock *root_object_map_gate = nil; static id rootObjectForInPort(NSPort *aPort) { id rootObject; GS_F_LOCK(root_object_map_gate); rootObject = (id)NSMapGet(root_object_map, (void*)(uintptr_t)aPort); GS_F_UNLOCK(root_object_map_gate); return rootObject; } /* Pass nil to remove any reference keyed by aPort. */ static void setRootObjectForInPort(id anObj, NSPort *aPort) { id oldRootObject; GS_F_LOCK(root_object_map_gate); oldRootObject = (id)NSMapGet(root_object_map, (void*)(uintptr_t)aPort); if (oldRootObject != anObj) { if (anObj != nil) { NSMapInsert(root_object_map, (void*)(uintptr_t)aPort, (void*)(uintptr_t)anObj); } else /* anObj == nil && oldRootObject != nil */ { NSMapRemove(root_object_map, (void*)(uintptr_t)aPort); } } GS_F_UNLOCK(root_object_map_gate); } static NSMapTable *targetToCached = NULL; static NSLock *cached_proxies_gate = nil; /** * NSConnection objects are used to manage communications between * objects in different processes, in different machines, or in * different threads. */ @implementation NSConnection /** * Returns an array containing all the NSConnection objects known to * the system. These connections will be valid at the time that the * array was created, but may be invalidated by other threads * before you get to examine the array. */ + (NSArray*) allConnections { NSArray *a; GS_M_LOCK(connection_table_gate); a = NSAllHashTableObjects(connection_table); GSM_UNLOCK(connection_table_gate); return a; } /** * Returns a connection initialised using -initWithReceivePort:sendPort:
* Both ports must be of the same type. */ + (NSConnection*) connectionWithReceivePort: (NSPort*)r sendPort: (NSPort*)s { NSConnection *c = existingConnection(r, s); if (c == nil) { c = [self allocWithZone: NSDefaultMallocZone()]; c = [c initWithReceivePort: r sendPort: s]; IF_NO_ARC([c autorelease];) } return c; } /** *

Returns an NSConnection object whose send port is that of the * NSConnection registered under the name n on the host h *

*

This method calls +connectionWithRegisteredName:host:usingNameServer: * using the default system name server. *

*

Use [NSSocketPortNameServer] for connections to remote hosts. *

*/ + (NSConnection*) connectionWithRegisteredName: (NSString*)n host: (NSString*)h { NSPortNameServer *s; s = [NSPortNameServer systemDefaultPortNameServer]; return [self connectionWithRegisteredName: n host: h usingNameServer: s]; } /** *

* Returns an NSConnection object whose send port is that of the * NSConnection registered under name on host. *

*

* The nameserver server is used to look up the send * port to be used for the connection.
* Use [NSSocketPortNameServer+sharedInstance] * for connections to remote hosts. *

*

* If host is nil or an empty string, * the host is taken to be the local machine.
* If it is an asterisk ('*') then the nameserver checks all * hosts on the local subnet (unless the nameserver is one * that only manages local ports).
* In the GNUstep implementation, the local host is searched before * any other hosts.
* NB. if the nameserver does not support connections to remote hosts * (the default situation) the host argeument should be omitted. *

*

* If no NSConnection can be found for name and * hosthost, the method returns nil. *

*

* The returned object has the default NSConnection of the * current thread as its parent (it has the same receive port * as the default connection). *

*/ + (NSConnection*) connectionWithRegisteredName: (NSString*)n host: (NSString*)h usingNameServer: (NSPortNameServer*)s { NSConnection *con = nil; if (s != nil) { NSPort *sendPort = [s portForName: n onHost: h]; if (sendPort != nil) { NSPort *recvPort; recvPort = [[self defaultConnection] receivePort]; if (recvPort == sendPort) { /* * If the receive and send port are the same, the server * must be in this process - so we need to create a new * connection to talk to it. */ recvPort = [NSPort port]; } else if (![recvPort isMemberOfClass: [sendPort class]]) { /* We can only use the port of the default connection for connections using the same port class. For other port classes, we must use a receiving port of the same class as the sending port, so we allocate one here. */ recvPort = [[sendPort class] port]; } con = existingConnection(recvPort, sendPort); if (con == nil) { con = [self connectionWithReceivePort: recvPort sendPort: sendPort]; } ASSIGNCOPY(GSIVar(con, _remoteName), n); } } return con; } /** * Return the current conversation ... not implemented in GNUstep */ + (id) currentConversation { return nil; } /** * Returns the default connection for a thread.
* Creates a new instance if necessary.
* The default connection has a single NSPort object used for * both sending and receiving - this it can't be used to * connect to a remote process, but can be used to vend objects.
* Possible problem - if the connection is invalidated, it won't be * cleaned up until this thread calls this method again. The connection * and it's ports could hang around for a very long time. */ + (NSConnection*) defaultConnection { static NSString *tkey = @"NSConnectionThreadKey"; NSConnection *c; NSMutableDictionary *d; d = GSCurrentThreadDictionary(); c = (NSConnection*)[d objectForKey: tkey]; if (c != nil && [c isValid] == NO) { /* * If the default connection for this thread has been invalidated - * release it and create a new one. */ [d removeObjectForKey: tkey]; c = nil; } if (c == nil) { NSPort *port; c = [self alloc]; port = [NSPort port]; c = [c initWithReceivePort: port sendPort: nil]; if (c != nil) { [d setObject: c forKey: tkey]; RELEASE(c); } } return c; } + (void) initialize { if (connectionClass == nil) { NSNotificationCenter *nc; connectionClass = self; dateClass = [NSDate class]; distantObjectClass = [NSDistantObject class]; sendCoderClass = [NSPortCoder class]; recvCoderClass = [NSPortCoder class]; runLoopClass = [NSRunLoop class]; dummyObject = [NSObject new]; [[NSObject leakAt: &dummyObject] release]; connection_table = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0); [[NSObject leakAt: &connection_table] release]; targetToCached = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSObjectMapValueCallBacks, 0); [[NSObject leakAt: &targetToCached] release]; root_object_map = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSObjectMapValueCallBacks, 0); [[NSObject leakAt: &root_object_map] release]; if (connection_table_gate == nil) { connection_table_gate = [NSRecursiveLock new]; [[NSObject leakAt: &connection_table_gate] release]; } if (cached_proxies_gate == nil) { cached_proxies_gate = [NSLock new]; [[NSObject leakAt: &cached_proxies_gate] release]; } if (root_object_map_gate == nil) { root_object_map_gate = [NSLock new]; [[NSObject leakAt: &root_object_map_gate] release]; } /* * When any thread exits, we must check to see if we are using its * runloop, and remove ourselves from it if necessary. */ nc = [NSNotificationCenter defaultCenter]; [nc addObserver: self selector: @selector(_threadWillExit:) name: NSThreadWillExitNotification object: nil]; } } /** * Undocumented feature for compatibility with OPENSTEP/MacOS-X * +new returns the default connection. */ + (id) new { return RETAIN([self defaultConnection]); } /** * This method calls * +rootProxyForConnectionWithRegisteredName:host:usingNameServer: * to return a proxy for a root object on the remote connection with * the send port registered under name n on host h. */ + (NSDistantObject*) rootProxyForConnectionWithRegisteredName: (NSString*)n host: (NSString*)h { NSAutoreleasePool *arp = [NSAutoreleasePool new]; NSConnection *connection; NSDistantObject *proxy = nil; connection = [self connectionWithRegisteredName: n host: h]; if (connection != nil) { proxy = [[connection rootProxy] retain]; } [arp drain]; return [proxy autorelease]; } /** * This method calls * +connectionWithRegisteredName:host:usingNameServer: * to get a connection, then sends it a -rootProxy message to get * a proxy for the root object being vended by the remote connection. * Returns the proxy or nil if it couldn't find a connection or if * the root object for the connection has not been set.
* Use [NSSocketPortNameServer+sharedInstance] * for connections to remote hosts. */ + (NSDistantObject*) rootProxyForConnectionWithRegisteredName: (NSString*)n host: (NSString*)h usingNameServer: (NSPortNameServer*)s { NSAutoreleasePool *arp = [NSAutoreleasePool new]; NSConnection *connection; NSDistantObject *proxy = nil; connection = [self connectionWithRegisteredName: n host: h usingNameServer: s]; if (connection != nil) { proxy = RETAIN([connection rootProxy]); } [arp drain]; return AUTORELEASE(proxy); } + (id) serviceConnectionWithName: (NSString *)name rootObject: (id)root { return [self serviceConnectionWithName: name rootObject: root usingNameServer: [NSPortNameServer systemDefaultPortNameServer]]; } + (id) serviceConnectionWithName: (NSString *)name rootObject: (id)root usingNameServer: (NSPortNameServer *)server { NSConnection *c; NSPort *p; if ([server isKindOfClass: [NSMessagePortNameServer class]] == YES) { p = [NSMessagePort port]; } else if ([server isKindOfClass: [NSSocketPortNameServer class]] == YES) { p = [NSSocketPort port]; } else { p = nil; } c = [[NSConnection alloc] initWithReceivePort: p sendPort: nil]; [c setRootObject: root]; if ([c registerName: name withNameServer: server] == NO) { DESTROY(c); } return AUTORELEASE(c); } + (void) _timeout: (NSTimer*)t { NSArray *cached_locals; int i; GS_M_LOCK(cached_proxies_gate); cached_locals = NSAllMapTableValues(targetToCached); for (i = [cached_locals count]; i > 0; i--) { CachedLocalObject *item = [cached_locals objectAtIndex: i-1]; if ([item countdown] == NO) { NSDistantObject *obj = [item obj]; NSMapRemove(targetToCached, (void*)(uintptr_t)obj->_handle); } } if ([cached_locals count] == 0) { [t invalidate]; timer = nil; } GSM_UNLOCK(cached_proxies_gate); } /** * Adds mode to the run loop modes that the NSConnection * will listen to for incoming messages. */ - (void) addRequestMode: (NSString*)mode { GS_M_LOCK(IrefGate); if ([self isValid] == YES) { if ([IrequestModes containsObject: mode] == NO) { NSUInteger c = [IrunLoops count]; while (c-- > 0) { NSRunLoop *loop = [IrunLoops objectAtIndex: c]; [IreceivePort addConnection: self toRunLoop: loop forMode: mode]; } [IrequestModes addObject: mode]; } } GSM_UNLOCK(IrefGate); } /** * Adds loop to the set of run loops that the NSConnection * will listen to for incoming messages. */ - (void) addRunLoop: (NSRunLoop*)loop { GS_M_LOCK(IrefGate); if ([self isValid] == YES) { if ([IrunLoops indexOfObjectIdenticalTo: loop] == NSNotFound) { NSUInteger c = [IrequestModes count]; while (c-- > 0) { NSString *mode = [IrequestModes objectAtIndex: c]; [IreceivePort addConnection: self toRunLoop: loop forMode: mode]; } [IrunLoops addObject: loop]; } } GSM_UNLOCK(IrefGate); } - (void) dealloc { if (debug_connection) NSLog(@"deallocating %@", self); if (GS_EXISTS_INTERNAL) { [self finalize]; GS_DESTROY_INTERNAL(NSConnection); } [super dealloc]; } /** * Returns the delegate of the NSConnection. */ - (id) delegate { return Idelegate; } - (NSString*) description { return [NSString stringWithFormat: @"%@ local: '%@',%@ remote '%@',%@", [super description], IregisteredName ? (id)IregisteredName : (id)@"", [self receivePort], IremoteName ? (id)IremoteName : (id)@"", [self sendPort]]; } /** * Sets the NSConnection configuration so that multiple threads may * use the connection to send requests to the remote connection.
* This option is inherited by child connections.
* NB. A connection with multiple threads enabled will run slower than * a normal connection. */ - (void) enableMultipleThreads { ImultipleThreads = YES; } /** * Returns YES if the NSConnection is configured to * handle remote messages atomically, NO otherwise.
* This option is inherited by child connections. */ - (BOOL) independentConversationQueueing { return IindependentQueueing; } /** * Return a connection able to act as a server receive incoming requests. */ - (id) init { NSPort *port = [NSPort port]; self = [self initWithReceivePort: port sendPort: nil]; return self; } /** * Initialises an NSConnection with the receive port r and the * send port s.
* Behavior varies with the port values as follows - * * r is nil * * The NSConnection is released and the method returns * nil. * * s is nil * * The NSConnection uses r as the send port as * well as the receive port. * * s is the same as r * * The NSConnection is usable only for vending objects. * * A connection with the same ports exists * * The new connection is released and the old connection * is retained and returned. * * A connection with the same ports (swapped) exists * * The new connection is initialised as normal, and will * communicate with the old connection. * * *

* If a connection exists whose send and receive ports are * both the same as the new connections receive port, that * existing connection is deemed to be the parent of the * new connection. The new connection inherits configuration * information from the parent, and the delegate of the * parent has a chance to adjust the configuration of the * new connection or veto its creation. *
* NSConnectionDidInitializeNotification is posted once a new * connection is initialised. *

*/ - (id) initWithReceivePort: (NSPort*)r sendPort: (NSPort*)s { NSNotificationCenter *nCenter; NSConnection *parent; NSConnection *conn; NSRunLoop *loop; id del; NSZone *z; z = NSDefaultMallocZone(); /* * If the receive port is nil, deallocate connection and return nil. */ if (r == nil) { if (debug_connection > 2) { NSLog(@"Asked to create connection with nil receive port"); } DESTROY(self); return self; } /* * If the send port is nil, set it to the same as the receive port * This connection will then only be useful to act as a server. */ if (s == nil) { s = r; } conn = existingConnection(r, s); /* * If the send and receive ports match an existing connection * deallocate the new one and retain and return the old one. */ if (conn != nil) { DESTROY(self); self = RETAIN(conn); if (debug_connection > 2) { NSLog(@"Found existing connection (%@) for \n\t%@\n\t%@", conn, r, s); } return self; } /* Create our private data structure. */ GS_CREATE_INTERNAL(NSConnection); /* * The parent connection is the one whose send and receive ports are * both the same as our receive port. */ parent = existingConnection(r, r); if (debug_connection) { NSLog(@"Initialising new connection with parent %@, %@\n " @"Send: %@\n Recv: %@", parent, self, s, r); } GS_M_LOCK(connection_table_gate); IisValid = YES; IreceivePort = RETAIN(r); IsendPort = RETAIN(s); ImessageCount = 0; IrepOutCount = 0; IreqOutCount = 0; IrepInCount = 0; IreqInCount = 0; /* * These arrays cache NSPortCoder objects */ if (cacheCoders == YES) { IcachedDecoders = [NSMutableArray new]; IcachedEncoders = [NSMutableArray new]; } /* * This is used to queue up incoming NSPortMessages representing requests * that can't immediately be dealt with. */ IrequestQueue = [NSMutableArray new]; /* * This maps request sequence numbers to the NSPortCoder objects representing * replies arriving from the remote connection. */ IreplyMap = (GSIMapTable)NSZoneMalloc(z, sizeof(GSIMapTable_t)); GSIMapInitWithZoneAndCapacity(IreplyMap, z, 4); /* * This maps (void*)obj to (id)obj. The obj's are retained. * We use this instead of an NSHashTable because we only care about * the object's address, and don't want to send the -hash message to it. */ IlocalObjects = (GSIMapTable)NSZoneMalloc(z, sizeof(GSIMapTable_t)); GSIMapInitWithZoneAndCapacity(IlocalObjects, z, 4); /* * This maps handles for local objects to their local proxies. */ IlocalTargets = (GSIMapTable)NSZoneMalloc(z, sizeof(GSIMapTable_t)); GSIMapInitWithZoneAndCapacity(IlocalTargets, z, 4); /* * This maps targets to remote proxies. */ IremoteProxies = (GSIMapTable)NSZoneMalloc(z, sizeof(GSIMapTable_t)); GSIMapInitWithZoneAndCapacity(IremoteProxies, z, 4); IrequestDepth = 0; Idelegate = nil; IrefGate = [NSRecursiveLock new]; /* * Some attributes are inherited from the parent if possible. */ if (parent != nil) { NSUInteger count; ImultipleThreads = GSIVar(parent, _multipleThreads); IindependentQueueing = GSIVar(parent, _independentQueueing); IreplyTimeout = GSIVar(parent, _replyTimeout); IrequestTimeout = GSIVar(parent, _requestTimeout); IrunLoops = [GSIVar(parent, _runLoops) mutableCopy]; count = [GSIVar(parent, _requestModes) count]; IrequestModes = [[NSMutableArray alloc] initWithCapacity: count]; while (count-- > 0) { [self addRequestMode: [GSIVar(parent, _requestModes) objectAtIndex: count]]; } if (GSIVar(parent, _useKeepalive) == YES) { [self _enableKeepalive]; } } else { ImultipleThreads = NO; IindependentQueueing = NO; IreplyTimeout = 1.0E12; IrequestTimeout = 1.0E12; /* * Set up request modes array and make sure the receiving port * is added to the run loop to get data. */ loop = GSRunLoopForThread(nil); IrunLoops = [[NSMutableArray alloc] initWithObjects: &loop count: 1]; IrequestModes = [[NSMutableArray alloc] initWithCapacity: 2]; [self addRequestMode: NSDefaultRunLoopMode]; [self addRequestMode: NSConnectionReplyMode]; IuseKeepalive = NO; /* * If we have no parent, we must handle incoming packets on our * receive port ourself - so we set ourself up as the port delegate. */ [IreceivePort setDelegate: self]; } /* Ask the delegate for permission, (OpenStep-style and GNUstep-style). */ /* Preferred MacOS-X version, which just allows the returning of BOOL */ del = [parent delegate]; if ([del respondsToSelector: @selector(connection:shouldMakeNewConnection:)]) { if ([del connection: parent shouldMakeNewConnection: self] == NO) { GSM_UNLOCK(connection_table_gate); DESTROY(self); return nil; } } /* Deprecated OpenStep version, which just allows the returning of BOOL */ if ([del respondsToSelector: @selector(makeNewConnection:sender:)]) { if (![del makeNewConnection: self sender: parent]) { GSM_UNLOCK(connection_table_gate); DESTROY(self); return nil; } } /* Here is the GNUstep version, which allows the delegate to specify a substitute. Note: The delegate is responsible for freeing newConn if it returns something different. */ if ([del respondsToSelector: @selector(connection:didConnect:)]) { self = [del connection: parent didConnect: self]; } nCenter = [NSNotificationCenter defaultCenter]; /* * Register ourselves for invalidation notification when the * ports become invalid. */ [nCenter addObserver: self selector: @selector(_portIsInvalid:) name: NSPortDidBecomeInvalidNotification object: r]; if (s != nil) { [nCenter addObserver: self selector: @selector(_portIsInvalid:) name: NSPortDidBecomeInvalidNotification object: s]; } /* In order that connections may be deallocated - there is an implementation of [-release] to automatically remove the connection from this array when it is the only thing retaining it. */ NSHashInsert(connection_table, (void*)self); GSM_UNLOCK(connection_table_gate); [nCenter postNotificationName: NSConnectionDidInitializeNotification object: self]; return self; } /** * Marks the receiving NSConnection as invalid. *
* Removes the NSConnections ports from any run loops. *
* Posts an NSConnectionDidDieNotification. *
* Invalidates all remote objects and local proxies. */ - (void) invalidate { GS_M_LOCK(IrefGate); if (IisValid == NO) { GSM_UNLOCK(IrefGate); return; } if (IshuttingDown == NO) { IshuttingDown = YES; /* * Not invalidated as a result of a shutdown from the other end, * so tell the other end it must shut down. */ //[self _shutdown]; } IisValid = NO; GS_M_LOCK(connection_table_gate); NSHashRemove(connection_table, self); GSM_UNLOCK(connection_table_gate); GSM_UNLOCK(IrefGate); /* * Don't need notifications any more - so remove self as observer. */ [[NSNotificationCenter defaultCenter] removeObserver: self]; /* * Make sure we are not registered. */ #if !defined(_WIN32) if ([IreceivePort isKindOfClass: [NSMessagePort class]]) { [self registerName: nil withNameServer: [NSMessagePortNameServer sharedInstance]]; } else #endif if ([IreceivePort isKindOfClass: [NSSocketPort class]]) { [self registerName: nil withNameServer: [NSSocketPortNameServer sharedInstance]]; } else { [self registerName: nil]; } /* * Withdraw from run loops. */ [self setRequestMode: nil]; IF_NO_ARC(RETAIN(self);) if (debug_connection) { NSLog(@"Invalidating connection %@", self); } /* * We need to notify any watchers of our death - but if we are already * in the deallocation process, we can't have a notification retaining * and autoreleasing us later once we are deallocated - so we do the * notification with a local autorelease pool to ensure that any release * is done before the deallocation completes. */ { NSAutoreleasePool *arp = [NSAutoreleasePool new]; [[NSNotificationCenter defaultCenter] postNotificationName: NSConnectionDidDieNotification object: self]; [arp drain]; } /* * If we have been invalidated, we don't need to retain proxies * for local objects any more. In fact, we want to get rid of * these proxies in case they are keeping us retained when we * might otherwise de deallocated. */ GS_M_LOCK(IrefGate); if (IlocalTargets != 0) { NSMutableArray *targets; NSUInteger i = IlocalTargets->nodeCount; GSIMapEnumerator_t enumerator; GSIMapNode node; targets = [[NSMutableArray alloc] initWithCapacity: i]; enumerator = GSIMapEnumeratorForMap(IlocalTargets); node = GSIMapEnumeratorNextNode(&enumerator); while (node != 0) { [targets addObject: node->value.obj]; node = GSIMapEnumeratorNextNode(&enumerator); } while (i-- > 0) { [self removeLocalObject: [targets objectAtIndex: i]]; } RELEASE(targets); GSIMapEmptyMap(IlocalTargets); NSZoneFree(IlocalTargets->zone, (void*)IlocalTargets); IlocalTargets = 0; } if (IremoteProxies != 0) { GSIMapEmptyMap(IremoteProxies); NSZoneFree(IremoteProxies->zone, (void*)IremoteProxies); IremoteProxies = 0; } if (IlocalObjects != 0) { GSIMapEnumerator_t enumerator; GSIMapNode node; enumerator = GSIMapEnumeratorForMap(IlocalObjects); node = GSIMapEnumeratorNextNode(&enumerator); while (node != 0) { RELEASE(node->key.obj); node = GSIMapEnumeratorNextNode(&enumerator); } GSIMapEmptyMap(IlocalObjects); NSZoneFree(IlocalObjects->zone, (void*)IlocalObjects); IlocalObjects = 0; } GSM_UNLOCK(IrefGate); /* * If we are invalidated, we shouldn't be receiving any event and * should not need to be in any run loops. */ while ([IrunLoops count] > 0) { [self removeRunLoop: [IrunLoops lastObject]]; } /* * Invalidate the current conversation so we don't leak. */ if ([IsendPort isValid] == YES) { [[IsendPort conversation: IreceivePort] invalidate]; } RELEASE(self); } /** * Returns YES if the connection is valid, NO otherwise. * A connection is valid until it has been sent an -invalidate message. */ - (BOOL) isValid { return IisValid; } /** * Returns an array of all the local objects that have proxies at the * remote end of the connection because they have been sent over the * connection and not yet released by the far end. */ - (NSArray*) localObjects { NSArray *a; /* Don't assert (IisValid); */ GS_M_LOCK(IrefGate); if (IlocalObjects != 0) { GSIMapEnumerator_t enumerator; GSIMapNode node; NSMutableArray *c; enumerator = GSIMapEnumeratorForMap(IlocalObjects); node = GSIMapEnumeratorNextNode(&enumerator); c = [NSMutableArray arrayWithCapacity: IlocalObjects->nodeCount]; while (node != 0) { [c addObject: node->key.obj]; node = GSIMapEnumeratorNextNode(&enumerator); } a = c; } else { a = [NSArray array]; } GSM_UNLOCK(IrefGate); return a; } /** * Returns YES if the connection permits multiple threads to use it to * send requests, NO otherwise.
* See the -enableMultipleThreads method. */ - (BOOL) multipleThreadsEnabled { return ImultipleThreads; } /** * Returns the NSPort object on which incoming messages are received. */ - (NSPort*) receivePort { return IreceivePort; } /** * Simply invokes -registerName:withNameServer: * passing it the default system nameserver. */ - (BOOL) registerName: (NSString*)name { NSPortNameServer *svr = [NSPortNameServer systemDefaultPortNameServer]; return [self registerName: name withNameServer: svr]; } /** * Registers the receive port of the NSConnection as name and * unregisters the previous value (if any).
* Returns YES on success, NO on failure.
* On failure, the connection remains registered under the * previous name.
* Supply nil as name to unregister the NSConnection. */ - (BOOL) registerName: (NSString*)name withNameServer: (NSPortNameServer*)svr { BOOL result = YES; if (name != nil) { result = [svr registerPort: IreceivePort forName: name]; } if (result == YES) { if (IregisteredName != nil) { [InameServer removePort: IreceivePort forName: IregisteredName]; } ASSIGN(IregisteredName, name); ASSIGN(InameServer, svr); } return result; } - (oneway void) release { /* We lock the connection table while checking, to prevent * another thread from grabbing this connection while we are * checking it. * If we are going to deallocate the object, we first remove * it from the table so that no other thread will find it * and try to use it while it is being deallocated. */ GS_M_LOCK(connection_table_gate); if (1 == [self retainCount]) { NSHashRemove(connection_table, self); } GSM_UNLOCK(connection_table_gate); [super release]; } /** * Returns an array of proxies to all the remote objects known to * the NSConnection. */ - (NSArray *) remoteObjects { NSMutableArray *c; /* Don't assert (IisValid); */ GS_M_LOCK(IrefGate); if (IremoteProxies != 0) { GSIMapEnumerator_t enumerator; GSIMapNode node; enumerator = GSIMapEnumeratorForMap(IremoteProxies); node = GSIMapEnumeratorNextNode(&enumerator); c = [NSMutableArray arrayWithCapacity: IremoteProxies->nodeCount]; while (node != 0) { [c addObject: node->key.obj]; node = GSIMapEnumeratorNextNode(&enumerator); } } else { c = [NSMutableArray array]; } GSM_UNLOCK(IrefGate); return c; } /** * Removes mode from the run loop modes used to receive incoming messages. */ - (void) removeRequestMode: (NSString*)mode { GS_M_LOCK(IrefGate); if (IrequestModes != nil && [IrequestModes containsObject: mode]) { NSUInteger c = [IrunLoops count]; while (c-- > 0) { NSRunLoop *loop = [IrunLoops objectAtIndex: c]; [IreceivePort removeConnection: self fromRunLoop: loop forMode: mode]; } [IrequestModes removeObject: mode]; } GSM_UNLOCK(IrefGate); } /** * Removes loop from the run loops used to receive incoming messages. */ - (void) removeRunLoop: (NSRunLoop*)loop { GS_M_LOCK(IrefGate); if (IrunLoops != nil) { NSUInteger pos = [IrunLoops indexOfObjectIdenticalTo: loop]; if (pos != NSNotFound) { NSUInteger c = [IrequestModes count]; while (c-- > 0) { NSString *mode = [IrequestModes objectAtIndex: c]; [IreceivePort removeConnection: self fromRunLoop: [IrunLoops objectAtIndex: pos] forMode: mode]; } [IrunLoops removeObjectAtIndex: pos]; } } GSM_UNLOCK(IrefGate); } /** * Returns the timeout interval used when waiting for a reply to * a request sent on the NSConnection. This value is inherited * from the parent connection or may be set using the -setReplyTimeout: * method.
* The default value is the maximum delay (effectively infinite). */ - (NSTimeInterval) replyTimeout { return IreplyTimeout; } /** * Returns an array of all the run loop modes that the NSConnection * uses when waiting for an incoming request. */ - (NSArray*) requestModes { NSArray *c; GS_M_LOCK(IrefGate); c = AUTORELEASE([IrequestModes copy]); GSM_UNLOCK(IrefGate); return c; } /** * Returns the timeout interval used when trying to send a request * on the NSConnection. This value is inherited from the parent * connection or may be set using the -setRequestTimeout: method.
* The default value is the maximum delay (effectively infinite). */ - (NSTimeInterval) requestTimeout { return IrequestTimeout; } /** * Returns the object that is made available by this connection * or by its parent (the object is associated with the receive port).
* Returns nil if no root object has been set. */ - (id) rootObject { return rootObjectForInPort(IreceivePort); } /** * Returns the proxy for the root object of the remote NSConnection.
* Generally you will wish to call [NSDistantObject-setProtocolForProxy:] * immediately after obtaining such a root proxy. */ - (NSDistantObject*) rootProxy { NSPortCoder *op; NSPortCoder *ip; NSDistantObject *newProxy = nil; int seq_num; NSParameterAssert(IreceivePort); NSParameterAssert(IisValid); NS_DURING { /* * If this is a server connection without a remote end, its root proxy * is the same as its root object. */ if (IreceivePort == IsendPort) { return [self rootObject]; } op = [self _newOutRmc: 0 generate: &seq_num reply: YES]; [self _sendOutRmc: op type: ROOTPROXY_REQUEST sequence: seq_num]; ip = [self _getReplyRmc: seq_num for: "rootproxy"]; [ip decodeValueOfObjCType: @encode(id) at: &newProxy]; [self _doneInRmc: ip]; } NS_HANDLER { /* The ports/connection may have been invalidated while getting the * root proxy ... if so we should return nil. */ newProxy = nil; } NS_ENDHANDLER return AUTORELEASE(newProxy); } /** * Removes the NSConnection from the current threads default * run loop, then creates a new thread and runs the NSConnection in it. */ - (void) runInNewThread { [self removeRunLoop: GSRunLoopForThread(nil)]; [NSThread detachNewThreadSelector: @selector(_runInNewThread) toTarget: self withObject: nil]; } /** * Returns the port on which the NSConnection sends messages. */ - (NSPort*) sendPort { return IsendPort; } /** * Sets the NSConnection's delegate (without retaining it).
* The delegate is able to control some of the NSConnection's * behavior by implementing methods in an informal protocol. */ - (void) setDelegate: (id)anObj { Idelegate = anObj; IauthenticateIn = [anObj respondsToSelector: @selector(authenticateComponents:withData:)]; IauthenticateOut = [anObj respondsToSelector: @selector(authenticationDataForComponents:)]; } /** * Sets whether or not the NSConnection should handle requests * arriving from the remote NSConnection atomically.
* By default, this is set to NO ... if set to YES then any messages * arriving while one message is being dealt with, will be queued.
* NB. careful - use of this option can cause deadlocks. */ - (void) setIndependentConversationQueueing: (BOOL)flag { IindependentQueueing = flag; } /** * Sets the time interval that the NSConnection will wait for a * reply for one of its requests before raising an * NSPortTimeoutException.
* NB. In GNUstep you may also get such an exception if the connection * becomes invalidated while waiting for a reply to a request. */ - (void) setReplyTimeout: (NSTimeInterval)to { if (to <= 0.0 || to > 1.0E12) to = 1.0E12; IreplyTimeout = to; } /** * Sets the runloop mode in which requests will be sent to the remote * end of the connection. Normally this is NSDefaultRunloopMode */ - (void) setRequestMode: (NSString*)mode { GS_M_LOCK(IrefGate); if (IrequestModes != nil) { while ([IrequestModes count] > 0 && [IrequestModes objectAtIndex: 0] != mode) { [self removeRequestMode: [IrequestModes objectAtIndex: 0]]; } while ([IrequestModes count] > 1) { [self removeRequestMode: [IrequestModes objectAtIndex: 1]]; } if (mode != nil && [IrequestModes count] == 0) { [self addRequestMode: mode]; } } GSM_UNLOCK(IrefGate); } /** * Sets the time interval that the NSConnection will wait to send * one of its requests before raising an NSPortTimeoutException. */ - (void) setRequestTimeout: (NSTimeInterval)to { if (to <= 0.0 || to > 1.0E12) to = 1.0E12; IrequestTimeout = to; } /** * Sets the root object that is vended by the connection. */ - (void) setRootObject: (id)anObj { setRootObjectForInPort(anObj, IreceivePort); #if defined(_WIN32) /* On ms-windows, the operating system does not inform us when the remote * client of a message port goes away ... so we need to enable keepalive * to detect that condition. */ if ([IreceivePort isKindOfClass: [NSMessagePort class]]) { [self _enableKeepalive]; } #endif } /** * Returns an object containing various statistics for the * NSConnection. *
* On GNUstep the dictionary contains - * * NSConnectionRepliesReceived * * The number of messages replied to by the remote NSConnection. * * NSConnectionRepliesSent * * The number of replies sent to the remote NSConnection. * * NSConnectionRequestsReceived * * The number of messages received from the remote NSConnection. * * NSConnectionRequestsSent * * The number of messages sent to the remote NSConnection. * * NSConnectionLocalCount * * The number of local objects currently vended. * * NSConnectionProxyCount * * The number of remote objects currently in use. * * */ - (NSDictionary*) statistics { NSMutableDictionary *d; id o; d = [NSMutableDictionary dictionaryWithCapacity: 8]; GS_M_LOCK(IrefGate); /* * These are in OPENSTEP 4.2 */ o = [NSNumber numberWithUnsignedInt: IrepInCount]; [d setObject: o forKey: NSConnectionRepliesReceived]; o = [NSNumber numberWithUnsignedInt: IrepOutCount]; [d setObject: o forKey: NSConnectionRepliesSent]; o = [NSNumber numberWithUnsignedInt: IreqInCount]; [d setObject: o forKey: NSConnectionRequestsReceived]; o = [NSNumber numberWithUnsignedInt: IreqOutCount]; [d setObject: o forKey: NSConnectionRequestsSent]; /* * These are GNUstep extras */ o = [NSNumber numberWithUnsignedInt: IlocalTargets ? IlocalTargets->nodeCount : 0]; [d setObject: o forKey: NSConnectionLocalCount]; o = [NSNumber numberWithUnsignedInt: IremoteProxies ? IremoteProxies->nodeCount : 0]; [d setObject: o forKey: NSConnectionProxyCount]; o = [NSNumber numberWithUnsignedInt: IreplyMap ? IreplyMap->nodeCount : 0]; [d setObject: o forKey: @"NSConnectionReplyQueue"]; o = [NSNumber numberWithUnsignedInt: [IrequestQueue count]]; [d setObject: o forKey: @"NSConnectionRequestQueue"]; GSM_UNLOCK(IrefGate); return d; } @end @implementation NSConnection (GNUstepExtensions) + (NSConnection*) newRegisteringAtName: (NSString*)name withRootObject: (id)anObject { NSConnection *conn; GSOnceMLog(@"This method is deprecated, use standard initialisation"); conn = [[self alloc] initWithReceivePort: [NSPort port] sendPort: nil]; [conn setRootObject: anObject]; if ([conn registerName: name] == NO) { DESTROY(conn); } return conn; } - (void) finalize { NSAutoreleasePool *arp = [NSAutoreleasePool new]; if (debug_connection) NSLog(@"finalising %@", self); [self invalidate]; /* Remove rootObject from root_object_map if this is last connection */ if (IreceivePort != nil && existingConnection(IreceivePort, nil) == nil) { setRootObjectForInPort(nil, IreceivePort); } /* Remove receive port from run loop. */ [self setRequestMode: nil]; DESTROY(IrequestModes); DESTROY(IrunLoops); /* * Finished with ports - releasing them may generate a notification * If we are the receive port delagate, try to shift responsibility. */ if ([IreceivePort delegate] == self) { NSConnection *root = existingConnection(IreceivePort, IreceivePort); if (root == nil) { root = existingConnection(IreceivePort, nil); } [IreceivePort setDelegate: root]; } DESTROY(IreceivePort); DESTROY(IsendPort); DESTROY(IrequestQueue); if (IreplyMap != 0) { GSIMapEnumerator_t enumerator; GSIMapNode node; enumerator = GSIMapEnumeratorForMap(IreplyMap); node = GSIMapEnumeratorNextNode(&enumerator); while (node != 0) { if (node->value.obj != dummyObject) { RELEASE(node->value.obj); } node = GSIMapEnumeratorNextNode(&enumerator); } GSIMapEmptyMap(IreplyMap); NSZoneFree(IreplyMap->zone, (void*)IreplyMap); IreplyMap = 0; } DESTROY(IcachedDecoders); DESTROY(IcachedEncoders); DESTROY(IremoteName); DESTROY(IrefGate); [arp drain]; } /* * NSDistantObject's -forwardInvocation: method calls this to send the message * over the wire. */ - (void) forwardInvocation: (NSInvocation*)inv forProxy: (NSDistantObject*)object { NSPortCoder *op; BOOL outParams; BOOL needsResponse; const char *name; const char *type; unsigned seq; NSRunLoop *runLoop = GSRunLoopForThread(nil); if ([IrunLoops indexOfObjectIdenticalTo: runLoop] == NSNotFound) { if (ImultipleThreads == NO) { [NSException raise: NSObjectInaccessibleException format: @"Forwarding message for %p in wrong thread - %@", object, inv]; } else { [self addRunLoop: runLoop]; } } /* Encode the method on an RMC, and send it. */ NSParameterAssert (IisValid); /* get the method types from the selector */ type = [[inv methodSignature] methodType]; if (type == 0 || *type == '\0') { type = [[object methodSignatureForSelector: [inv selector]] methodType]; if (type) { GSSelectorFromNameAndTypes(sel_getName([inv selector]), type); } } NSParameterAssert(type); NSParameterAssert(*type); op = [self _newOutRmc: 0 generate: (int*)&seq reply: YES]; if (debug_connection > 4) NSLog(@"building packet seq %d", seq); [inv setTarget: object]; outParams = [inv encodeWithDistantCoder: op passPointers: NO]; if (outParams == YES) { needsResponse = YES; } else { int flags; needsResponse = NO; flags = objc_get_type_qualifiers(type); if ((flags & _F_ONEWAY) == 0) { needsResponse = YES; } else { const char *tmptype = objc_skip_type_qualifiers(type); if (*tmptype != _C_VOID) { needsResponse = YES; } } } [self _sendOutRmc: op type: METHOD_REQUEST sequence: seq]; name = sel_getName([inv selector]); NSDebugMLLog(@"RMC", @"Sent message %s RMC %d to %p", name, seq, self); if (needsResponse == NO) { GSIMapNode node; /* * Since we don't need a response, we can remove the placeholder from * the IreplyMap. However, in case the other end has already sent us * a response, we must check for it and scrap it if necessary. */ GS_M_LOCK(IrefGate); node = GSIMapNodeForKey(IreplyMap, (GSIMapKey)(NSUInteger)seq); if (node != 0 && node->value.obj != dummyObject) { BOOL is_exception = NO; [node->value.obj decodeValueOfObjCType: @encode(BOOL) at: &is_exception]; if (is_exception == YES) { /* Decode the exception object, and log it. */ id exc = [node->value.obj decodeObject]; NSLog(@"%@ got exception from oneway method %s - %@", self, name, exc); } else { NSLog(@"%@ got response from oneway method %s", self, name); } [self _doneInRmc: node->value.obj]; } GSIMapRemoveKey(IreplyMap, (GSIMapKey)(NSUInteger)seq); GSM_UNLOCK(IrefGate); } else { int argnum; int flags; const char *tmptype; void *datum; NSPortCoder *aRmc; BOOL is_exception; if ([self isValid] == NO) { [NSException raise: NSGenericException format: @"connection waiting for request was shut down"]; } aRmc = [self _getReplyRmc: seq for: name]; /* * Find out if the server is returning an exception instead * of the return values. */ [aRmc decodeValueOfObjCType: @encode(BOOL) at: &is_exception]; if (is_exception == YES) { /* Decode the exception object, and raise it. */ id exc = [aRmc decodeObject]; [self _doneInReply: aRmc]; [exc raise]; } /* Get the return type qualifier flags, and the return type. */ flags = objc_get_type_qualifiers(type); tmptype = objc_skip_type_qualifiers(type); /* Decode the return value and pass-by-reference values, if there are any. OUT_PARAMETERS should be the value returned by cifframe_dissect_call(). */ if (outParams || *tmptype != _C_VOID || (flags & _F_ONEWAY) == 0) /* xxx What happens with method declared "- (oneway) foo: (out int*)ip;" */ /* xxx What happens with method declared "- (in char *) bar;" */ /* xxx Is this right? Do we also have to check _F_ONEWAY? */ { id obj; /* If there is a return value, decode it, and put it in datum. */ if (*tmptype != _C_VOID || (flags & _F_ONEWAY) == 0) { switch (*tmptype) { case _C_ID: datum = &obj; [aRmc decodeValueOfObjCType: tmptype at: datum]; [obj autorelease]; break; case _C_PTR: /* We are returning a pointer to something. */ tmptype++; datum = alloca (objc_sizeof_type (tmptype)); [aRmc decodeValueOfObjCType: tmptype at: datum]; break; case _C_VOID: datum = alloca (sizeof (int)); [aRmc decodeValueOfObjCType: @encode(int) at: datum]; break; default: datum = alloca (objc_sizeof_type (tmptype)); [aRmc decodeValueOfObjCType: tmptype at: datum]; break; } } else { datum = 0; } [inv setReturnValue: datum]; /* Decode the values returned by reference. Note: this logic must match exactly the code in _service_forwardForProxy: */ if (outParams) { /* Step through all the arguments, finding the ones that were passed by reference. */ for (tmptype = skip_argspec (tmptype), argnum = 0; *tmptype != '\0'; tmptype = skip_argspec (tmptype), argnum++) { /* Get the type qualifiers, like IN, OUT, INOUT, ONEWAY. */ flags = objc_get_type_qualifiers(tmptype); /* Skip over the type qualifiers, so now TYPE is pointing directly at the char corresponding to the argument's type. */ tmptype = objc_skip_type_qualifiers(tmptype); if (*tmptype == _C_PTR && ((flags & _F_OUT) || !(flags & _F_IN))) { /* If the arg was byref, we obtain its address * and decode the data directly to it. */ tmptype++; [inv getArgument: &datum atIndex: argnum]; [aRmc decodeValueOfObjCType: tmptype at: datum]; if (*tmptype == _C_ID) { [*(id*)datum autorelease]; } } else if (*tmptype == _C_CHARPTR && ((flags & _F_OUT) || !(flags & _F_IN))) { [aRmc decodeValueOfObjCType: tmptype at: &datum]; [inv setArgument: datum atIndex: argnum]; } } } } [self _doneInReply: aRmc]; } } - (const char *) typeForSelector: (SEL)sel remoteTarget: (unsigned)target { id op, ip; char *type = 0; int seq_num; NSData *data; NSParameterAssert(IreceivePort); NSParameterAssert (IisValid); op = [self _newOutRmc: 0 generate: &seq_num reply: YES]; [op encodeValueOfObjCType: ":" at: &sel]; [op encodeValueOfObjCType: @encode(unsigned) at: &target]; [self _sendOutRmc: op type: METHODTYPE_REQUEST sequence: seq_num]; ip = [self _getReplyRmc: seq_num for: "methodtype"]; [ip decodeValueOfObjCType: @encode(char*) at: &type]; data = type ? [NSData dataWithBytes: type length: strlen(type)+1] : nil; [self _doneInRmc: ip]; return (const char*)[data bytes]; } /* Class-wide stats and collections. */ + (unsigned) connectionsCount { unsigned result; GS_M_LOCK(connection_table_gate); result = NSCountHashTable(connection_table); GSM_UNLOCK(connection_table_gate); return result; } + (unsigned) connectionsCountWithInPort: (NSPort*)aPort { unsigned count = 0; NSHashEnumerator enumerator; NSConnection *o; GS_M_LOCK(connection_table_gate); enumerator = NSEnumerateHashTable(connection_table); while ((o = (NSConnection*)NSNextHashEnumeratorItem(&enumerator)) != nil) { if ([aPort isEqual: [o receivePort]]) { count++; } } NSEndHashTableEnumeration(&enumerator); GSM_UNLOCK(connection_table_gate); return count; } @end @implementation NSConnection (Private) - (void) handlePortMessage: (NSPortMessage*)msg { NSPortCoder *rmc; int type = [msg msgid]; NSMutableArray *components = [msg _components]; NSPort *rp = [msg receivePort]; NSPort *sp = [msg sendPort]; NSConnection *conn; if (debug_connection > 4) { NSLog(@"handling packet of type %d (%@)", type, stringFromMsgType(type)); } conn = [connectionClass connectionWithReceivePort: rp sendPort: sp]; if (conn == nil) { NSLog(@"Received port message for unknown connection - %@", msg); NSLog(@"All connections: %@", [NSConnection allConnections]); return; } else if ([conn isValid] == NO) { if (debug_connection) { NSLog(@"received port message for invalid connection - %@", msg); } return; } if (debug_connection > 4) { NSLog(@" connection is %@", conn); } if (GSIVar(conn, _authenticateIn) == YES && (type == METHOD_REQUEST || type == METHOD_REPLY)) { NSData *d; NSUInteger count = [components count]; d = RETAIN([components objectAtIndex: --count]); [components removeObjectAtIndex: count]; if ([[conn delegate] authenticateComponents: components withData: d] == NO) { RELEASE(d); [NSException raise: NSFailedAuthenticationException format: @"message not authenticated by delegate"]; } RELEASE(d); } rmc = [conn _newInRmc: components]; if (debug_connection > 5) { NSLog(@"made rmc %p for %d", rmc, type); } switch (type) { case ROOTPROXY_REQUEST: /* It won't take much time to handle this, so go ahead and service it, even if we are waiting for a reply. */ [conn _service_rootObject: rmc]; break; case METHODTYPE_REQUEST: /* It won't take much time to handle this, so go ahead and service it, even if we are waiting for a reply. */ [conn _service_typeForSelector: rmc]; break; case METHOD_REQUEST: /* * We just got a new request; we need to decide whether to queue * it or service it now. * If the REPLY_DEPTH is 0, then we aren't in the middle of waiting * for a reply, we are waiting for requests---so service it now. * If REPLY_DEPTH is non-zero, we may still want to service it now * if independent_queuing is NO. */ GS_M_LOCK(GSIVar(conn, _refGate)); if (GSIVar(conn, _requestDepth) == 0 || GSIVar(conn, _independentQueueing) == NO) { GSIVar(conn, _requestDepth)++; GSM_UNLOCK(GSIVar(conn, _refGate)); [conn _service_forwardForProxy: rmc]; // Catches exceptions GS_M_LOCK(GSIVar(conn, _refGate)); GSIVar(conn, _requestDepth)--; } else { [GSIVar(conn, _requestQueue) addObject: rmc]; } /* * Service any requests that were queued while we * were waiting for replies. */ while (GSIVar(conn, _requestDepth) == 0 && [GSIVar(conn, _requestQueue) count] > 0) { rmc = [GSIVar(conn, _requestQueue) objectAtIndex: 0]; [GSIVar(conn, _requestQueue) removeObjectAtIndex: 0]; GSM_UNLOCK(GSIVar(conn, _refGate)); [conn _service_forwardForProxy: rmc]; // Catches exceptions GS_M_LOCK(GSIVar(conn, _refGate)); } GSM_UNLOCK(GSIVar(conn, _refGate)); break; /* * For replies, we read the sequence number from the reply object and * store it in a map using thee sequence number as the key. That way * it's easy for the connection to find replies by their numbers. */ case ROOTPROXY_REPLY: case METHOD_REPLY: case METHODTYPE_REPLY: case RETAIN_REPLY: { int sequence; GSIMapNode node; [rmc decodeValueOfObjCType: @encode(int) at: &sequence]; if (type == ROOTPROXY_REPLY && GSIVar(conn, _keepaliveWait) == YES && sequence == GSIVar(conn, _lastKeepalive)) { GSIVar(conn, _keepaliveWait) = NO; NSDebugMLLog(@"NSConnection", @"Handled keepalive %d on %@", sequence, conn); [self _doneInRmc: rmc]; break; } GS_M_LOCK(GSIVar(conn, _refGate)); node = GSIMapNodeForKey(GSIVar(conn, _replyMap), (GSIMapKey)(NSUInteger)sequence); if (node == 0) { NSDebugMLLog(@"NSConnection", @"Ignoring reply RMC %d on %@", sequence, conn); [self _doneInRmc: rmc]; } else if (node->value.obj == dummyObject) { NSDebugMLLog(@"NSConnection", @"Saving reply RMC %d on %@", sequence, conn); node->value.obj = rmc; } else { NSDebugMLLog(@"NSConnection", @"Replace reply RMC %d on %@", sequence, conn); [self _doneInRmc: node->value.obj]; node->value.obj = rmc; } GSM_UNLOCK(GSIVar(conn, _refGate)); } break; case CONNECTION_SHUTDOWN: { [conn _service_shutdown: rmc]; break; } case PROXY_RELEASE: { [conn _service_release: rmc]; break; } case PROXY_RETAIN: { [conn _service_retain: rmc]; break; } default: [NSException raise: NSGenericException format: @"unrecognized NSPortCoder identifier"]; } } - (void) _runInNewThread { NSRunLoop *loop = GSRunLoopForThread(nil); [self addRunLoop: loop]; [loop run]; } + (int) setDebug: (int)val { int old = debug_connection; debug_connection = val; return old; } - (void) _keepalive: (NSNotification*)n { if ([self isValid]) { if (IkeepaliveWait == NO) { NSPortCoder *op; /* Send out a root proxy request to ping the other end. */ op = [self _newOutRmc: 0 generate: &IlastKeepalive reply: NO]; IkeepaliveWait = YES; [self _sendOutRmc: op type: ROOTPROXY_REQUEST sequence: IlastKeepalive]; } else { /* keepalive timeout outstanding still. */ [self invalidate]; } } } /** */ - (void) _enableKeepalive { IuseKeepalive = YES; /* Set so that child connections will inherit. */ IkeepaliveWait = NO; if (IreceivePort !=IsendPort) { /* If this is not a listening connection, we actually enable the * keepalive timing (usng the regular housekeeping notifications) * and must also enable multiple thread support as the keepalive * notification may arrive in a different thread from the one we * are running in. */ [self enableMultipleThreads]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_keepalive:) name: @"GSHousekeeping" object: nil]; } } /* NSConnection calls this to service the incoming method request. */ - (void) _service_forwardForProxy: (NSPortCoder*)aRmc { char *forward_type = 0; NSPortCoder *decoder = nil; NSPortCoder *encoder = nil; NSInvocation *inv = nil; unsigned seq; /* Save this for later */ [aRmc decodeValueOfObjCType: @encode(int) at: &seq]; /* * Make sure don't let exceptions caused by servicing the client's * request cause us to crash. */ NS_DURING { NSRunLoop *runLoop = GSRunLoopForThread(nil); const char *type; const char *tmptype; const char *etmptype; id tmp; id target; SEL selector; BOOL is_exception = NO; BOOL is_async = NO; BOOL is_void = NO; unsigned flags; int argnum; unsigned in_parameters = 0; unsigned out_parameters = 0; NSMethodSignature *sig; const char *encoded_types; NSParameterAssert (IisValid); if ([IrunLoops indexOfObjectIdenticalTo: runLoop] == NSNotFound) { if (ImultipleThreads == YES) { [self addRunLoop: runLoop]; } else { [NSException raise: NSObjectInaccessibleException format: @"Message received in wrong thread"]; } } /* * Get the types that we're using, so that we know * exactly what qualifiers the forwarder used. * If all selectors included qualifiers and I could make * sel_types_match() work the way I wanted, we wouldn't need * to do this. */ [aRmc decodeValueOfObjCType: @encode(char*) at: &forward_type]; if (debug_connection > 1) { NSLog(@"Handling message (sig %s) RMC %d from %p", forward_type, seq, self); } IreqInCount++; /* Handling an incoming request. */ encoded_types = forward_type; etmptype = encoded_types; decoder = aRmc; /* Decode the object, (which is always the first argument to a method). * Use the -decodeObject method to ensure that the target of the * invocation is autoreleased and will be deallocated when we finish. */ target = [decoder decodeObject]; /* Decode the selector, (which is the second argument to a method). */ /* xxx @encode(SEL) produces "^v" in gcc 2.5.8. It should be ":" */ [decoder decodeValueOfObjCType: @encode(SEL) at: &selector]; /* Get the "selector type" for this method. The "selector type" is a string that lists the return and argument types, and also indicates in which registers and where on the stack the arguments should be placed before the method call. The selector type string we get here should have the same argument and return types as the ENCODED_TYPES string, but it will have different register and stack locations if the ENCODED_TYPES came from a machine of a different architecture. */ sig = [target methodSignatureForSelector: selector]; if (nil == sig) { [NSException raise: NSInvalidArgumentException format: @"decoded object %p doesn't handle %s", target, sel_getName(selector)]; } type = [sig methodType]; /* Make sure we successfully got the method type, and that its types match the ENCODED_TYPES. */ NSCParameterAssert (type); if (GSSelectorTypesMatch(encoded_types, type) == NO) { [NSException raise: NSInvalidArgumentException format: @"NSConection types (%s / %s) mismatch for %s", encoded_types, type, sel_getName(selector)]; } inv = [[NSInvocation alloc] initWithMethodSignature: sig]; tmptype = skip_argspec (type); etmptype = skip_argspec (etmptype); [inv setTarget: target]; tmptype = skip_argspec (tmptype); etmptype = skip_argspec (etmptype); [inv setSelector: selector]; /* Step TMPTYPE and ETMPTYPE in lock-step through their method type strings. */ for (tmptype = skip_argspec (tmptype), etmptype = skip_argspec (etmptype), argnum = 2; *tmptype != '\0'; tmptype = skip_argspec (tmptype), etmptype = skip_argspec (etmptype), argnum++) { void *datum; /* Get the type qualifiers, like IN, OUT, INOUT, ONEWAY. */ flags = objc_get_type_qualifiers (etmptype); /* Skip over the type qualifiers, so now TYPE is pointing directly at the char corresponding to the argument's type. */ tmptype = objc_skip_type_qualifiers(tmptype); /* Decide how, (or whether or not), to decode the argument depending on its FLAGS and TMPTYPE. Only the first two cases involve parameters that may potentially be passed by reference, and thus only the first two may change the value of OUT_PARAMETERS. *** Note: This logic must match exactly the code in cifframe_dissect_call(); that function should encode exactly what we decode here. *** */ switch (*tmptype) { case _C_CHARPTR: /* Handle a (char*) argument. */ /* If the char* is qualified as an OUT parameter, or if it not explicitly qualified as an IN parameter, then we will have to get this char* again after the method is run, because the method may have changed it. Set OUT_PARAMETERS accordingly. */ if ((flags & _F_OUT) || !(flags & _F_IN)) out_parameters++; else in_parameters++; /* If the char* is qualified as an IN parameter, or not explicity qualified as an OUT parameter, then decode it. Note: the decoder allocates memory for holding the string, and it is also responsible for making sure that the memory gets freed eventually, (usually through the autorelease of NSData object). */ if ((flags & _F_IN) || !(flags & _F_OUT)) { datum = alloca (sizeof(char*)); [decoder decodeValueOfObjCType: tmptype at: datum]; [inv setArgument: datum atIndex: argnum]; } break; case _C_PTR: /* If the pointer's value is qualified as an OUT parameter, or if it not explicitly qualified as an IN parameter, then we will have to get the value pointed to again after the method is run, because the method may have changed it. Set OUT_PARAMETERS accordingly. */ if ((flags & _F_OUT) || !(flags & _F_IN)) out_parameters++; else in_parameters++; /* Handle an argument that is a pointer to a non-char. But (void*) and (anything**) is not allowed. */ /* The argument is a pointer to something; increment TYPE so we can see what it is a pointer to. */ tmptype++; /* If the pointer's value is qualified as an IN parameter, or not explicity qualified as an OUT parameter, then decode it. */ if ((flags & _F_IN) || !(flags & _F_OUT)) { datum = alloca (objc_sizeof_type (tmptype)); [decoder decodeValueOfObjCType: tmptype at: datum]; [inv setArgument: &datum atIndex: argnum]; } break; default: in_parameters++; datum = alloca (objc_sizeof_type (tmptype)); if (*tmptype == _C_ID) { *(id*)datum = [decoder decodeObject]; } else { [decoder decodeValueOfObjCType: tmptype at: datum]; } [inv setArgument: datum atIndex: argnum]; } } /* Stop using the decoder. */ tmp = decoder; decoder = nil; [self _doneInRmc: tmp]; /* Get the qualifier type of the return value. */ flags = objc_get_type_qualifiers (encoded_types); /* Get the return type; store it our two temporary char*'s. */ etmptype = objc_skip_type_qualifiers (encoded_types); tmptype = objc_skip_type_qualifiers (type); if (_C_VOID == *tmptype) { is_void = YES; } /* If this is a oneway void with no out parameters, we don't need to * send back any response. */ if (YES == is_void && (flags & _F_ONEWAY) && !out_parameters) { is_async = YES; } NSDebugMLLog(@"RMC", @"RMC %d %s method '%s' on %p(%s)", seq, (YES == is_async) ? "async" : "invoke", selector ? sel_getName(selector) : "nil", target, target ? class_getName([target class]) : "nil"); /* Invoke the method! */ [inv invoke]; if (YES == is_async) { tmp = inv; inv = nil; [tmp release]; NS_VOIDRETURN; } /* It is possible that our connection died while the method was * being called - in this case we mustn't try to send the result * back to the remote application! */ if ([self isValid] == NO) { NSDebugMLLog(@"RMC", @"RMC %d invalidated ... no return", seq); tmp = inv; inv = nil; [tmp release]; NS_VOIDRETURN; } /* Encode the return value and pass-by-reference values, if there are any. This logic must match exactly that in cifframe_build_return(). */ /* OUT_PARAMETERS should be true here in exactly the same situations as it was true in cifframe_dissect_call(). */ /* We create a new coder object and encode a flag to * say that this is not an exception. */ encoder = [self _newOutRmc: seq generate: 0 reply: NO]; [encoder encodeValueOfObjCType: @encode(BOOL) at: &is_exception]; /* Only encode return values if there is a non-void return value, a non-oneway void return value, or if there are values that were passed by reference. */ if (YES == is_void) { if ((flags & _F_ONEWAY) == 0) { int dummy = 0; [encoder encodeValueOfObjCType: @encode(int) at: (void*)&dummy]; } /* No return value to encode; do nothing. */ } else { void *datum; if (*tmptype == _C_PTR) { /* The argument is a pointer to something; increment TYPE so we can see what it is a pointer to. */ tmptype++; datum = alloca (objc_sizeof_type (tmptype)); } else { datum = alloca (objc_sizeof_type (tmptype)); } [inv getReturnValue: datum]; [encoder encodeValueOfObjCType: tmptype at: datum]; } /* Encode the values returned by reference. Note: this logic must match exactly the code in cifframe_build_return(); that function should decode exactly what we encode here. */ if (out_parameters) { /* Step through all the arguments, finding the ones that were passed by reference. */ for (tmptype = skip_argspec (tmptype), argnum = 0, etmptype = skip_argspec (etmptype); *tmptype != '\0'; tmptype = skip_argspec (tmptype), argnum++, etmptype = skip_argspec (etmptype)) { /* Get the type qualifiers, like IN, OUT, INOUT, ONEWAY. */ flags = objc_get_type_qualifiers(etmptype); /* Skip over the type qualifiers, so now TYPE is pointing directly at the char corresponding to the argument's type. */ tmptype = objc_skip_type_qualifiers (tmptype); /* Decide how, (or whether or not), to encode the argument depending on its FLAGS and TMPTYPE. */ if (((flags & _F_OUT) || !(flags & _F_IN)) && (*tmptype == _C_PTR || *tmptype == _C_CHARPTR)) { void *datum; if (*tmptype == _C_PTR) { /* The argument is a pointer (to a non-char), and the pointer's value is qualified as an OUT parameter, or it not explicitly qualified as an IN parameter, then it is a pass-by-reference argument.*/ ++tmptype; [inv getArgument: &datum atIndex: argnum]; [encoder encodeValueOfObjCType: tmptype at: datum]; } else if (*tmptype == _C_CHARPTR) { datum = alloca (sizeof (char*)); [inv getArgument: datum atIndex: argnum]; [encoder encodeValueOfObjCType: tmptype at: datum]; } } } } tmp = inv; inv = nil; [tmp release]; tmp = encoder; encoder = nil; NSDebugMLLog(@"RMC", @"RMC %d replying with %s" @" and %u/%u in/out parameters", seq, (YES == is_void ? "void result" : "result"), in_parameters, out_parameters); [self _sendOutRmc: tmp type: METHOD_REPLY sequence: seq]; } NS_HANDLER { if (debug_connection > 3) NSLog(@"forwarding exception for (%@) - %@", self, localException); /* Send the exception back to the client. */ if (IisValid == YES) { BOOL is_exception = YES; NS_DURING { NSPortCoder *op; if (inv != nil) { [inv release]; } if (decoder != nil) { [self _failInRmc: decoder]; } if (encoder != nil) { [self _failOutRmc: encoder]; } op = [self _newOutRmc: seq generate: 0 reply: NO]; [op encodeValueOfObjCType: @encode(BOOL) at: &is_exception]; [op encodeBycopyObject: localException]; [self _sendOutRmc: op type: METHOD_REPLY sequence: seq]; } NS_HANDLER { NSLog(@"Exception when sending exception back to client - %@", localException); } NS_ENDHANDLER; } } NS_ENDHANDLER; } - (void) _service_rootObject: (NSPortCoder*)rmc { id rootObject = rootObjectForInPort(IreceivePort); int sequence; NSPortCoder *op; NSParameterAssert(IreceivePort); NSParameterAssert(IisValid); NSParameterAssert([rmc connection] == self); [rmc decodeValueOfObjCType: @encode(int) at: &sequence]; [self _doneInRmc: rmc]; op = [self _newOutRmc: sequence generate: 0 reply: NO]; [op encodeObject: rootObject]; [self _sendOutRmc: op type: ROOTPROXY_REPLY sequence: sequence]; } - (void) _service_release: (NSPortCoder*)rmc { unsigned int count; unsigned int pos; int sequence; NSParameterAssert (IisValid); [rmc decodeValueOfObjCType: @encode(int) at: &sequence]; [rmc decodeValueOfObjCType: @encode(__typeof__(count)) at: &count]; for (pos = 0; pos < count; pos++) { unsigned target; NSDistantObject *prox; [rmc decodeValueOfObjCType: @encode(__typeof__(target)) at: &target]; prox = [self includesLocalTarget: target]; if (prox != 0) { if (debug_connection > 3) NSLog(@"releasing object with target (0x%x) on (%@) counter %d", target, self, prox->_counter); GS_M_LOCK(IrefGate); NS_DURING { if (--(prox->_counter) == 0) { id rootObject = rootObjectForInPort(IreceivePort); if (rootObject == prox->_object) { /* Don't deallocate root object ... */ prox->_counter = 0; } else { [self removeLocalObject: (id)prox]; } } } NS_HANDLER { GSM_UNLOCK(IrefGate); [localException raise]; } NS_ENDHANDLER GSM_UNLOCK(IrefGate); } else if (debug_connection > 3) NSLog(@"releasing object with target (0x%x) on (%@) - nothing to do", target, self); } [self _doneInRmc: rmc]; } - (void) _service_retain: (NSPortCoder*)rmc { unsigned target; NSPortCoder *op; int sequence; NSDistantObject *local; NSString *response = nil; NSParameterAssert (IisValid); [rmc decodeValueOfObjCType: @encode(int) at: &sequence]; op = [self _newOutRmc: sequence generate: 0 reply: NO]; [rmc decodeValueOfObjCType: @encode(__typeof__(target)) at: &target]; [self _doneInRmc: rmc]; if (debug_connection > 3) NSLog(@"looking to retain local object with target (0x%x) on (%@)", target, self); GS_M_LOCK(IrefGate); local = [self locateLocalTarget: target]; if (local == nil) { response = @"target not found anywhere"; } else { local->_counter++; // Vended on connection. } GSM_UNLOCK(IrefGate); [op encodeObject: response]; [self _sendOutRmc: op type: RETAIN_REPLY sequence: sequence]; } - (void) _shutdown { NSParameterAssert(IreceivePort); NSParameterAssert (IisValid); NS_DURING { NSPortCoder *op; int sno; op = [self _newOutRmc: 0 generate: &sno reply: NO]; [self _sendOutRmc: op type: CONNECTION_SHUTDOWN sequence: sno]; } NS_HANDLER NS_ENDHANDLER } - (void) _service_shutdown: (NSPortCoder*)rmc { NSParameterAssert (IisValid); IshuttingDown = YES; // Prevent shutdown being sent back to other end [self _doneInRmc: rmc]; [self invalidate]; } - (void) _service_typeForSelector: (NSPortCoder*)rmc { NSPortCoder *op; unsigned target; NSDistantObject *p; int sequence; id o; SEL sel; const char *type; struct objc_method* m; NSParameterAssert(IreceivePort); NSParameterAssert (IisValid); [rmc decodeValueOfObjCType: @encode(int) at: &sequence]; op = [self _newOutRmc: sequence generate: 0 reply: NO]; [rmc decodeValueOfObjCType: ":" at: &sel]; [rmc decodeValueOfObjCType: @encode(unsigned) at: &target]; [self _doneInRmc: rmc]; p = [self includesLocalTarget: target]; o = (p != nil) ? p->_object : nil; /* xxx We should make sure that TARGET is a valid object. */ /* Not actually a Proxy, but we avoid the warnings "id" would have made. */ m = GSGetMethod(object_getClass(o), sel, YES, YES); /* Perhaps I need to be more careful in the line above to get the version of the method types that has the type qualifiers in it. Search the protocols list. */ if (m) type = method_getTypeEncoding(m); else type = ""; [op encodeValueOfObjCType: @encode(char*) at: &type]; [self _sendOutRmc: op type: METHODTYPE_REPLY sequence: sequence]; } /* * Check the queue, then try to get it from the network by waiting * while we run the NSRunLoop. Raise exception if we don't get anything * before timing out. */ - (NSPortCoder*) _getReplyRmc: (int)sn for: (const char*)request { NSPortCoder *rmc = nil; GSIMapNode node = 0; NSDate *timeout_date = nil; NSTimeInterval delay_interval = 0.0; NSTimeInterval last_interval; NSTimeInterval maximum_interval; NSDate *delay_date = nil; NSDate *start_date = nil; NSRunLoop *runLoop; BOOL isLocked = NO; if (IisValid == NO) { [NSException raise: NSObjectInaccessibleException format: @"Connection has been invalidated for reply %d (%s)", sn, request]; } /* * If we have sent out a request on a run loop that we don't already * know about, it must be on a new thread - so if we have multipleThreads * enabled, we must add the run loop of the new thread so that we can * get the reply in this thread. */ runLoop = GSRunLoopForThread(nil); if ([IrunLoops indexOfObjectIdenticalTo: runLoop] == NSNotFound) { if (ImultipleThreads == YES) { [self addRunLoop: runLoop]; } else { [NSException raise: NSObjectInaccessibleException format: @"Waiting for reply %d (%s) in wrong thread", sn, request]; } } if (ImultipleThreads == YES) { /* Since multiple threads are using this connection, another * thread may read the reply we are waiting for - so we must * break out of the runloop frequently to check. We do this * by setting a small delay and increasing it each time round * so that this semi-busy wait doesn't consume too much * processor time (I hope). * We set an upper limit on the delay to avoid responsiveness * problems. */ last_interval = 0.0001; maximum_interval = 1.0; } else { /* As the connection is single threaded, we can wait indefinitely * for a response ... but we recheck every five minutes anyway. */ last_interval = maximum_interval = 300.0; } NS_DURING { BOOL warned = NO; NSDebugMLLog(@"RMC", @"Waiting up to %g sec for reply RMC %d (%s) on %@", IreplyTimeout, sn, request, self); GS_M_LOCK(IrefGate); isLocked = YES; while (IisValid == YES && (node = GSIMapNodeForKey(IreplyMap, (GSIMapKey)(NSUInteger)sn)) != 0 && node->value.obj == dummyObject) { NSDate *limit_date; GSM_UNLOCK(IrefGate); isLocked = NO; if (start_date == nil) { start_date = [dateClass allocWithZone: NSDefaultMallocZone()]; start_date = [start_date init]; timeout_date = [dateClass allocWithZone: NSDefaultMallocZone()]; timeout_date = [timeout_date initWithTimeIntervalSinceNow: IreplyTimeout]; } RELEASE(delay_date); delay_date = [dateClass allocWithZone: NSDefaultMallocZone()]; if (delay_interval < maximum_interval) { NSTimeInterval next_interval = last_interval + delay_interval; last_interval = delay_interval; delay_interval = next_interval; } delay_date = [delay_date initWithTimeIntervalSinceNow: delay_interval]; /* * We must not set a delay date that is further in the future * than the timeout date for the response to be returned. */ if ([timeout_date earlierDate: delay_date] == timeout_date) { limit_date = timeout_date; } else { limit_date = delay_date; } /* * If the runloop returns without having done anything, AND we * were waiting for the final timeout, then we must break out * of the loop. */ if (([runLoop runMode: NSConnectionReplyMode beforeDate: limit_date] == NO && (limit_date == timeout_date)) || [timeout_date timeIntervalSinceNow] <= 0.0) { GS_M_LOCK(IrefGate); isLocked = YES; node = GSIMapNodeForKey(IreplyMap, (GSIMapKey)(NSUInteger)sn); break; } else if (warned == NO && [start_date timeIntervalSinceNow] <= -300.0) { warned = YES; NSLog(@"WARNING ... waiting for reply %u (%s) since %@ on %@", sn, request, start_date, self); } GS_M_LOCK(IrefGate); isLocked = YES; } if (node == 0) { rmc = nil; } else { rmc = node->value.obj; GSIMapRemoveKey(IreplyMap, (GSIMapKey)(NSUInteger)sn); } GSM_UNLOCK(IrefGate); isLocked = NO; TEST_RELEASE(start_date); TEST_RELEASE(delay_date); TEST_RELEASE(timeout_date); if (rmc == nil) { [NSException raise: NSInternalInconsistencyException format: @"no reply available %d (%s)", sn, request]; } if (rmc == dummyObject) { if (IisValid == YES) { [NSException raise: NSPortTimeoutException format: @"timed out waiting for reply %d (%s)", sn, request]; } else { [NSException raise: NSInvalidReceivePortException format: @"invalidated while awaiting reply %d (%s)", sn, request]; } } } NS_HANDLER { if (isLocked == YES) { GSM_UNLOCK(IrefGate); } [localException raise]; } NS_ENDHANDLER NSDebugMLLog(@"RMC", @"Consuming reply RMC %d (%s) on %p", sn, request, self); return rmc; } - (void) _doneInReply: (NSPortCoder*)c { [self _doneInRmc: c]; IrepInCount++; } - (void) _doneInRmc: (NSPortCoder*) NS_CONSUMED c { GS_M_LOCK(IrefGate); if (debug_connection > 5) { NSLog(@"done rmc %p", c); } if (cacheCoders == YES && IcachedDecoders != nil) { [IcachedDecoders addObject: c]; } [c dispatch]; /* Tell NSPortCoder to release the connection. */ RELEASE(c); GSM_UNLOCK(IrefGate); } /* * This method called if an exception occurred, and we don't know * whether we have already tidied the NSPortCoder object up or not. */ - (void) _failInRmc: (NSPortCoder*)c { GS_M_LOCK(IrefGate); if (cacheCoders == YES && IcachedDecoders != nil && [IcachedDecoders indexOfObjectIdenticalTo: c] == NSNotFound) { [IcachedDecoders addObject: c]; } if (debug_connection > 5) { NSLog(@"fail rmc %p", c); } [c dispatch]; /* Tell NSPortCoder to release the connection. */ RELEASE(c); GSM_UNLOCK(IrefGate); } /* * This method called if an exception occurred, and we don't know * whether we have already tidied the NSPortCoder object up or not. */ - (void) _failOutRmc: (NSPortCoder*)c { GS_M_LOCK(IrefGate); if (cacheCoders == YES && IcachedEncoders != nil && [IcachedEncoders indexOfObjectIdenticalTo: c] == NSNotFound) { [IcachedEncoders addObject: c]; } [c dispatch]; /* Tell NSPortCoder to release the connection. */ RELEASE(c); GSM_UNLOCK(IrefGate); } - (NSPortCoder*) _newInRmc: (NSMutableArray*)components { NSPortCoder *coder; NSUInteger count; NSParameterAssert(IisValid); GS_M_LOCK(IrefGate); if (cacheCoders == YES && IcachedDecoders != nil && (count = [IcachedDecoders count]) > 0) { coder = RETAIN([IcachedDecoders objectAtIndex: --count]); [IcachedDecoders removeObjectAtIndex: count]; } else { coder = [recvCoderClass allocWithZone: NSDefaultMallocZone()]; } GSM_UNLOCK(IrefGate); coder = [coder initWithReceivePort: IreceivePort sendPort: IsendPort components: components]; return coder; } /* * Create an NSPortCoder object for encoding an outgoing message or reply. * * sno Is the seqence number to encode into the coder. * ret If non-null, generate a new sequence number and return it * here. Ignore the sequence number passed in sno. * rep If this flag is YES, add a placeholder to the IreplyMap * so we handle an incoming reply for this sequence number. */ - (NSPortCoder*) _newOutRmc: (int)sno generate: (int*)ret reply: (BOOL)rep { NSPortCoder *coder; NSUInteger count; NSParameterAssert(IisValid); GS_M_LOCK(IrefGate); /* * Generate a new sequence number if required. */ if (ret != 0) { sno = ImessageCount++; *ret = sno; } /* * Add a placeholder to the reply map if we expect a reply. */ if (rep == YES) { GSIMapAddPair(IreplyMap, (GSIMapKey)(NSUInteger)sno, (GSIMapVal)dummyObject); } /* * Locate or create an rmc */ if (cacheCoders == YES && IcachedEncoders != nil && (count = [IcachedEncoders count]) > 0) { coder = RETAIN([IcachedEncoders objectAtIndex: --count]); [IcachedEncoders removeObjectAtIndex: count]; } else { coder = [sendCoderClass allocWithZone: NSDefaultMallocZone()]; } GSM_UNLOCK(IrefGate); coder = [coder initWithReceivePort: IreceivePort sendPort:IsendPort components: nil]; [coder encodeValueOfObjCType: @encode(int) at: &sno]; NSDebugMLLog(@"NSConnection", @"Make out RMC %u on %@", sno, self); return coder; } - (void) _sendOutRmc: (NSPortCoder*)c type: (int)msgid sequence: (int)sno { NSDate *limit; BOOL sent = NO; BOOL raiseException = NO; NSMutableArray *components = [c _components]; if (IauthenticateOut == YES && (msgid == METHOD_REQUEST || msgid == METHOD_REPLY)) { NSData *d; d = [[self delegate] authenticationDataForComponents: components]; if (d == nil) { RELEASE(c); [NSException raise: NSGenericException format: @"Bad authentication data provided by delegate"]; } [components addObject: d]; } switch (msgid) { case PROXY_RETAIN: case CONNECTION_SHUTDOWN: case METHOD_REPLY: case ROOTPROXY_REPLY: case METHODTYPE_REPLY: case PROXY_RELEASE: case RETAIN_REPLY: raiseException = NO; break; case METHOD_REQUEST: case ROOTPROXY_REQUEST: case METHODTYPE_REQUEST: default: raiseException = YES; break; } NSDebugMLLog(@"NSConnection", @"Sending %d (%@) on %@", sno, stringFromMsgType(msgid), self); limit = [dateClass dateWithTimeIntervalSinceNow: IrequestTimeout]; sent = [IsendPort sendBeforeDate: limit msgid: msgid components: components from: IreceivePort reserved: [IsendPort reservedSpaceLength]]; GS_M_LOCK(IrefGate); /* * We replace the coder we have just used in the cache, and tell it not to * retain this connection any more. */ if (cacheCoders == YES && IcachedEncoders != nil) { [IcachedEncoders addObject: c]; } [c dispatch]; /* Tell NSPortCoder to release the connection. */ RELEASE(c); GSM_UNLOCK(IrefGate); if (sent == NO) { NSString *text = stringFromMsgType(msgid); if ([IsendPort isValid] == NO) { text = [text stringByAppendingFormat: @" - port was invalidated"]; } if (raiseException == YES) { [NSException raise: NSPortTimeoutException format: @"%@", text]; } else { NSLog(@"Port operation timed out - %@", text); } } else { switch (msgid) { case METHOD_REQUEST: IreqOutCount++; /* Sent a request. */ break; case METHOD_REPLY: IrepOutCount++; /* Sent back a reply. */ break; default: break; } } } /* Managing objects and proxies. */ - (void) addLocalObject: (NSDistantObject*)anObj { static unsigned local_object_counter = 0; id object; unsigned target; GSIMapNode node; GS_M_LOCK(IrefGate); NSParameterAssert (IisValid); object = anObj->_object; target = anObj->_handle; /* * If there is no target allocated to the proxy, we add one. */ if (target == 0) { anObj->_handle = target = ++local_object_counter; } /* * Record the value in the IlocalObjects map, retaining it. */ node = GSIMapNodeForKey(IlocalObjects, (GSIMapKey)object); NSAssert(node == 0, NSInternalInconsistencyException); node = GSIMapNodeForKey(IlocalTargets, (GSIMapKey)(NSUInteger)target); NSAssert(node == 0, NSInternalInconsistencyException); IF_NO_ARC([anObj retain];) GSIMapAddPair(IlocalObjects, (GSIMapKey)object, (GSIMapVal)((id)anObj)); GSIMapAddPair(IlocalTargets, (GSIMapKey)(NSUInteger)target, (GSIMapVal)((id)anObj)); if (debug_connection > 2) NSLog(@"add local object (%p) target (0x%x) " @"to connection (%@)", object, target, self); GSM_UNLOCK(IrefGate); } - (NSDistantObject*) retainOrAddLocal: (NSDistantObject*)proxy forObject: (id)object { GSIMapNode node; NSDistantObject *p; /* Don't assert (IisValid); */ GS_M_LOCK(IrefGate); node = GSIMapNodeForKey(IlocalObjects, (GSIMapKey)object); if (node == 0) { p = nil; } else { p = RETAIN(node->value.obj); DESTROY(proxy); } if (p == nil && proxy != nil) { p = proxy; [self addLocalObject: p]; } GSM_UNLOCK(IrefGate); return p; } - (void) removeLocalObject: (NSDistantObject*)prox { id anObj; unsigned target; unsigned val = 0; GSIMapNode node; GS_M_LOCK(IrefGate); anObj = prox->_object; node = GSIMapNodeForKey(IlocalObjects, (GSIMapKey)anObj); /* * The NSDistantObject concerned may not belong to this connection, * so we need to check that any matching proxy is identical to the * argument we were given. */ if (node != 0 && node->value.obj == prox) { target = prox->_handle; /* * If this proxy has been vended onwards to another process * which has not myet released it, we need to keep a reference * to the local object around for a while in case that other * process needs it. */ if ((prox->_counter) != 0) { CachedLocalObject *item; (prox->_counter) = 0; GS_M_LOCK(cached_proxies_gate); if (timer == nil) { timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: connectionClass selector: @selector(_timeout:) userInfo: nil repeats: YES]; } item = [CachedLocalObject newWithObject: prox time: 5]; NSMapInsert(targetToCached, (void*)(uintptr_t)target, item); GSM_UNLOCK(cached_proxies_gate); RELEASE(item); if (debug_connection > 3) NSLog(@"placed local object (%p) target (0x%x) in cache", anObj, target); } /* * Remove the proxy from IlocalObjects and release it. */ GSIMapRemoveKey(IlocalObjects, (GSIMapKey)anObj); RELEASE(prox); /* * Remove the target info too - no release required. */ GSIMapRemoveKey(IlocalTargets, (GSIMapKey)(NSUInteger)target); if (debug_connection > 2) NSLog(@"removed local object (%p) target (0x%x) " @"from connection (%@) (ref %d)", anObj, target, self, val); } GSM_UNLOCK(IrefGate); } - (void) _release_target: (unsigned)target count: (unsigned)number { NS_DURING { /* * Tell the remote app that it can release its local objects * for the targets in the specified list since we don't have * proxies for them any more. */ if (IreceivePort != nil && IisValid == YES && number > 0) { id op; unsigned i; int sequence; op = [self _newOutRmc: 0 generate: &sequence reply: NO]; [op encodeValueOfObjCType: @encode(unsigned) at: &number]; for (i = 0; i < number; i++) { [op encodeValueOfObjCType: @encode(unsigned) at: &target]; if (debug_connection > 3) NSLog(@"sending release for target (0x%x) on (%@)", target, self); } [self _sendOutRmc: op type: PROXY_RELEASE sequence: sequence]; } } NS_HANDLER { if (debug_connection) NSLog(@"failed to release targets - %@", localException); } NS_ENDHANDLER } - (NSDistantObject*) locateLocalTarget: (unsigned)target { NSDistantObject *proxy = nil; GSIMapNode node; GS_M_LOCK(IrefGate); /* * Try a quick lookup to see if the target references a local object * belonging to the receiver ... usually it should. */ node = GSIMapNodeForKey(IlocalTargets, (GSIMapKey)(NSUInteger)target); if (node != 0) { proxy = node->value.obj; } /* * If the target doesn't exist in the receiver, but still * persists in the cache (ie it was recently released) then * we move it back from the cache to the receiver. */ if (proxy == nil) { CachedLocalObject *cached; GS_M_LOCK(cached_proxies_gate); cached = NSMapGet (targetToCached, (void*)(uintptr_t)target); if (cached != nil) { proxy = [cached obj]; /* * Found in cache ... add to this connection as the object * is no longer in use by any connection. */ ASSIGN(proxy->_connection, self); [self addLocalObject: proxy]; NSMapRemove(targetToCached, (void*)(uintptr_t)target); if (debug_connection > 3) NSLog(@"target (0x%x) moved from cache", target); } GSM_UNLOCK(cached_proxies_gate); } /* * If not found in the current connection or the cache of local references * of recently invalidated connections, try all other existing connections. */ if (proxy == nil) { NSHashEnumerator enumerator; NSConnection *c; GS_M_LOCK(connection_table_gate); enumerator = NSEnumerateHashTable(connection_table); while (proxy == nil && (c = (NSConnection*)NSNextHashEnumeratorItem(&enumerator)) != nil) { if (c != self && [c isValid] == YES) { GS_M_LOCK(GSIVar(c, _refGate)); node = GSIMapNodeForKey(GSIVar(c, _localTargets), (GSIMapKey)(NSUInteger)target); if (node != 0) { id local; unsigned nTarget; /* * We found the local object in use in another connection * so we create a new reference to the same object and * add it to our connection, adjusting the target of the * new reference to be the value we need. * * We don't want to just share the NSDistantObject with * the other connection, since we might want to keep * track of information on a per-connection basis in * order to handle connection shutdown cleanly. */ proxy = node->value.obj; local = RETAIN(proxy->_object); proxy = [NSDistantObject proxyWithLocal: local connection: self]; nTarget = proxy->_handle; GSIMapRemoveKey(IlocalTargets, (GSIMapKey)(NSUInteger)nTarget); proxy->_handle = target; GSIMapAddPair(IlocalTargets, (GSIMapKey)(NSUInteger)target, (GSIMapVal)((id)proxy)); } GSM_UNLOCK(GSIVar(c, _refGate)); } } NSEndHashTableEnumeration(&enumerator); GSM_UNLOCK(connection_table_gate); } GSM_UNLOCK(IrefGate); if (proxy == nil) { if (debug_connection > 3) NSLog(@"target (0x%x) not found anywhere", target); } return proxy; } - (void) vendLocal: (NSDistantObject*)aProxy { GS_M_LOCK(IrefGate); aProxy->_counter++; GSM_UNLOCK(IrefGate); } - (void) acquireProxyForTarget: (unsigned)target { NSDistantObject *found; GSIMapNode node; /* Don't assert (IisValid); */ GS_M_LOCK(IrefGate); node = GSIMapNodeForKey(IremoteProxies, (GSIMapKey)(NSUInteger)target); if (node == 0) { found = nil; } else { found = node->value.obj; } GSM_UNLOCK(IrefGate); if (found == nil) { NS_DURING { /* * Tell the remote app that it must retain the local object * for the target on this connection. */ if (IreceivePort && IisValid) { NSPortCoder *op; id ip; id result; int seq_num; op = [self _newOutRmc: 0 generate: &seq_num reply: YES]; [op encodeValueOfObjCType: @encode(__typeof__(target)) at: &target]; [self _sendOutRmc: op type: PROXY_RETAIN sequence: seq_num]; ip = [self _getReplyRmc: seq_num for: "retain"]; [ip decodeValueOfObjCType: @encode(id) at: &result]; [self _doneInRmc: ip]; if (result != nil) NSLog(@"failed to retain target - %@", result); else if (debug_connection > 3) NSLog(@"sending retain for target - %u", target); } } NS_HANDLER { NSLog(@"failed to retain target - %@", localException); } NS_ENDHANDLER } } - (id) retain { return [super retain]; } - (void) removeProxy: (NSDistantObject*)aProxy { GS_M_LOCK(IrefGate); if (IisValid == YES) { unsigned target; unsigned count = 1; GSIMapNode node; target = aProxy->_handle; node = GSIMapNodeForKey(IremoteProxies, (GSIMapKey)(NSUInteger)target); /* * Only remove if the proxy for the target is the same as the * supplied argument. */ if (node != 0 && node->value.obj == aProxy) { count = aProxy->_counter; GSIMapRemoveKey(IremoteProxies, (GSIMapKey)(NSUInteger)target); /* * Tell the remote application that we have removed our proxy and * it can release it's local object. */ [self _release_target: target count: count]; } } GSM_UNLOCK(IrefGate); } /** * Private method used only when a remote process/thread has sent us a * target which we are decoding into a proxy in this process/thread. *

The argument aProxy may be nil, in which case an existing proxy * matching aTarget is retrieved retained, and returned (this is done * when a proxy target is sent to us by a remote process). *

*

If aProxy is not nil, but a proxy with the same target already * exists, then aProxy is released and the existing proxy is returned * as in the case where aProxy was nil. *

*

If aProxy is not nil and there was no prior proxy with the same * target, aProxy is added to the receiver and returned. *

*/ - (NSDistantObject*) retainOrAddProxy: (NSDistantObject*)aProxy forTarget: (unsigned)aTarget { NSDistantObject *p; GSIMapNode node; /* Don't assert (IisValid); */ NSParameterAssert(aTarget > 0); NSParameterAssert(aProxy == nil || object_getClass(aProxy) == distantObjectClass); NSParameterAssert(aProxy == nil || [aProxy connectionForProxy] == self); NSParameterAssert(aProxy == nil || aTarget == aProxy->_handle); GS_M_LOCK(IrefGate); node = GSIMapNodeForKey(IremoteProxies, (GSIMapKey)(NSUInteger)aTarget); if (node == 0) { p = nil; } else { p = RETAIN(node->value.obj); DESTROY(aProxy); } if (p == nil && aProxy != nil) { p = aProxy; GSIMapAddPair(IremoteProxies, (GSIMapKey)(NSUInteger)aTarget, (GSIMapVal)((id)p)); } /* * Whether this is a new proxy or an existing proxy, this method is * only called for an object being vended by a remote process/thread. * We therefore need to increment the count of the number of times * the proxy has been vended. */ if (p != nil) { p->_counter++; } GSM_UNLOCK(IrefGate); return p; } - (id) includesLocalObject: (id)anObj { NSDistantObject *ret; GSIMapNode node; /* Don't assert (IisValid); */ GS_M_LOCK(IrefGate); node = GSIMapNodeForKey(IlocalObjects, (GSIMapKey)(NSUInteger)anObj); if (node == 0) { ret = nil; } else { ret = node->value.obj; } GSM_UNLOCK(IrefGate); return ret; } - (NSDistantObject*) includesLocalTarget: (unsigned)target { NSDistantObject *ret; GSIMapNode node; /* Don't assert (IisValid); */ GS_M_LOCK(IrefGate); node = GSIMapNodeForKey(IlocalTargets, (GSIMapKey)(NSUInteger)target); if (node == 0) { ret = nil; } else { ret = node->value.obj; } GSM_UNLOCK(IrefGate); return ret; } /* Prevent trying to encode the connection itself */ - (void) encodeWithCoder: (NSCoder*)anEncoder { [self shouldNotImplement: _cmd]; } - (id) initWithCoder: (NSCoder*)aDecoder; { [self shouldNotImplement: _cmd]; return self; } /* * We register this method for a notification when a port dies. * NB. It is possible that the death of a port could be notified * to us after we are invalidated - in which case we must ignore it. */ - (void) _portIsInvalid: (NSNotification*)notification { if (IisValid) { id port = [notification object]; if (debug_connection) { NSLog(@"Received port invalidation notification for " @"connection %@\n\t%@", self, port); } /* We shouldn't be getting any port invalidation notifications, except from our own ports; this is how we registered ourselves with the NSNotificationCenter in +newForInPort: outPort: ancestorConnection. */ NSParameterAssert (port == IreceivePort || port == IsendPort); [self invalidate]; } } /** * On thread exit, we need all connections to be removed from the runloop * of the thread or they will retain that and cause a memory leak. */ + (void) _threadWillExit: (NSNotification*)notification { NSRunLoop *runLoop = GSRunLoopForThread ([notification object]); if (runLoop != nil) { NSEnumerator *enumerator; NSConnection *c; GS_M_LOCK (connection_table_gate); enumerator = [NSAllHashTableObjects(connection_table) objectEnumerator]; GSM_UNLOCK (connection_table_gate); /* * We enumerate an array copy of the contents of the hash table * as we know we can do that safely outside the locked region. * The temporary array and the enumerator are autoreleased and * will be deallocated with the threads autorelease pool. */ while ((c = [enumerator nextObject]) != nil) { [c removeRunLoop: runLoop]; } } } @end