diff --git a/ChangeLog b/ChangeLog index 61558d065..752adb6d2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2002-12-12 Richard Frith-Macdonald + + * Headers/gnustep/base/NSDistributedNotificationCenter.h: add type + for LAN-wide notifications. Add ivar to support this. + * Source/NSDistributedNotificationCenter.m: Implement support for + a LAN-wide notification center. Fully document class. + * Tools/gdnc.m: Add new GSNetwork flag to operate as LAN-wide + notification center. + * Tools/AGSHtml.m: Don't output contents section if there is + nothing to show (ie must have at least two sections to index). + * Source/NSConnection.m: Add locks to protect proxy cache in timeout. + 2002-12-10 Adam Fedor * configure.ac: Enable libffi on darwin by default. diff --git a/Headers/gnustep/base/NSDistributedNotificationCenter.h b/Headers/gnustep/base/NSDistributedNotificationCenter.h index 8b45700fb..c95e9d71b 100644 --- a/Headers/gnustep/base/NSDistributedNotificationCenter.h +++ b/Headers/gnustep/base/NSDistributedNotificationCenter.h @@ -39,11 +39,15 @@ typedef enum { } NSNotificationSuspensionBehavior; GS_EXPORT NSString *NSLocalNotificationCenterType; +#ifndef NO_GNUSTEP +GS_EXPORT NSString *GSNetworkNotificationCenterType; +#endif @interface NSDistributedNotificationCenter : NSNotificationCenter { NSRecursiveLock *_centerLock; /* For thread safety. */ - id _remote; /* Proxy for center. */ + NSString *_type; /* Type of notification center. */ + id _remote; /* Proxy for center. */ BOOL _suspended; /* Is delivery suspended? */ } + (NSNotificationCenter*) defaultCenter; diff --git a/Source/NSConnection.m b/Source/NSConnection.m index e19a24258..608e539ff 100644 --- a/Source/NSConnection.m +++ b/Source/NSConnection.m @@ -662,6 +662,7 @@ static BOOL multi_threaded = NO; NSArray *cached_locals; int i; + M_LOCK(global_proxies_gate); cached_locals = NSAllMapTableValues(targetToCached); for (i = [cached_locals count]; i > 0; i--) { @@ -678,6 +679,7 @@ static BOOL multi_threaded = NO; [t invalidate]; timer = nil; } + M_UNLOCK(global_proxies_gate); } /** @@ -3107,12 +3109,12 @@ static void callEncoder (DOContext *ctxt) node = GSIMapNodeForKey(_localObjects, (GSIMapKey)anObj); if (node == 0) { - prox = nil; - } - else - { - prox = node->value.obj; + M_LOCK(global_proxies_gate); + M_LOCK(_proxiesGate); + [NSException raise: NSInternalInconsistencyException + format: @"Attempt to remove non-existent local %@", anObj]; } + prox = node->value.obj; target = ((ProxyStruct*)prox)->_handle; /* diff --git a/Source/NSDistributedNotificationCenter.m b/Source/NSDistributedNotificationCenter.m index a5f9a5c52..32bb7f395 100644 --- a/Source/NSDistributedNotificationCenter.m +++ b/Source/NSDistributedNotificationCenter.m @@ -47,6 +47,8 @@ */ NSString *NSLocalNotificationCenterType = @"NSLocalNotificationCenterType"; +NSString *GSNetworkNotificationCenterType = + @"GSNetworkNotificationCenterType"; @interface NSDistributedNotificationCenter (Private) @@ -59,41 +61,143 @@ NSString *NSLocalNotificationCenterType = to: (unsigned long)observer; @end +/** + *

The NSDistributedNotificationCenter provides a versatile yet + * simple mechanism for objects in different processes to communicate + * effectively while knowing very little about each others internals.
+ * A distributed notification center acts much like a normal + * notification center, but it handles notifications on a machine-wide + * (or local network wide) basis rather than just notifications within + * a single process. Objects are able to register themselves as + * observers for particular notification names and objects, and they + * will then receive notifications (including optional user information + * consisting of a dictionary of property-list objects) as they are posted. + *

+ *

Since posting of distributed notifications involves inter-process + * (and sometimes inter-host) communication, it is fundamentally slower + * than normal notifications, and should be used relatively sparingly. + * In order to help with this, the NSDistributedNotificationCenter + * provides a notion of 'suspension', whereby a center can be suspended + * causing notifications for observers in the process where the center + * was suspended to cease receiving notifications. Observers can + * specify how notifications are to be handled in this case (queued + * or discarded) and posters can specify that particular notifications + * are to be delivered immediately irrespective of suspension. + *

+ *

Distributed notifications are mediated by a server process which + * handles all notifications for a particular center type. In GNUstep + * this process is the gdnc tool, and when started without special + * options, a gdnc process acts as the local centre for the host it is + * running on. When started with the GSNetwork user default set to YES, + * the gdnc tool acts as a local network wide server (you should only + * run one copy of gdnc like this on your LAN).
+ * The gdnc process should be started at machine boot time, but GNUstep + * will attempt to start it automatically if it can't find it. + *

+ *

MacOS-X currently defines only a notification center for the + * local host. GNUstep also defines a local network center which can + * be used from multiple hosts. By default the system sends this to + * any gdnc process it can find which is configured as a network-wide + * server, but the GDNCHost user default may be used to specify a + * particular host to be contacted ... this may be of use where you + * wish to have logically separate clusters of machines on a shared LAN. + *

+ */ @implementation NSDistributedNotificationCenter -static NSDistributedNotificationCenter *defCenter = nil; +static NSDistributedNotificationCenter *locCenter = nil; +static NSDistributedNotificationCenter *netCenter = nil; ++ (id) allocWithZone: (NSZone*)z +{ + [NSException raise: NSInternalInconsistencyException + format: @"Should not call +alloc for NSDistributedNotificationCenter"]; + return nil; +} + +/** + * Returns the default notification center ... a shared notification + * center for the local host. This is simply a convenience method + * equivalent to calling +notificationCenterForType: with + * NSLocalNotificationCenterType as its argument. + */ + (id) defaultCenter { return [self notificationCenterForType: NSLocalNotificationCenterType]; } +/** + * Returns a notification center of the specified type.
+ * The NSLocalNotificationCenterType provides a shared access to + * a notificatiuon center used by processes on the local host.
+ * The GSNetworkNotificationCenterType provides a shared access to + * a notificatiuon center used by processes on the local network.
+ * MacOS-X supports only NSLocalNotificationCenterType. + */ + (id) notificationCenterForType: (NSString*)type { - NSAssert([type isEqual: NSLocalNotificationCenterType], - NSInvalidArgumentException); - if (defCenter == nil) + if ([type isEqual: NSLocalNotificationCenterType] == YES) { - [gnustep_global_lock lock]; - if (defCenter == nil) - { - NS_DURING + if (locCenter == nil) + { + [gnustep_global_lock lock]; + if (locCenter == nil) { - id tmp; + NS_DURING + { + NSDistributedNotificationCenter *tmp; - tmp = NSAllocateObject(self, 0, NSDefaultMallocZone()); - defCenter = (NSDistributedNotificationCenter*)[tmp init]; + tmp = (NSDistributedNotificationCenter*) + NSAllocateObject(self, 0, NSDefaultMallocZone()); + tmp->_centerLock = [NSRecursiveLock new]; + tmp->_type = RETAIN(NSLocalNotificationCenterType); + locCenter = tmp; + } + NS_HANDLER + { + [gnustep_global_lock unlock]; + [localException raise]; + } + NS_ENDHANDLER } - NS_HANDLER - { - [gnustep_global_lock unlock]; - [localException raise]; - } - NS_ENDHANDLER - } - [gnustep_global_lock unlock]; + [gnustep_global_lock unlock]; + } + return locCenter; + } + else if ([type isEqual: GSNetworkNotificationCenterType] == YES) + { + if (netCenter == nil) + { + [gnustep_global_lock lock]; + if (netCenter == nil) + { + NS_DURING + { + NSDistributedNotificationCenter *tmp; + + tmp = (NSDistributedNotificationCenter*) + NSAllocateObject(self, 0, NSDefaultMallocZone()); + tmp->_centerLock = [NSRecursiveLock new]; + tmp->_type = RETAIN(GSNetworkNotificationCenterType); + netCenter = tmp; + } + NS_HANDLER + { + [gnustep_global_lock unlock]; + [localException raise]; + } + NS_ENDHANDLER + } + [gnustep_global_lock unlock]; + } + return netCenter; + } + else + { + [NSException raise: NSInvalidArgumentException + format: @"Unknown center type (%@)", type]; + return nil; /* NOT REACHED */ } - return defCenter; } - (void) dealloc @@ -103,16 +207,26 @@ static NSDistributedNotificationCenter *defCenter = nil; [_remote unregisterClient: (id)self]; } RELEASE(_remote); - [super dealloc]; + RELEASE(_type); + NSDeallocateObject(self); } +/** + * Should not be used. + */ - (id) init { - NSAssert(_centerLock == nil, NSInternalInconsistencyException); - _centerLock = [NSRecursiveLock new]; - return self; + RELEASE(self); + [NSException raise: NSInternalInconsistencyException + format: @"Should not call -init for NSDistributedNotificationCenter"]; + return nil; } +/** + * Adds an observer to the receiver. Calls + * -addObserver:selector:name:object:suspensionBehavior: with + * NSNotificationSuspensionBehaviorCoalesce. + */ - (void) addObserver: (id)anObserver selector: (SEL)aSelector name: (NSString*)notificationName @@ -125,6 +239,38 @@ static NSDistributedNotificationCenter *defCenter = nil; suspensionBehavior: NSNotificationSuspensionBehaviorCoalesce]; } +/** + * Adds an observer to the receiver.
+ * When a notification matching notificationName and anObject is + * sent to the center, the object anObserver is sent the message + * aSelector with the notification info dictionary as its argument.
+ * The suspensionBehavior governs how the center deals with notifications + * when the process to which the notification should be delivered is + * suspended: + * + * NSNotificationSuspensionBehaviorDrop + * + * Discards the notification if the observing process is suspended. + * + * NSNotificationSuspensionBehaviorCoalesce + * + * Discards previously queued notifications when the observing process + * is suspended, leaving only the last notification posted in the queue. + * Delivers this single notification when the process becomes unsuspended. + * + * NSNotificationSuspensionBehaviorHold + * + * Queues notifications when the observing process is suspended, + * delivering all the queued notifications when the process becomes + * unsuspended again. + * + * NSNotificationSuspensionBehaviorDeliverImmediately + * + * Deliver the notification immediately, even if the destination + * process is suspended. + * + * + */ - (void) addObserver: (id)anObserver selector: (SEL)aSelector name: (NSString*)notificationName @@ -141,8 +287,8 @@ static NSDistributedNotificationCenter *defCenter = nil; [NSException raise: NSInvalidArgumentException format: @"null selector"]; } - if (notificationName != nil && - [notificationName isKindOfClass: [NSString class]] == NO) + if (notificationName != nil + && [notificationName isKindOfClass: [NSString class]] == NO) { [NSException raise: NSInvalidArgumentException format: @"invalid notification name"]; @@ -178,6 +324,11 @@ static NSDistributedNotificationCenter *defCenter = nil; [_centerLock unlock]; } +/** + * Posts the notification to the center using + * postNotificationName:object:userInfo:deliverImmediately: with the + * delivery flag set to NO. + */ - (void) postNotification: (NSNotification*)notification { [self postNotificationName: [notification name] @@ -186,6 +337,11 @@ static NSDistributedNotificationCenter *defCenter = nil; deliverImmediately: NO]; } +/** + * Posts the notificationName and anObject to the center using + * postNotificationName:object:userInfo:deliverImmediately: with the + * user info set to nil and the delivery flag set to NO. + */ - (void) postNotificationName: (NSString*)notificationName object: (NSString*)anObject { @@ -195,6 +351,11 @@ static NSDistributedNotificationCenter *defCenter = nil; deliverImmediately: NO]; } +/** + * Posts the notificationName, anObject and userInfo to the center using + * postNotificationName:object:userInfo:deliverImmediately: with the + * delivery flag set to NO. + */ - (void) postNotificationName: (NSString*)notificationName object: (NSString*)anObject userInfo: (NSDictionary*)userInfo @@ -205,13 +366,19 @@ static NSDistributedNotificationCenter *defCenter = nil; deliverImmediately: NO]; } +/** + * The primitive notification posting method ...
+ * The userInfo dictionary may contain only property-list objects.
+ * The deliverImmediately flag specifies whether the suspension + * state of the receiving process is to be ignored. + */ - (void) postNotificationName: (NSString*)notificationName object: (NSString*)anObject userInfo: (NSDictionary*)userInfo deliverImmediately: (BOOL)deliverImmediately { - if (notificationName == nil || - [notificationName isKindOfClass: [NSString class]] == NO) + if (notificationName == nil + || [notificationName isKindOfClass: [NSString class]] == NO) { [NSException raise: NSInvalidArgumentException format: @"invalid notification name"]; @@ -244,12 +411,15 @@ static NSDistributedNotificationCenter *defCenter = nil; [_centerLock unlock]; } +/** + * Removes the observer from the center. + */ - (void) removeObserver: (id)anObserver name: (NSString*)notificationName object: (NSString*)anObject { - if (notificationName != nil && - [notificationName isKindOfClass: [NSString class]] == NO) + if (notificationName != nil + && [notificationName isKindOfClass: [NSString class]] == NO) { [NSException raise: NSInvalidArgumentException format: @"invalid notification name"]; @@ -278,6 +448,12 @@ static NSDistributedNotificationCenter *defCenter = nil; [_centerLock unlock]; } +/** + * Sets the suspension state of the receiver ... if the receiver is + * suspended, it won't handle notification until it is unsuspended + * again, unless the notifications are posted to be delivered + * immediately. + */ - (void) setSuspended: (BOOL)flag { [_centerLock lock]; @@ -296,6 +472,9 @@ static NSDistributedNotificationCenter *defCenter = nil; [_centerLock unlock]; } +/** + * Returns the current suspension state of the receiver. + */ - (BOOL) suspended { return _suspended; @@ -309,58 +488,83 @@ static NSDistributedNotificationCenter *defCenter = nil; { if (_remote == nil) { - NSString *host; - NSString *description; - - /* - * Connect to the NSDistributedNotificationCenter for this host. - */ - host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; - if (host == nil) - { - host = @""; - } - else - { - NSHost *h; + NSString *host = nil; + NSString *service = nil; + NSString *description = nil; + if (_type == NSLocalNotificationCenterType) + { /* - * If we have a host specified, but it is the current host, - * we do not need to ask for a host by name (nameserver lookup - * can be faster) and the empty host name can be used to - * indicate that we may start a gdnc server locally. + * Connect to the NSDistributedNotificationCenter for this host. */ - h = [NSHost hostWithName: host]; - if (h == nil) - { - NSLog(@"Unknown -NSHost '%@' ignored", host); - host = @""; - } - else if ([h isEqual: [NSHost currentHost]] == YES) + host = [[NSUserDefaults standardUserDefaults] + stringForKey: @"NSHost"]; + if (host == nil) { host = @""; } else { - host = [h name]; - } - } + NSHost *h; - if ([host length] == 0) - { - description = @"local host"; + /* + * If we have a host specified, but it is the current host, + * we do not need to ask for a host by name (nameserver lookup + * can be faster) and the empty host name can be used to + * indicate that we may start a gdnc server locally. + */ + h = [NSHost hostWithName: host]; + if (h == nil) + { + NSLog(@"Unknown -NSHost '%@' ignored", host); + host = @""; + } + else if ([h isEqual: [NSHost currentHost]] == YES) + { + host = @""; + } + else + { + host = [h name]; + } + } + if ([host length] == 0) + { + description = @"local host"; + } + else + { + description = host; + } + service = GDNC_SERVICE; + } + else if (_type == GSNetworkNotificationCenterType) + { + host = [[NSUserDefaults standardUserDefaults] + stringForKey: @"GDNCHost"]; + description = host; + if (host == nil) + { + host = @"*"; + description = @"network host"; + } + service = GDNC_NETWORK; } else - { - description = host; + { + [NSException raise: NSInternalInconsistencyException + format: @"Unknown center type - %@", _type]; } + _remote = RETAIN([NSConnection rootProxyForConnectionWithRegisteredName: - GDNC_SERVICE host: host]); - if (_remote == nil && [host isEqual: @""] == NO) + service host: host]); + + if (_type == NSLocalNotificationCenterType + && _remote == nil && [host isEqual: @""] == NO) { _remote = [NSConnection rootProxyForConnectionWithRegisteredName: - [GDNC_SERVICE stringByAppendingFormat: @"-%@", host] host: @"*"]; + [service stringByAppendingFormat: @"-%@", host] host: @"*"]; RETAIN(_remote); } @@ -405,7 +609,12 @@ static NSDistributedNotificationCenter *defCenter = nil; @"login or (better) when your computer is started up.\n", description, [cmd stringByDeletingLastPathComponent]); - if ([host length] > 0) + if (_type == GSNetworkNotificationCenterType) + { + args = [[NSArray alloc] initWithObjects: + @"-GSNetwork", @"YES", nil]; + } + else if ([host length] > 0) { args = [[NSArray alloc] initWithObjects: @"-NSHost", host, nil]; diff --git a/Testing/nsconnection_client.m b/Testing/nsconnection_client.m index 77129b393..ad97ee3f4 100644 --- a/Testing/nsconnection_client.m +++ b/Testing/nsconnection_client.m @@ -343,13 +343,15 @@ con_loop (id prx) id cobj; arp = [NSAutoreleasePool new]; + [prx addObject: [NSObject new]]; // So loss of this connection is logged. cobj = [prx connectionForProxy]; printf("%d\n", [cobj retainCount]); printf("%s\n", [[[cobj statistics] description] cString]); //printf("%s\n", GSDebugAllocationList(YES)); + printf("loop left running idle for 30 seconds\n"); [[NSRunLoop currentRunLoop] runUntilDate: - [NSDate dateWithTimeIntervalSinceNow: 2 * 60]]; + [NSDate dateWithTimeIntervalSinceNow: 30]]; [cobj invalidate]; [arp release]; return 0; diff --git a/Tools/AGSHtml.m b/Tools/AGSHtml.m index abab89d32..f07b28399 100644 --- a/Tools/AGSHtml.m +++ b/Tools/AGSHtml.m @@ -607,7 +607,7 @@ static NSMutableSet *textNodes = nil; NSDictionary *dict; dict = [[localRefs refs] objectForKey: @"contents"]; - if ([dict count] > 0) + if ([dict count] > 1) { NSArray *a; unsigned i; diff --git a/Tools/gdnc.h b/Tools/gdnc.h index 63df8f5ef..7eb09789b 100644 --- a/Tools/gdnc.h +++ b/Tools/gdnc.h @@ -22,6 +22,7 @@ */ #define GDNC_SERVICE @"GDNCServer" +#define GDNC_NETWORK @"GDNCNetwork" @protocol GDNCClient - (oneway void) postNotificationName: (NSString*)name diff --git a/Tools/gdnc.m b/Tools/gdnc.m index 01d167a0e..b7da2165c 100644 --- a/Tools/gdnc.m +++ b/Tools/gdnc.m @@ -216,6 +216,7 @@ - (id) init { NSString *hostname; + NSString *service = GDNC_SERVICE; connections = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); @@ -225,13 +226,18 @@ conn = [NSConnection defaultConnection]; [conn setRootObject: self]; + if ([[NSUserDefaults standardUserDefaults] boolForKey: @"GSNetwork"] == YES) + { + service = GDNC_NETWORK; + } hostname = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; if ([hostname length] == 0 - || [[NSHost hostWithName: hostname] isEqual: [NSHost currentHost]] == YES) + || [[NSHost hostWithName: hostname] isEqual: [NSHost currentHost]] == YES) { - if ([conn registerName: GDNC_SERVICE] == NO) + if ([conn registerName: service] == NO) { - NSLog(@"gdnc - unable to register with name server - quiting."); + NSLog(@"gdnc - unable to register with name server as %@ - quiting.", + service); DESTROY(self); return self; } @@ -256,7 +262,7 @@ { NSString *name = [a objectAtIndex: c]; - name = [GDNC_SERVICE stringByAppendingFormat: @"-%@", name]; + name = [service stringByAppendingFormat: @"-%@", name]; if ([ns registerPort: port forName: name] == NO) { } @@ -267,7 +273,7 @@ { NSString *name = [a objectAtIndex: c]; - name = [GDNC_SERVICE stringByAppendingFormat: @"-%@", name]; + name = [service stringByAppendingFormat: @"-%@", name]; if ([ns registerPort: port forName: name] == NO) { } @@ -555,8 +561,8 @@ * the queue is empty ([obs->queue count] == 0) * the observer is removed (obs is not in allObservers) */ - while (obs != nil && [obs->queue count] > 0 && - NSHashGet(allObservers, obs) != 0) + while (obs != nil && [obs->queue count] > 0 + && NSHashGet(allObservers, obs) != 0) { NS_DURING {