This is Connection.m in view mode; [Download] [Up]
/* Implementation of connection object for remote object messaging Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc. Written by: Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu> Date: July 1994 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 Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* To do: Integrate with NSRunLoop Pass exceptions back to client. Find bug with proxies of invalidated connections. Make it thread-safe. Support @"*" hostname. */ /* RMC == Remote Method Coder, or Remote Method Call. It's an instance of ConnectedEncoder or ConnectedDecoder. */ #include <gnustep/base/preface.h> #include <gnustep/base/Connection.h> #include <gnustep/base/Proxy.h> #include <gnustep/base/ConnectedCoder.h> #include <gnustep/base/TcpPort.h> #include <gnustep/base/Array.h> #include <gnustep/base/Dictionary.h> #include <gnustep/base/Queue.h> #include <gnustep/base/mframe.h> #include <gnustep/base/Notification.h> #include <gnustep/base/RunLoop.h> #include <gnustep/base/MallocAddress.h> #include <Foundation/NSString.h> #include <Foundation/NSDate.h> #include <Foundation/NSException.h> #include <assert.h> @interface Connection (GettingCoderInterface) - (void) _handleRmc: rmc; - (void) _handleQueuedRmcRequests; - _getReceivedReplyRmcWithSequenceNumber: (int)n; - newSendingRequestRmc; - newSendingReplyRmcWithSequenceNumber: (int)n; - (int) _newMsgNumber; @end @interface Connection (Private) - _superInit; @end #define proxiesHashGate refGate #define sequenceNumberGate refGate /* xxx Fix this! */ #define refGate nil static inline BOOL class_is_kind_of (Class self, Class aClassObject) { Class class; for (class = self; class!=Nil; class = class_get_super_class(class)) if (class==aClassObject) return YES; return NO; } static inline unsigned int hash_int (cache_ptr cache, const void *key) { return (unsigned)key & cache->mask; } static inline int compare_ints (const void *k1, const void *k2) { return !(k1 - k2); } static int type_get_number_of_arguments (const char *type) { int i = 0; while (*type) { type = objc_skip_argspec (type); i += 1; } return i - 1; } /* class defaults */ static id default_in_port_class; static id default_out_port_class; static id default_proxy_class; static id default_encoding_class; static id default_decoding_class; static int default_in_timeout; static int default_out_timeout; static int debug_connection = 0; /* Perhaps this should be a hashtable, keyed by remote port. But we may also need to include the local port---even though when receiving the local port is fixed, there may be more than one registered connection (with different in ports) in the application. */ /* We could write -hash and -isEqual implementations for Connection */ static Array *connection_array; static Lock *connection_array_gate; static Dictionary *root_object_dictionary; static Lock *root_object_dictionary_gate; static NSMapTable *in_port_2_ancestor; static NSMapTable *all_connections_local_targets = NULL; /* rmc handling */ static Queue *received_request_rmc_queue; static Lock *received_request_rmc_queue_gate; static Queue *received_reply_rmc_queue; static Lock *received_reply_rmc_queue_gate; static int messages_received_count; @implementation Connection + (void) initialize { connection_array = [[Array alloc] init]; connection_array_gate = [Lock new]; /* xxx When NSHashTable's are working, change this. */ all_connections_local_targets = NSCreateMapTable (NSNonOwnedPointerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); received_request_rmc_queue = [[Queue alloc] init]; received_request_rmc_queue_gate = [Lock new]; received_reply_rmc_queue = [[Queue alloc] init]; received_reply_rmc_queue_gate = [Lock new]; root_object_dictionary = [[Dictionary alloc] init]; root_object_dictionary_gate = [Lock new]; in_port_2_ancestor = NSCreateMapTable (NSNonOwnedPointerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); messages_received_count = 0; default_in_port_class = [TcpInPort class]; default_out_port_class = [TcpOutPort class]; default_proxy_class = [Proxy class]; default_encoding_class = [ConnectedEncoder class]; default_decoding_class = [ConnectedDecoder class]; default_in_timeout = CONNECTION_DEFAULT_TIMEOUT; default_out_timeout = CONNECTION_DEFAULT_TIMEOUT; } /* Getting and setting class variables */ + (void) setDefaultInPortClass: (Class)aClass { default_in_port_class = aClass; } + (Class) defaultInPortClass { return default_in_port_class; } + (void) setDefaultOutPortClass: (Class)aClass { default_out_port_class = aClass; } + (Class) defaultOutPortClass { return default_out_port_class; } + (void) setDefaultProxyClass: (Class)aClass { default_proxy_class = aClass; } + (Class) defaultProxyClass { return default_proxy_class; } + (void) setDefaultDecodingClass: (Class) aClass { default_decoding_class = aClass; } + (Class) default_decoding_class { return default_decoding_class; } + (int) defaultOutTimeout { return default_out_timeout; } + (void) setDefaultOutTimeout: (int)to { default_out_timeout = to; } + (int) defaultInTimeout { return default_in_timeout; } + (void) setDefaultInTimeout: (int)to { default_in_timeout = to; } /* Class-wide stats and collections. */ + (int) messagesReceived { return messages_received_count; } + (id <Collecting>) allConnections { return [connection_array copy]; } + (unsigned) connectionsCount { return [connection_array count]; } + (unsigned) connectionsCountWithInPort: (InPort*)aPort { unsigned count = 0; id o; [connection_array_gate lock]; FOR_ARRAY (connection_array, o) { if ([aPort isEqual: [o inPort]]) count++; } END_FOR_ARRAY (connection_array); [connection_array_gate unlock]; return count; } /* Creating and initializing connections. */ - init { id newPort = [default_in_port_class newForReceiving]; id newConn = [Connection newForInPort:newPort outPort:nil ancestorConnection:nil]; [self release]; return newConn; } + new { id newPort = [default_in_port_class newForReceiving]; id newConn = [Connection newForInPort:newPort outPort:nil ancestorConnection:nil]; return newConn; } + (Connection*) newWithRootObject: anObj; { id newPort; id newConn; newPort = [default_in_port_class newForReceiving]; newConn = [self newForInPort:newPort outPort:nil ancestorConnection:nil]; [self setRootObject:anObj forInPort:newPort]; return newConn; } /* I want this method name to clearly indicate that we're not connecting to a pre-existing registration name, we're registering a new name, and this method will fail if that name has already been registered. This is why I don't like "newWithRegisteredName:" --- it's unclear if we're connecting to another Connection that already registered with that name. */ + (Connection*) newRegisteringAtName: (id <String>)n withRootObject: anObj { id newPort; id newConn; newPort = [default_in_port_class newForReceivingFromRegisteredName: n]; newConn = [self newForInPort:newPort outPort:nil ancestorConnection:nil]; [self setRootObject:anObj forInPort:newPort]; return newConn; } + (Proxy*) rootProxyAtName: (id <String>)n { return [self rootProxyAtName: n onHost: @""]; } + (Proxy*) rootProxyAtName: (id <String>)n onHost: (id <String>)h { id p = [default_out_port_class newForSendingToRegisteredName: n onHost: h]; return [self rootProxyAtPort: p]; } + (Proxy*) rootProxyAtPort: (OutPort*)anOutPort { id newInPort = [default_in_port_class newForReceiving]; return [self rootProxyAtPort: anOutPort withInPort: newInPort]; } + (Proxy*) rootProxyAtPort: (OutPort*)anOutPort withInPort: (InPort*)anInPort { Connection *newConn = [self newForInPort:anInPort outPort:anOutPort ancestorConnection:nil]; Proxy *newRemote; newRemote = [newConn rootProxy]; return newRemote; } /* This is the designated initializer for Connection */ + (Connection*) newForInPort: (InPort*)ip outPort: (OutPort*)op ancestorConnection: ancestor { Connection *newConn; int i, count; id newConnInPort, newConnOutPort; NSParameterAssert (ip); [connection_array_gate lock]; /* Find previously existing connection if there */ /* xxx Clean this up */ count = [connection_array count]; for (i = 0; i < count; i++) { newConn = [connection_array objectAtIndex: i]; newConnInPort = [newConn inPort]; newConnOutPort = [newConn outPort]; if ([newConnInPort isEqual: ip] && [newConnOutPort isEqual: op]) { [connection_array_gate unlock]; return newConn; } } newConn = [[Connection alloc] _superInit]; if (debug_connection) fprintf(stderr, "Created new connection 0x%x\n\t%s\n\t%s\n", (unsigned)newConn, [[ip description] cStringNoCopy], [[op description] cStringNoCopy]); newConn->is_valid = 1; newConn->in_port = ip; [ip retain]; newConn->out_port = op; [op retain]; newConn->message_count = 0; /* 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. */ newConn->local_targets = NSCreateMapTable (NSNonOwnedPointerMapKeyCallBacks, NSObjectMapValueCallBacks, 0); /* This maps [proxy targetForProxy] to proxy. The proxy's are retained. */ newConn->remote_proxies = NSCreateMapTable (NSIntMapKeyCallBacks, NSObjectMapValueCallBacks, 0); newConn->incoming_xref_2_const_ptr = NSCreateMapTable (NSIntMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); newConn->outgoing_const_ptr_2_xref = NSCreateMapTable (NSIntMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); newConn->in_timeout = [self defaultInTimeout]; newConn->out_timeout = [self defaultOutTimeout]; newConn->encoding_class = default_encoding_class; /* xxx ANCESTOR argument is currently ignored; in the future it will be removed. */ /* xxx It this the correct behavior? */ if (!(ancestor = NSMapGet (in_port_2_ancestor, ip))) { NSMapInsert (in_port_2_ancestor, ip, newConn); [[RunLoop currentInstance] addPort: ip forMode: RunLoopDefaultMode]; [[RunLoop currentInstance] addPort: ip forMode: RunLoopConnectionReplyMode]; /* This will cause the connection with the registered name to receive the -invokeWithObject: from the IN_PORT. This ends up being the ancestor of future new Connections on this in port. */ /* xxx Could it happen that this connection was invalidated, but the others would still be OK? That would cause problems. No. I don't think that can happen. */ [ip setReceivedPacketInvocation: (id)[self class]]; } if (ancestor) { newConn->in_port_class = [ancestor inPortClass]; newConn->out_port_class = [ancestor outPortClass]; } else { newConn->in_port_class = default_in_port_class; newConn->out_port_class = default_out_port_class; } newConn->delay_dialog_interruptions = YES; newConn->reply_depth = 0; newConn->delegate = nil; /* Here ask the delegate for permission. */ /* delegate is responsible for freeing newConn if it returns something different. */ if ([[ancestor delegate] respondsTo:@selector(connection:didConnect:)]) newConn = [[ancestor delegate] connection:ancestor didConnect:newConn]; /* Register outselves for invalidation notification when the ports become invalid. */ [NotificationDispatcher addObserver: newConn selector: @selector(portIsInvalid:) name: PortBecameInvalidNotification object: ip]; if (op) [NotificationDispatcher addObserver: newConn selector: @selector(portIsInvalid:) name: PortBecameInvalidNotification object: op]; /* if OP is nil, making this notification request would have registered us to receive all PortBecameInvalidNotification requests, independent of which port posted them. This isn't what we want. */ /* xxx This is weird, though. When will newConn ever get dealloc'ed? connectionArray will retain it, but connectionArray will never get deallocated. This sort of retain/release cirularity must be common enough. Think about this and fix it. */ [connection_array addObject: newConn]; [connection_array_gate unlock]; [NotificationDispatcher postNotificationName: ConnectionWasCreatedNotification object: newConn]; return newConn; } - _superInit { [super init]; return self; } /* Creating new rmc's for encoding requests and replies */ /* Create a new, empty rmc, which will be filled with a request. */ - newSendingRequestRmc { id rmc; NSParameterAssert(in_port); NSParameterAssert (is_valid); rmc = [[self encodingClass] newForWritingWithConnection: self sequenceNumber: [self _newMsgNumber] identifier: METHOD_REQUEST]; return rmc; } /* Create a new, empty rmc, which will be filled with a reply to msg #n. */ - newSendingReplyRmcWithSequenceNumber: (int)n { id rmc = [[self encodingClass] newForWritingWithConnection: self sequenceNumber: n identifier: METHOD_REPLY]; NSParameterAssert (is_valid); return rmc; } /* Methods for handling client and server, requests and replies */ /* Proxy's -forward:: method calls this to the the message over the wire. */ - (retval_t) forwardForProxy: (Proxy*)object selector: (SEL)sel argFrame: (arglist_t)argframe { ConnectedEncoder *op; /* The callback for encoding the args of the method call. */ void encoder (int argnum, void *datum, const char *type, int flags) { #define ENCODED_ARGNAME @"argument value" switch (*type) { case _C_ID: if (flags & _F_BYCOPY) [op encodeBycopyObject: *(id*)datum withName: ENCODED_ARGNAME]; else [op encodeObject: *(id*)datum withName: ENCODED_ARGNAME]; break; default: [op encodeValueOfObjCType: type at: datum withName: ENCODED_ARGNAME]; } } /* Encode the method on an RMC, and send it. */ { BOOL out_parameters; const char *type; retval_t retframe; int seq_num; NSParameterAssert (is_valid); op = [self newSendingRequestRmc]; seq_num = [op sequenceNumber]; /* get the method types from the selector */ #if NeXT_runtime [NSException raise: NSGenericException format: @"Sorry, distributed objects does not work with NeXT runtime"]; /* type = [object selectorTypeForProxy:sel]; */ #else type = sel_get_type(sel); #endif NSParameterAssert(type); NSParameterAssert(*type); /* Send the types that we're using, so that the performer knows exactly what qualifiers we're using. If all selectors included qualifiers, and if I could make sel_types_match() work the way I wanted, we wouldn't need to do this. */ [op encodeValueOfCType: @encode(char*) at: &type withName: @"selector type"]; /* xxx This doesn't work with proxies and the NeXT runtime because type may be a method_type from a remote machine with a different architecture, and its argframe layout specifiers won't be right for this machine! */ out_parameters = mframe_dissect_call (argframe, type, encoder); /* Send the rmc */ [op dismiss]; /* Get the reply rmc, and decode it. */ { ConnectedDecoder *ip = nil; int last_argnum; BOOL is_exception; void decoder(int argnum, void *datum, const char *type, int flags) { NSParameterAssert(ip != (id)-1); /* If we didn't get the reply packet yet, get it now. */ if (!ip) { /* xxx Why do we get the reply packet in here, and not just before calling dissect_method_return() below? */ ip = [self _getReceivedReplyRmcWithSequenceNumber:seq_num]; /* Find out if the server is returning an exception instead of the return values. */ [ip decodeValueOfCType: @encode(BOOL) at: &is_exception withName: NULL]; if (is_exception) { /* Decode the exception object, and raise it. */ id exc; [ip decodeObjectAt: &exc withName: NULL]; [ip dismiss]; /* xxx Is there anything else to clean up in dissect_method_return()? */ [exc raise]; } } [ip decodeValueOfObjCType: type at: datum withName: NULL]; /* -decodeValueOfCType:at:withName: malloc's new memory for char*'s. We need to make sure it gets freed eventually so we don't have a memory leak. Request here that it be autorelease'ed. */ if (*type == _C_CHARPTR) [MallocAddress autoreleaseMallocAddress: *(char**)datum]; if (argnum == last_argnum) { /* this must be here to avoid trashing alloca'ed retframe */ [ip dismiss]; ip = (id)-1; } } last_argnum = type_get_number_of_arguments(type) - 1; retframe = mframe_build_return (argframe, type, out_parameters, decoder); return retframe; } } } /* Connection calls this to service the incoming method request. */ - (void) _service_forwardForProxy: aRmc { char *forward_type; id op = nil; int reply_sequence_number; int numargs; void decoder (int argnum, void *datum, const char *type) { [aRmc decodeValueOfObjCType:type at:datum withName:NULL]; /* -decodeValueOfCType:at:withName: malloc's new memory for char*'s. We need to make sure it gets freed eventually so we don't have a memory leak. Request here that it be autorelease'ed. */ if (*type == _C_CHARPTR) [MallocAddress autoreleaseMallocAddress: *(char**)datum]; /* We need this "dismiss" to happen here and not later so that Coder "-awake..." methods will get sent before the __builtin_apply! */ if (argnum == numargs-1) [aRmc dismiss]; } void encoder (int argnum, void *datum, const char *type, int flags) { #define ENCODED_RETNAME @"return value" if (op == nil) { BOOL is_exception = NO; op = [self newSendingReplyRmcWithSequenceNumber: reply_sequence_number]; [op encodeValueOfCType: @encode(BOOL) at: &is_exception withName: @"Exceptional reply flag"]; } switch (*type) { case _C_ID: if (flags & _F_BYCOPY) [op encodeBycopyObject:*(id*)datum withName:ENCODED_RETNAME]; else [op encodeObject:*(id*)datum withName:ENCODED_RETNAME]; break; default: [op encodeValueOfObjCType:type at:datum withName:ENCODED_RETNAME]; } } /* Make sure don't let exceptions caused by servicing the client's request cause us to crash. */ NS_DURING { NSParameterAssert (is_valid); /* Save this for later */ reply_sequence_number = [aRmc sequenceNumber]; /* 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 decodeValueOfCType:@encode(char*) at:&forward_type withName:NULL]; numargs = type_get_number_of_arguments(forward_type); mframe_do_call (forward_type, decoder, encoder); [op dismiss]; } /* Make sure we pass all exceptions back to the requestor. */ NS_HANDLER { BOOL is_exception = YES; /* Try to clean up a little. */ if (op) [op release]; /* Send the exception back to the client. */ op = [self newSendingReplyRmcWithSequenceNumber: reply_sequence_number]; [op encodeValueOfCType: @encode(BOOL) at: &is_exception withName: @"Exceptional reply flag"]; [op encodeBycopyObject: localException withName: @"Exception object"]; [op dismiss]; } NS_ENDHANDLER; if (forward_type) (*objc_free) (forward_type); } - (Proxy*) rootProxy { id op, ip; Proxy *newProxy; int seq_num = [self _newMsgNumber]; NSParameterAssert(in_port); NSParameterAssert (is_valid); op = [[self encodingClass] newForWritingWithConnection: self sequenceNumber: seq_num identifier: ROOTPROXY_REQUEST]; [op dismiss]; ip = [self _getReceivedReplyRmcWithSequenceNumber: seq_num]; [ip decodeObjectAt: &newProxy withName: NULL]; NSParameterAssert (class_is_kind_of (newProxy->isa, objc_get_class ("Proxy"))); [ip dismiss]; return newProxy; } - (void) _service_rootObject: rmc { id rootObject = [Connection rootObjectForInPort:in_port]; ConnectedEncoder* op = [[self encodingClass] newForWritingWithConnection: [rmc connection] sequenceNumber: [rmc sequenceNumber] identifier: ROOTPROXY_REPLY]; NSParameterAssert (in_port); NSParameterAssert (is_valid); /* Perhaps we should turn this into a class method. */ NSParameterAssert([rmc connection] == self); [op encodeObject: rootObject withName: @"root object"]; [op dismiss]; } - (void) shutdown { id op; NSParameterAssert(in_port); NSParameterAssert (is_valid); op = [[self encodingClass] newForWritingWithConnection: self sequenceNumber: [self _newMsgNumber] identifier: CONNECTION_SHUTDOWN]; [op dismiss]; } - (void) _service_shutdown: rmc forConnection: receiving_connection { NSParameterAssert (is_valid); [self invalidate]; if (receiving_connection == self) [NSException raise: NSGenericException format: @"connection waiting for request was shut down"]; [self dealloc]; // xxx release instead? YES!! [rmc dismiss]; } - (const char *) typeForSelector: (SEL)sel remoteTarget: (unsigned)target { id op, ip; char *type; int seq_num; NSParameterAssert(in_port); NSParameterAssert (is_valid); seq_num = [self _newMsgNumber]; op = [[self encodingClass] newForWritingWithConnection: self sequenceNumber: seq_num identifier: METHODTYPE_REQUEST]; [op encodeValueOfObjCType:":" at:&sel withName:NULL]; [op encodeValueOfCType:@encode(unsigned) at:&target withName:NULL]; [op dismiss]; ip = [self _getReceivedReplyRmcWithSequenceNumber:seq_num]; [ip decodeValueOfCType:@encode(char*) at:&type withName:NULL]; [ip dismiss]; return type; } - (void) _service_typeForSelector: rmc { ConnectedEncoder* op; unsigned target; SEL sel; const char *type; struct objc_method* m; NSParameterAssert(in_port); NSParameterAssert (is_valid); NSParameterAssert([rmc connection] == self); op = [[self encodingClass] newForWritingWithConnection: [rmc connection] sequenceNumber: [rmc sequenceNumber] identifier: METHODTYPE_REPLY]; [rmc decodeValueOfObjCType:":" at:&sel withName:NULL]; [rmc decodeValueOfCType:@encode(unsigned) at:&target withName:NULL]; /* 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 = class_get_instance_method(((Proxy*)target)->isa, sel); /* 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 = m->method_types; else type = ""; [op encodeValueOfCType:@encode(char*) at:&type withName:@"Requested Method Type for Target"]; [op dismiss]; } /* Running the connection, getting/sending requests/replies. */ - (void) runConnectionUntilDate: date { [RunLoop runUntilDate: date]; } - (void) runConnection { [self runConnectionUntilDate: [NSDate distantFuture]]; } - (void) _handleRmc: rmc { switch ([rmc identifier]) { 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. */ [[rmc connection] _service_rootObject: rmc]; [rmc dismiss]; 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. */ [[rmc connection] _service_typeForSelector: rmc]; [rmc dismiss]; 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 it is a request made as a callback from our peer---the request is part of the remote code necessary to finish calculating our reply; we know it's a callback from our peer if the [RMC CONNECTION] is self. If REPLY_DEPTH is non-zero, and the [RMC CONNECTION] is not self, then we may still want to service it now if DELAY_DIALOG_INTERRUPTIONS is false. */ if (reply_depth == 0 || [rmc connection] == self || !delay_dialog_interruptions) { [[rmc connection] _service_forwardForProxy: rmc]; /* Service any requests that were queued while we were waiting for replies. xxx Is this the right place for this check? */ if (reply_depth == 0) [self _handleQueuedRmcRequests]; } else { [received_request_rmc_queue_gate lock]; [received_request_rmc_queue enqueueObject: rmc]; [received_request_rmc_queue_gate unlock]; } break; case ROOTPROXY_REPLY: case METHOD_REPLY: case METHODTYPE_REPLY: /* Remember multi-threaded callbacks will have to be handled specially */ [received_reply_rmc_queue_gate lock]; [received_reply_rmc_queue enqueueObject: rmc]; [received_reply_rmc_queue_gate unlock]; break; case CONNECTION_SHUTDOWN: { [[rmc connection] _service_shutdown: rmc forConnection: self]; break; } default: [NSException raise: NSGenericException format: @"unrecognized ConnectedDecoder identifier"]; } } - (void) _handleQueuedRmcRequests { id rmc; [received_request_rmc_queue_gate lock]; while ((rmc = [received_request_rmc_queue dequeueObject])) { [received_request_rmc_queue_gate unlock]; [self _handleRmc: rmc]; [received_request_rmc_queue_gate lock]; } [received_request_rmc_queue_gate unlock]; } /* Deal with an RMC, either by queuing it for later service, or by servicing it right away. This method is called by the in_port's received-packet-invocation. */ /* Look for it on the queue, if it is not there, return nil. */ - _getReceivedReplyRmcFromQueueWithSequenceNumber: (int)sn { id the_rmc = nil; unsigned count, i; [received_reply_rmc_queue_gate lock]; count = [received_reply_rmc_queue count]; /* xxx There should be a per-thread queue of rmcs so we can do callbacks when multi-threaded. */ for (i = 0; i < count; i++) { id a_rmc = [received_reply_rmc_queue objectAtIndex: i]; if ([a_rmc connection] == self && [a_rmc sequenceNumber] == sn) { if (debug_connection) printf("Getting received reply from queue\n"); [received_reply_rmc_queue removeObjectAtIndex: i]; the_rmc = a_rmc; break; } /* xxx Make sure that there isn't a higher sequenceNumber, meaning that we somehow dropped a packet. */ } [received_reply_rmc_queue_gate unlock]; return the_rmc; } /* Check the queue, then try to get it from the network by waiting while we run the RunLoop. Return nil if we don't get anything before timing out. */ - _getReceivedReplyRmcWithSequenceNumber: (int)sn { id rmc; id timeout_date = nil; reply_depth++; while (!(rmc = [self _getReceivedReplyRmcFromQueueWithSequenceNumber: sn])) { if (!timeout_date) timeout_date = [[NSDate alloc] initWithTimeIntervalSinceNow: in_timeout]; [RunLoop runOnceBeforeDate: timeout_date forMode: RunLoopConnectionReplyMode]; } reply_depth--; return rmc; } /* Sneaky, sneaky. See "sneaky" comment in TcpPort.m. This method is called by InPort when it receives a new packet. */ + (void) invokeWithObject: packet { id rmc = [ConnectedDecoder newDecodingWithPacket: packet connection: NSMapGet (in_port_2_ancestor, [packet receivingInPort])]; [[rmc connection] _handleRmc: rmc]; } - (int) _newMsgNumber { int n; NSParameterAssert (is_valid); [sequenceNumberGate lock]; n = message_count++; [sequenceNumberGate unlock]; return n; } /* Managing objects and proxies. */ - (void) addLocalObject: anObj { NSParameterAssert (is_valid); [proxiesHashGate lock]; /* xxx Do we need to check to make sure it's not already there? */ /* This retains anObj. */ NSMapInsert (local_targets, (void*)anObj, anObj); /* This does not retain anObj. */ NSMapInsert (all_connections_local_targets, (void*)anObj, anObj); [proxiesHashGate unlock]; } /* This should get called whenever an object free's itself */ + (void) removeLocalObject: anObj { id c; int i, count = [connection_array count]; /* Don't assert (is_valid); */ for (i = 0; i < count; i++) { c = [connection_array objectAtIndex:i]; [c removeLocalObject: anObj]; [c removeProxy: anObj]; } } - (void) removeLocalObject: anObj { /* Don't assert (is_valid); */ [proxiesHashGate lock]; /* This also releases anObj */ NSMapRemove (local_targets, (void*)anObj); NSMapRemove (all_connections_local_targets, (void*)anObj); [proxiesHashGate unlock]; } - (void) removeProxy: (Proxy*)aProxy { unsigned target = [aProxy targetForProxy]; /* Don't assert (is_valid); */ [proxiesHashGate lock]; /* This also releases aProxy */ NSMapRemove (remote_proxies, (void*)target); [proxiesHashGate unlock]; } - (id <Collecting>) localObjects { id c; /* Don't assert (is_valid); */ [proxiesHashGate lock]; c = NSAllMapTableValues (local_targets); [proxiesHashGate unlock]; return c; } - (id <Collecting>) proxies { id c; /* Don't assert (is_valid); */ [proxiesHashGate lock]; c = NSAllMapTableValues (remote_proxies); [proxiesHashGate unlock]; return c; } - (Proxy*) proxyForTarget: (unsigned)target { Proxy *p; /* Don't assert (is_valid); */ [proxiesHashGate lock]; p = NSMapGet (remote_proxies, (void*)target); [proxiesHashGate unlock]; NSParameterAssert(!p || [p connectionForProxy] == self); return p; } - (void) addProxy: (Proxy*) aProxy { unsigned target = [aProxy targetForProxy]; NSParameterAssert (is_valid); NSParameterAssert(aProxy->isa == [Proxy class]); NSParameterAssert([aProxy connectionForProxy] == self); [proxiesHashGate lock]; if (NSMapGet (remote_proxies, (void*)target)) [NSException raise: NSGenericException format: @"Trying to add the same proxy twice"]; NSMapInsert (remote_proxies, (void*)target, aProxy); [proxiesHashGate unlock]; } - (BOOL) includesProxyForTarget: (unsigned)target { BOOL ret; /* Don't assert (is_valid); */ [proxiesHashGate lock]; ret = NSMapGet (remote_proxies, (void*)target) ? YES : NO; [proxiesHashGate unlock]; return ret; } - (BOOL) includesLocalObject: anObj { BOOL ret; /* Don't assert (is_valid); */ [proxiesHashGate lock]; ret = NSMapGet (local_targets, (void*)anObj) ? YES : NO; [proxiesHashGate unlock]; return ret; } /* Check all connections. Proxy needs to use this when decoding a local object in order to make sure the target address is a valid object. It is not enough for the Proxy to check the Proxy's connection only (using -includesLocalObject), because the proxy may have come from a triangle connection. */ + (BOOL) includesLocalObject: anObj { BOOL ret; /* Don't assert (is_valid); */ NSParameterAssert (all_connections_local_targets); [proxiesHashGate lock]; ret = NSMapGet (all_connections_local_targets, (void*)anObj) ? YES : NO; [proxiesHashGate unlock]; return ret; } /* Pass nil to remove any reference keyed by aPort. */ + (void) setRootObject: anObj forInPort: (InPort*)aPort { id oldRootObject = [self rootObjectForInPort: aPort]; NSParameterAssert ([aPort isValid]); /* xxx This retains aPort? How will aPort ever get dealloc'ed? */ if (oldRootObject != anObj) { if (anObj) { [root_object_dictionary_gate lock]; [root_object_dictionary putObject: anObj atKey: aPort]; [root_object_dictionary_gate unlock]; } else /* anObj == nil && oldRootObject != nil */ { [root_object_dictionary_gate lock]; [root_object_dictionary removeObjectAtKey: aPort]; [root_object_dictionary_gate unlock]; } } } + rootObjectForInPort: (InPort*)aPort { id ro; [root_object_dictionary_gate lock]; ro = [root_object_dictionary objectAtKey:aPort]; [root_object_dictionary_gate unlock]; return ro; } - setRootObject: anObj { [[self class] setRootObject: anObj forInPort: in_port]; return self; } - rootObject { return [[self class] rootObjectForInPort: in_port]; } /* Accessing ivars */ - (int) outTimeout { return out_timeout; } - (int) inTimeout { return in_timeout; } - (void) setOutTimeout: (int)to { out_timeout = to; } - (void) setInTimeout: (int)to { in_timeout = to; } - (Class) inPortClass { return in_port_class; } - (Class) outPortClass { return out_port_class; } - (void) setInPortClass: (Class) aPortClass { in_port_class = aPortClass; } - (void) setOutPortClass: (Class) aPortClass { out_port_class = aPortClass; } - (Class) proxyClass { /* we might replace this with a per-Connection proxy class. */ return default_proxy_class; } - (Class) encodingClass { return encoding_class; } - (Class) decodingClass { /* we might replace this with a per-Connection class. */ return default_decoding_class; } - outPort { return out_port; } - inPort { return in_port; } - delegate { return delegate; } - (void) setDelegate: anObj { delegate = anObj; } /* Support for cross-connection const-ptr cache. */ - (unsigned) _encoderCreateReferenceForConstPtr: (const void*)ptr { unsigned xref; NSParameterAssert (is_valid); /* This must match the assignment of xref in _decoderCreateRef... */ xref = NSCountMapTable (outgoing_const_ptr_2_xref) + 1; NSParameterAssert (! NSMapGet (outgoing_const_ptr_2_xref, (void*)xref)); NSMapInsert (outgoing_const_ptr_2_xref, ptr, (void*)xref); return xref; } - (unsigned) _encoderReferenceForConstPtr: (const void*)ptr { NSParameterAssert (is_valid); return (unsigned) NSMapGet (outgoing_const_ptr_2_xref, ptr); } - (unsigned) _decoderCreateReferenceForConstPtr: (const void*)ptr { unsigned xref; NSParameterAssert (is_valid); /* This must match the assignment of xref in _encoderCreateRef... */ xref = NSCountMapTable (incoming_xref_2_const_ptr) + 1; NSMapInsert (incoming_xref_2_const_ptr, (void*)xref, ptr); return xref; } - (const void*) _decoderConstPtrAtReference: (unsigned)xref { NSParameterAssert (is_valid); return NSMapGet (incoming_xref_2_const_ptr, (void*)xref); } /* Prevent trying to encode the connection itself */ - (void) encodeWithCoder: anEncoder { [self shouldNotImplement:_cmd]; } + newWithCoder: aDecoder; { [self shouldNotImplement:_cmd]; return self; } /* Shutting down and deallocating. */ /* We register this method with NotificationDispatcher for when a port dies. */ - (void) portIsInvalid: notification { id port = [notification object]; NSParameterAssert (is_valid); if (debug_connection) fprintf (stderr, "Received port invalidation notification for " "connection 0x%x\n\t%s\n", (unsigned)self, [[port description] cStringNoCopy]); /* We shouldn't be getting any port invalidation notifications, except from our own ports; this is how we registered ourselves with the NotificationDispatcher in +newForInPort:outPort:ancestorConnection. */ NSParameterAssert (port == in_port || port == out_port); /* xxx This also needs to be done properly in cases where the Connection invalidates itself. */ /* Remove ourselves from the in_port_2_ancestor, if necessary. */ { id ancestor; if ([port isKindOfClass: [InPort class]] && (self == (ancestor = NSMapGet (in_port_2_ancestor, port)))) NSMapRemove (in_port_2_ancestor, port); } [self invalidate]; /* xxx Anything else? */ } /* xxx This needs locks */ - (void) invalidate { if (is_valid) { is_valid = 0; /* xxx Note: this is causing us to send a shutdown message to the connection that shut *us* down. Don't do that. Well, perhaps it's a good idea just in case other side didn't really send us the shutdown; this way we let them know we're going away */ #if 0 [self shutdown]; #endif if (debug_connection) fprintf(stderr, "Invalidating connection 0x%x\n\t%s\n\t%s\n", (unsigned)self, [[in_port description] cStringNoCopy], [[out_port description] cStringNoCopy]); [NotificationDispatcher postNotificationName: ConnectionBecameInvalidNotification object: self]; /* xxx Anything else? */ /* xxx Yes, somehow Proxies of connections with invalid ports are being asked to encode themselves. */ } } - (BOOL) isValid { return is_valid; } /* This needs locks */ - (void) dealloc { if (debug_connection) printf("deallocating 0x%x\n", (unsigned)self); [self invalidate]; [connection_array removeObject: self]; /* Remove rootObject from root_object_dictionary if this is last connection */ if (![Connection connectionsCountWithInPort:in_port]) [Connection setRootObject:nil forInPort:in_port]; [NotificationDispatcher removeObserver: self]; [in_port release]; [out_port release]; [proxiesHashGate lock]; NSFreeMapTable (remote_proxies); NSFreeMapTable (local_targets); NSFreeMapTable (incoming_xref_2_const_ptr); NSFreeMapTable (outgoing_const_ptr_2_xref); [proxiesHashGate unlock]; [super dealloc]; } @end /* Notification Strings. */ NSString *ConnectionBecameInvalidNotification = @"ConnectionBecameInvalidNotification"; NSString *ConnectionWasCreatedNotification = @"ConnectionWasCreatedNotification"; /* RunLoop modes */ NSString *RunLoopConnectionReplyMode = @"RunLoopConnectionReplyMode";
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.