Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions Service/Sources/EDOClientService+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,36 @@ void EDOExportEDOClientError(NSError *error);
/** The internal use for sending and receiving EDOObject. */
@interface EDOClientService (Private)

/** The EDOObjects created by all services that are mapped by the remote address. */
@property(class, readonly) NSMapTable<NSNumber *, EDOObject *> *localDistantObjects;
/** The EDOObjects or distant blocks created by all services, mapped by remote address. */
@property(class, readonly) NSMapTable<NSNumber *, id> *localDistantObjects;
/** The synchronization queue for accessing remote object references. */
@property(class, readonly) dispatch_queue_t edoSyncQueue;
/** The data of ping message for channel health check. */
@property(class, readonly, nonatomic) NSData *pingMessageData;

/** Get the reference of a distant object of the given @c remoteAddress. */
+ (EDOObject *)distantObjectReferenceForRemoteAddress:(EDOPointerType)remoteAddress;

/** Add reference of given distant object. It could be an EDOObject or dummy block object. */
+ (void)addDistantObjectReference:(id)object;
/**
* Get the reference of a distant object of the given @c remoteAddress.
*/
+ (nullable id)distantObjectReferenceForRemoteAddress:(EDOPointerType)remoteAddress;

/** Remove the reference of a distant object of the given @c remoteAddress. */
/**
* Remove the reference of a distant object of the given @c remoteAddress.
*
* @note Prefer @c +removeDistantObjectReferenceForObject:.
*/
+ (void)removeDistantObjectReference:(EDOPointerType)remoteAddress;

/**
* Removes @c edoObject from the distant-object cache only if it is the currently cached entry (or
* its weak lookup returns @c nil during deallocation).
*
* @note @c edoObject is @c __unsafe_unretained because @c -[EDOObject dealloc] invokes this method,
* avoiding an ARC @c __strong parameter retain/release on a deallocating @c NSProxy.
*/
+ (void)removeDistantObjectReferenceForObject:(EDOObject *__unsafe_unretained)edoObject;

/** Try to get the object from local cache. Update the cache if @c object is not in it. */
+ (id)cachedEDOFromObjectUpdateIfNeeded:(id)object;
+ (nullable id)cachedEDOFromObjectUpdateIfNeeded:(nullable id)object;

/**
* Synchronously sends the request and waits for the response with the executor to process
Expand Down
93 changes: 65 additions & 28 deletions Service/Sources/EDOClientService.m
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ + (BOOL)isServiceAvailableOnPort:(UInt16)port {

#pragma mark - Private Category

+ (NSMapTable *)localDistantObjects {
static NSMapTable<NSNumber *, EDOObject *> *localDistantObjects;
+ (NSMapTable<NSNumber *, id> *)localDistantObjects {
static NSMapTable<NSNumber *, id> *localDistantObjects;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
localDistantObjects = [NSMapTable strongToWeakObjectsMapTable];
Expand All @@ -188,51 +188,88 @@ + (NSData *)pingMessageData {
return _pingMessageData;
}

+ (EDOObject *)distantObjectReferenceForRemoteAddress:(EDOPointerType)remoteAddress {
+ (nullable id)distantObjectReferenceForRemoteAddress:(EDOPointerType)remoteAddress {
NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress];
__block EDOObject *result;
__block id result;
dispatch_sync(self.edoSyncQueue, ^{
result = [self.localDistantObjects objectForKey:edoKey];
});
return result;
}

+ (void)addDistantObjectReference:(id)object {
EDOObject *edoObject =
[EDOBlockObject isBlock:object] ? [EDOBlockObject EDOBlockObjectFromBlock:object] : object;
NSNumber *edoKey = [NSNumber numberWithLongLong:edoObject.remoteAddress];
+ (void)removeDistantObjectReference:(EDOPointerType)remoteAddress {
NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress];
dispatch_sync(self.edoSyncQueue, ^{
[self.localDistantObjects setObject:object forKey:edoKey];
[self.localDistantObjects removeObjectForKey:edoKey];
});
}

+ (void)removeDistantObjectReference:(EDOPointerType)remoteAddress {
NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress];
+ (void)removeDistantObjectReferenceForObject:(EDOObject *__unsafe_unretained)edoObject {
NSParameterAssert(edoObject);
if (!edoObject) {
return;
}
NSNumber *edoKey = [NSNumber numberWithLongLong:edoObject.remoteAddress];
__block id cachedObject = nil;
dispatch_sync(self.edoSyncQueue, ^{
[self.localDistantObjects removeObjectForKey:edoKey];
EDOObject *__unsafe_unretained cachedEDO = nil;
// Drain NSMapTable weak-load and EDOBlockObjectFromBlock: autoreleases inside dispatch_sync
// while cachedObject holds a strong reference until after dispatch_sync returns.
@autoreleasepool {
cachedObject = [self.localDistantObjects objectForKey:edoKey];
cachedEDO = [EDOBlockObject isBlock:cachedObject]
? [EDOBlockObject EDOBlockObjectFromBlock:cachedObject]
: cachedObject;
}
// Also evict keys whose weak lookup returns nil because the cached entry is already
// deallocating.
if (!cachedObject || cachedEDO == edoObject) {
[self.localDistantObjects removeObjectForKey:edoKey];
}
});
// Release cachedObject outside edoSyncQueue so a concurrent final release cannot trigger a
// re-entrant -[EDOObject dealloc] while edoSyncQueue is held.
cachedObject = nil;
}

+ (id)cachedEDOFromObjectUpdateIfNeeded:(id)object {
+ (nullable id)cachedEDOFromObjectUpdateIfNeeded:(nullable id)object {
EDOObject *edoObject =
[EDOBlockObject isBlock:object] ? [EDOBlockObject EDOBlockObjectFromBlock:object] : object;
Class objClass = object_getClass(edoObject);
if (objClass == [EDOObject class] || objClass == [EDOBlockObject class]) {
id localObject = [self distantObjectReferenceForRemoteAddress:edoObject.remoteAddress];
EDOObject *localEDO = localObject;
if ([EDOBlockObject isBlock:localObject]) {
localEDO = [EDOBlockObject EDOBlockObjectFromBlock:localEDO];
}
// Verify the service in case the old address is overwritten by a new service.
if ([edoObject.servicePort match:localEDO.servicePort]) {
// Since we already have the EDOObject in the cache, the new decoded EDOObject is
// taken as a temporary local object, which does not send release message.
edoObject.local = YES;
return localObject;
} else {
// Track the new remote object.
[self addDistantObjectReference:object];
}
NSNumber *edoKey = [NSNumber numberWithLongLong:edoObject.remoteAddress];
__block id resolvedObject = nil;
__block id localObject = nil;
dispatch_sync(self.edoSyncQueue, ^{
EDOObject *__unsafe_unretained localEDO = nil;
// Drain NSMapTable weak-load and EDOBlockObjectFromBlock: autoreleases inside dispatch_sync
// while localObject holds a strong reference until after dispatch_sync returns.
@autoreleasepool {
localObject = [self.localDistantObjects objectForKey:edoKey];
localEDO = [EDOBlockObject isBlock:localObject]
? [EDOBlockObject EDOBlockObjectFromBlock:localObject]
: localObject;
}
if (localObject && localEDO == edoObject) {
// Re-normalizing the already-cached entry must not mark the sole cached proxy local.
resolvedObject = localObject;
} else if (localObject && [edoObject.servicePort match:localEDO.servicePort]) {
// Verify the service in case the old address is overwritten by a new service.
// Since we already have the EDOObject in the cache, the new decoded EDOObject is
// taken as a temporary local object, which does not send release message.
edoObject.local = YES;
resolvedObject = localObject;
} else {
// Track the new remote object.
[self.localDistantObjects setObject:object forKey:edoKey];
resolvedObject = object;
}
});
// Release localObject outside edoSyncQueue so a concurrent final release on the port-mismatch
// replacement branch cannot trigger a re-entrant -[EDOObject dealloc] while edoSyncQueue is
// held.
localObject = nil;
return resolvedObject;
}
return object;
}
Expand Down
4 changes: 2 additions & 2 deletions Service/Sources/EDOObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ - (instancetype)mutableCopy {
- (void)dealloc {
// Send a release message only when the object is not from the same process.
if (![self isLocal] && ![self isLocalEdo]) {
// Release the local edo manually to make sure the entry is removed from the cache.
[EDOClientService removeDistantObjectReference:self.remoteAddress];
// Evict the distant-object cache entry only if it still refers to self (or weak lookup is nil).
[EDOClientService removeDistantObjectReferenceForObject:self];
@try {
EDOObjectReleaseRequest *request =
[EDOObjectReleaseRequest requestWithRemoteAddress:_remoteAddress];
Expand Down
Loading
Loading