/core/externals/google-toolbox-for-mac/Foundation/GTMAbstractDOListener.m

http://macfuse.googlecode.com/ · Objective C · 454 lines · 296 code · 84 blank · 74 comment · 48 complexity · a6aa012df9e9685d64e7ccef8c94ba40 MD5 · raw file

  1. //
  2. // GTMAbstractDOListener.m
  3. //
  4. // Copyright 2006-2009 Google Inc.
  5. //
  6. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7. // use this file except in compliance with the License. You may obtain a copy
  8. // of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. // License for the specific language governing permissions and limitations under
  16. // the License.
  17. //
  18. #import "GTMAbstractDOListener.h"
  19. #import "GTMSystemVersion.h"
  20. #import <mach/mach_init.h>
  21. // Hack workaround suggested by DTS for the DO deadlock bug. Basically, this
  22. // class intercepts the delegate role for DO's receive port (which is an
  23. // NSMachPort). When -handlePortMessage: is called, it verifies that the send
  24. // and receive ports are not nil, then forwards the message on to the original
  25. // delegate. If the ports are nil, then the resulting NSConnection would
  26. // eventually cause us to deadlock. In this case, it simply ignores the
  27. // message. This is only need on Tiger.
  28. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  29. @interface GTMReceivePortDelegate : NSObject {
  30. __weak id delegate_;
  31. }
  32. - (id)initWithDelegate:(id)delegate;
  33. - (void)handlePortMessage:(NSPortMessage *)message;
  34. @end
  35. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  36. @interface GTMAbstractDOListener (PrivateMethods)
  37. - (BOOL)startListening;
  38. - (void)stopListening;
  39. // Returns a description of the port based on the type of port.
  40. - (NSString *)portDescription;
  41. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  42. // Uses the GTMReceivePortDelegate hack (see comments above) if we're on Tiger.
  43. - (void)hackaroundTigerDOWedgeBug:(NSConnection *)conn;
  44. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  45. @end
  46. // Static global set that holds a pointer to all instances of
  47. // GTMAbstractDOListener subclasses.
  48. //
  49. static NSMutableSet *gAllListeners = nil;
  50. @implementation GTMAbstractDOListener
  51. + (void)initialize {
  52. if (self == [GTMAbstractDOListener class]) {
  53. // We create the set using CFSetCreateMutable because we don't
  54. // want to retain things in this set. If we retained things in the
  55. // set we would never be able to dealloc ourselves because we
  56. // add "self" to this set in it's init routine would cause an
  57. // extra retain to be added to it.
  58. gAllListeners = (NSMutableSet*)CFSetCreateMutable(NULL, 0, NULL);
  59. }
  60. }
  61. + (NSArray *)allListeners {
  62. // We return an NSArray instead of an NSSet here because NSArrays look nicer
  63. // when displayed as %@
  64. NSArray *allListeners = nil;
  65. @synchronized (gAllListeners) {
  66. allListeners = [gAllListeners allObjects];
  67. }
  68. return allListeners;
  69. }
  70. - (id)init {
  71. return [self initWithRegisteredName:nil protocol:NULL];
  72. }
  73. - (id)initWithRegisteredName:(NSString *)name protocol:(Protocol *)proto {
  74. return [self initWithRegisteredName:name
  75. protocol:proto
  76. port:[NSMachPort port]];
  77. }
  78. - (id)initWithRegisteredName:(NSString *)name
  79. protocol:(Protocol *)proto
  80. port:(NSPort *)port {
  81. self = [super init];
  82. if (!self) {
  83. return nil;
  84. }
  85. if ((!proto) || (!port) || (!name)) {
  86. if (!proto) {
  87. _GTMDevLog(@"Failed to create a listener, a protocol must be specified");
  88. }
  89. if (!port) {
  90. _GTMDevLog(@"Failed to create a listener, a port must be specified");
  91. }
  92. if (!name) {
  93. _GTMDevLog(@"Failed to create a listener, a name must be specified");
  94. }
  95. [self release];
  96. return nil;
  97. }
  98. registeredName_ = [name copy];
  99. protocol_ = proto; // Can't retain protocols
  100. port_ = [port retain];
  101. requestTimeout_ = -1;
  102. replyTimeout_ = -1;
  103. heartRate_ = (NSTimeInterval)10.0;
  104. _GTMDevAssert(gAllListeners, @"gAllListeners is not nil");
  105. @synchronized (gAllListeners) {
  106. [gAllListeners addObject:self];
  107. }
  108. return self;
  109. }
  110. - (void)dealloc {
  111. _GTMDevAssert(gAllListeners, @"gAllListeners is not nil");
  112. @synchronized (gAllListeners) {
  113. [gAllListeners removeObject:self];
  114. }
  115. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  116. [receivePortDelegate_ release];
  117. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  118. [self shutdown];
  119. [port_ release];
  120. [registeredName_ release];
  121. [super dealloc];
  122. }
  123. #pragma mark Getters and Setters
  124. - (NSString *)registeredName {
  125. return registeredName_;
  126. }
  127. - (void)setRegisteredName:(NSString *)name {
  128. if (!name) {
  129. return;
  130. }
  131. [registeredName_ autorelease];
  132. registeredName_ = [name copy];
  133. }
  134. - (NSTimeInterval)requestTimeout {
  135. return requestTimeout_;
  136. }
  137. - (void)setRequestTimeout:(NSTimeInterval)timeout {
  138. requestTimeout_ = timeout;
  139. }
  140. - (NSTimeInterval)replyTimeout {
  141. return replyTimeout_;
  142. }
  143. - (void)setReplyTimeout:(NSTimeInterval)timeout {
  144. replyTimeout_ = timeout;
  145. }
  146. - (void)setThreadHeartRate:(NSTimeInterval)heartRate {
  147. heartRate_ = heartRate;
  148. }
  149. - (NSTimeInterval)ThreadHeartRate {
  150. return heartRate_;
  151. }
  152. - (NSConnection *)connection {
  153. return connection_;
  154. }
  155. - (NSString *)description {
  156. return [NSString stringWithFormat:@"%@<%p> { name=\"%@\", %@ }",
  157. [self class], self, registeredName_, [self portDescription]];
  158. }
  159. #pragma mark "Run" methods
  160. - (BOOL)runInCurrentThread {
  161. return [self startListening];
  162. }
  163. - (void)runInNewThreadWithErrorTarget:(id)errObject
  164. selector:(SEL)selector
  165. withObjectArgument:(id)argument {
  166. NSInvocation *invocation = nil;
  167. _GTMDevAssert(((errObject != nil && selector != NULL) ||
  168. (!errObject && !selector)), @"errObject and selector must "
  169. @"both be nil or not nil");
  170. // create an invocation we can use if things fail
  171. if (errObject) {
  172. NSMethodSignature *signature =
  173. [errObject methodSignatureForSelector:selector];
  174. invocation = [NSInvocation invocationWithMethodSignature:signature];
  175. [invocation setSelector:selector];
  176. [invocation setTarget:errObject];
  177. // If the selector they passed in takes an arg (i.e., it has at least one
  178. // colon in the selector name), then set the first user-specified arg to be
  179. // the |argument| they specified. The first two args are self and _cmd.
  180. if ([signature numberOfArguments] > 2) {
  181. [invocation setArgument:&argument atIndex:2];
  182. }
  183. [invocation retainArguments];
  184. }
  185. shouldShutdown_ = NO;
  186. [NSThread detachNewThreadSelector:@selector(threadMain:)
  187. toTarget:self
  188. withObject:invocation];
  189. }
  190. - (void)shutdown {
  191. // If we're not running in a new thread (then we're running in the "current"
  192. // thread), tear down the NSConnection here. If we are running in a new
  193. // thread we just set the shouldShutdown_ flag, and the thread will teardown
  194. // the NSConnection itself.
  195. if (!isRunningInNewThread_) {
  196. [self stopListening];
  197. } else {
  198. shouldShutdown_ = YES;
  199. }
  200. }
  201. @end
  202. @implementation GTMAbstractDOListener (PrivateMethods)
  203. - (BOOL)startListening {
  204. BOOL result = NO;
  205. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  206. _GTMDevAssert(!connection_, @"Connection_ should not be set. Was this "
  207. @"listener already started? %@", self);
  208. connection_ = [[NSConnection alloc] initWithReceivePort:port_ sendPort:nil];
  209. NSProtocolChecker *checker =
  210. [NSProtocolChecker protocolCheckerWithTarget:self
  211. protocol:protocol_];
  212. if (requestTimeout_ >= 0) {
  213. [connection_ setRequestTimeout:requestTimeout_];
  214. }
  215. if (replyTimeout_ >= 0) {
  216. [connection_ setReplyTimeout:replyTimeout_];
  217. }
  218. // Set the connection's root object to be the protocol checker so that only
  219. // methods listed in the protocol_ are available via DO.
  220. [connection_ setRootObject:checker];
  221. // Allow subclasses to be the connection delegate
  222. [connection_ setDelegate:self];
  223. // Because of radar 5493309 we need to do this. [NSConnection registeredName:]
  224. // returns NO when the connection is created using an NSSocketPort under
  225. // Leopard.
  226. //
  227. // The recommendation from Apple was to use the command:
  228. // [NSConnection registerName:withNameServer:].
  229. NSPortNameServer *server;
  230. if ([port_ isKindOfClass:[NSSocketPort class]]) {
  231. server = [NSSocketPortNameServer sharedInstance];
  232. } else {
  233. server = [NSPortNameServer systemDefaultPortNameServer];
  234. }
  235. BOOL registered = [connection_ registerName:registeredName_
  236. withNameServer:server];
  237. if (registeredName_ && registered) {
  238. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  239. [self hackaroundTigerDOWedgeBug:connection_];
  240. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  241. result = YES;
  242. _GTMDevLog(@"listening on %@ with name '%@'", [self portDescription],
  243. registeredName_);
  244. } else {
  245. _GTMDevLog(@"failed to register %@ with %@", connection_, registeredName_);
  246. }
  247. // we're good, so call the overrideable initializer
  248. if (result) {
  249. // Call the virtual "runIn*" initializer
  250. result = [self doRunInitialization];
  251. } else {
  252. [connection_ invalidate];
  253. [connection_ release];
  254. connection_ = nil;
  255. }
  256. [pool drain];
  257. return result;
  258. }
  259. - (void)stopListening {
  260. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  261. [connection_ invalidate];
  262. [connection_ release];
  263. connection_ = nil;
  264. [pool drain];
  265. }
  266. - (NSString *)portDescription {
  267. NSString *portDescription;
  268. if ([port_ isKindOfClass:[NSMachPort class]]) {
  269. portDescription = [NSString stringWithFormat:@"mach_port=%#x",
  270. [(NSMachPort *)port_ machPort]];
  271. } else {
  272. portDescription = [NSString stringWithFormat:@"port=%@",
  273. [port_ description]];
  274. }
  275. return portDescription;
  276. }
  277. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  278. - (void)hackaroundTigerDOWedgeBug:(NSConnection *)conn {
  279. if ([GTMSystemVersion isTiger]) {
  280. NSPort *receivePort = [conn receivePort];
  281. if ([receivePort isKindOfClass:[NSMachPort class]]) {
  282. id portDelegate = [receivePort delegate];
  283. receivePortDelegate_ =
  284. [[GTMReceivePortDelegate alloc] initWithDelegate:portDelegate];
  285. [receivePort setDelegate:receivePortDelegate_];
  286. }
  287. }
  288. }
  289. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  290. @end
  291. @implementation GTMAbstractDOListener (GTMAbstractDOListenerSubclassMethods)
  292. - (BOOL)doRunInitialization {
  293. return YES;
  294. }
  295. //
  296. // -threadMain:
  297. //
  298. //
  299. - (void)threadMain:(NSInvocation *)failureCallback {
  300. isRunningInNewThread_ = YES;
  301. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  302. // register
  303. if ([self startListening]) {
  304. // spin
  305. for (;;) { // Run forever
  306. // check if we were asked to shutdown
  307. if (shouldShutdown_) {
  308. break;
  309. }
  310. NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
  311. // Wrap our runloop in case we get an exception from DO
  312. @try {
  313. NSDate *waitDate = [NSDate dateWithTimeIntervalSinceNow:heartRate_];
  314. [[NSRunLoop currentRunLoop] runUntilDate:waitDate];
  315. } @catch (id e) {
  316. _GTMDevLog(@"Listener '%@' caught exception: %@", registeredName_, e);
  317. }
  318. [localPool drain];
  319. }
  320. } else {
  321. // failed, if we had something to invoke, call it on the main thread
  322. if (failureCallback) {
  323. [failureCallback performSelectorOnMainThread:@selector(invoke)
  324. withObject:nil
  325. waitUntilDone:NO];
  326. }
  327. }
  328. [self stopListening];
  329. [pool drain];
  330. isRunningInNewThread_ = NO;
  331. }
  332. @end
  333. #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
  334. @implementation GTMReceivePortDelegate
  335. - (id)initWithDelegate:(id)delegate {
  336. if ((self = [super init])) {
  337. delegate_ = delegate; // delegates aren't retained
  338. }
  339. return self;
  340. }
  341. - (void)handlePortMessage:(NSPortMessage *)message {
  342. NSPort *receivePort = [message receivePort];
  343. NSPort *sendPort = [message sendPort];
  344. // If we don't have a sensible send or receive port, just act like
  345. // the message never arrived. Otherwise, hand it off to the original
  346. // delegate (which is the NSMachPort itself).
  347. if (receivePort == nil || sendPort == nil || [receivePort isEqual:sendPort]) {
  348. _GTMDevLog(@"Dropping port message destined for itself to avoid DO wedge.");
  349. } else {
  350. // Uncomment for super-duper verbose DO message forward logging
  351. // _GTMDevLog(@"--> Forwarding message %@ to delegate %@",
  352. // message, delegate_);
  353. [delegate_ handlePortMessage:message];
  354. }
  355. // If processing the message caused us to drop no longer being the delegate,
  356. // set us back. Due to interactions between NSConnection and NSMachPort,
  357. // it's possible for the NSMachPort's delegate to get set back to its
  358. // original value. If that happens, we set it back to the value we want.
  359. if ([delegate_ delegate] != self) {
  360. if ([delegate_ delegate] == delegate_) {
  361. _GTMDevLog(@"Restoring DO delegate to %@", self);
  362. [delegate_ setDelegate:self];
  363. } else {
  364. _GTMDevLog(@"GMReceivePortDelegate replaced with %@",
  365. [delegate_ delegate]);
  366. }
  367. }
  368. }
  369. @end
  370. #endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4