Skip to content
Open
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
17 changes: 14 additions & 3 deletions Service/Sources/EDOClientService.m
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,11 @@ + (nullable id)cachedEDOFromObjectUpdateIfNeeded:(nullable id)object {
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;
// Only suppress release for legacy uncounted proxies; counted proxies must send a
// counted release on dealloc to balance the host's per-vend increment.
if (!edoObject.countedByHost) {
edoObject.local = YES;
}
resolvedObject = localObject;
} else {
// Track the new remote object.
Expand Down Expand Up @@ -312,6 +314,11 @@ + (EDOServiceResponse *)sendSynchronousRequest:(EDOServiceRequest *)request
[stats reportConnectionDuration:EDOGetMillisecondsSinceMachTime(connectionStartTime)];

if (connectionError) {
if ([request class] == [EDOObjectReleaseRequest class] && errorOut == NULL) {
// Release requests are best-effort cleanup (e.g. a temporary or stopped host service may
// already be gone), so do not report errors or invoke the global client error handler.
return nil;
}
[stats reportError];
NSDictionary<NSErrorUserInfoKey, id> *userInfo = @{
EDOErrorPortKey : port,
Expand Down Expand Up @@ -380,6 +387,10 @@ + (EDOServiceResponse *)sendSynchronousRequest:(EDOServiceRequest *)request
return response;
} else {
// Cleanup broken channels before retry.
// Retrying a request after attempt 1 already ran on the receiver is non-idempotent:
// (1) the host leaks one counted vend for any vended return object whose response was lost,
// and (2) invocation arguments were boxed once by the caller and re-decoded on retry,
// causing the receiver to emit two counted releases for one caller vend.
[EDOChannelPool.sharedChannelPool removeChannelsWithPort:port];
currentAttempt += 1;
}
Expand Down
12 changes: 11 additions & 1 deletion Service/Sources/EDOHostService+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,17 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress;

/**
* Removes an EDOObject with the specified address in the host cache.
* Decrements the per-vend reference count for the object at @c remoteAddress and removes it from
* the host cache when the count reaches zero.
*
* @param remoteAddress The @c EDOPointerType containing the object address.
* @return @c YES if a tracked entry existed (and was decremented or removed); @c NO otherwise.
*/
- (BOOL)releaseObjectWithAddress:(EDOPointerType)remoteAddress;

/**
* Unconditionally removes an EDOObject with the specified address in the host cache, clearing
* any outstanding per-vend reference count.
*
* @param remoteAddress The @c EDOPointerType containing the object address.
* @return @c YES if an object was removed; @c NO otherwise.
Expand Down
59 changes: 54 additions & 5 deletions Service/Sources/EDOHostService.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ @interface EDOHostService ()
* the object.
*/
@property(nonatomic, readonly) NSMutableDictionary<NSNumber *, id> *localObjects;
/**
* Per-vend reference counts for tracked entries in @c localObjects.
* The key is the object address as an @c NSNumber and the value is the active vend count.
*/
@property(nonatomic, readonly) NSMutableDictionary<NSNumber *, NSNumber *> *localObjectsCounts;
/** The queue to update local objects atomically. */
@property(nonatomic, readonly) dispatch_queue_t localObjectsSyncQueue;
/**
Expand Down Expand Up @@ -219,6 +224,7 @@ - (instancetype)initWithPort:(UInt16)port
if (self) {
_registeredToDevice = NO;
_localObjects = [[NSMutableDictionary alloc] init];
_localObjectsCounts = [[NSMutableDictionary alloc] init];
_localObjectsSyncQueue =
dispatch_queue_create("com.google.edo.service.localObjects", DISPATCH_QUEUE_SERIAL);

Expand Down Expand Up @@ -372,22 +378,30 @@ - (EDOObject *)distantObjectForLocalObject:(id)object hostPort:(EDOHostPort *)ho
}

NSNumber *objectKey = [NSNumber numberWithLongLong:(EDOPointerType)object];
if (object != self.rootLocalObject) {
BOOL isCountedVend = (object != self.rootLocalObject);
if (isCountedVend) {
dispatch_sync(_localObjectsSyncQueue, ^{
if (![self.localObjects objectForKey:objectKey]) {
[self.localObjects setObject:object forKey:objectKey];
}
NSUInteger count = self.localObjectsCounts[objectKey].unsignedIntegerValue;
self.localObjectsCounts[objectKey] = @(count + 1);
});
}

hostPort = hostPort ?: self.port.hostPort;
EDOServicePort *port = [EDOServicePort servicePortWithPort:self.port hostPort:hostPort];

EDOObject *distantObject = nil;
if (isObjectBlock) {
return [EDOBlockObject edo_remoteProxyFromUnderlyingObject:object withPort:port];
distantObject = [EDOBlockObject edo_remoteProxyFromUnderlyingObject:object withPort:port];
} else {
return [EDOObject edo_remoteProxyFromUnderlyingObject:object withPort:port];
distantObject = [EDOObject edo_remoteProxyFromUnderlyingObject:object withPort:port];
}
if (isCountedVend) {
[distantObject edo_enableHostCountedVend];
}
return distantObject;
}

- (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerType)remoteAddress {
Expand All @@ -404,13 +418,48 @@ - (BOOL)isObjectAliveWithPort:(EDOServicePort *)port remoteAddress:(EDOPointerTy
return isAlive;
}

- (BOOL)releaseObjectWithAddress:(EDOPointerType)remoteAddress {
NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress];
__block id removedObject = nil;
__block BOOL found = NO;

dispatch_sync(_localObjectsSyncQueue, ^{
id trackedObject = self.localObjects[edoKey];
if (!trackedObject) {
return;
}
found = YES;
NSUInteger count = self.localObjectsCounts[edoKey].unsignedIntegerValue;
if (count > 1) {
self.localObjectsCounts[edoKey] = @(count - 1);
} else {
removedObject = trackedObject;
[self.localObjects removeObjectForKey:edoKey];
[self.localObjectsCounts removeObjectForKey:edoKey];
}
});
// Release outside the sync queue so object dealloc cannot re-enter localObjectsSyncQueue.
removedObject = nil;
return found;
}

- (BOOL)removeObjectWithAddress:(EDOPointerType)remoteAddress {
NSNumber *edoKey = [NSNumber numberWithLongLong:remoteAddress];
__block id removedObject = nil;
__block BOOL found = NO;

dispatch_sync(_localObjectsSyncQueue, ^{
[self.localObjects removeObjectForKey:edoKey];
id trackedObject = self.localObjects[edoKey];
if (trackedObject) {
found = YES;
removedObject = trackedObject;
[self.localObjects removeObjectForKey:edoKey];
}
[self.localObjectsCounts removeObjectForKey:edoKey];
});
return YES;
// Release outside the sync queue so object dealloc cannot re-enter localObjectsSyncQueue.
removedObject = nil;
return found;
}

- (BOOL)removeWeakObjectWithAddress:(EDOPointerType)remoteAddress {
Expand Down
10 changes: 10 additions & 0 deletions Service/Sources/EDOObject+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ typedef int64_t EDOPointerType;
@property(readonly) BOOL isLocalEdo;
/** Whether the @c EDOObject is weakly referenced. */
@property(readonly) BOOL weaklyReferenced;
/** Whether this remote proxy represents a counted vend tracked on the host service. */
@property(nonatomic, readonly, getter=isCountedByHost) BOOL countedByHost;

/** Create an object with the given local target and port the session listens on. */
+ (instancetype)objectWithTarget:(id)target port:(EDOServicePort *)port;
Expand All @@ -55,6 +57,14 @@ typedef int64_t EDOPointerType;
/** Create an EDOObject distant object based on an underlying object. */
+ (EDOObject *)edo_remoteProxyFromUnderlyingObject:(id)object withPort:(EDOServicePort *)port;

/**
* Marks this local proxy as eligible to encode @c edoCountedByHost when archived.
*
* Host services call this on newly vended proxies for counted objects (non-root) so clients send
* a counted release rather than tearing down all outstanding vends.
*/
- (void)edo_enableHostCountedVend;

/**
* The method to forward invocation.
*
Expand Down
19 changes: 18 additions & 1 deletion Service/Sources/EDOObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
static NSString *const kEDOObjectCoderRemoteClassKey = @"edoRemoteClass";
static NSString *const kEDOObjectCoderClassNameKey = @"edoClassName";
static NSString *const kEDOObjectCoderProcessUUIDKey = @"edoProcessUUID";
static NSString *const kEDOObjectCoderCountedByHostKey = @"edoCountedByHost";

/** Returns the boolean idicating if the two objects are from the same process. */
static BOOL IsFromSameProcess(id object1, id object2);
Expand All @@ -49,6 +50,10 @@ @interface EDOObject ()
@property(nonatomic, readonly) NSString *className;
/** The process unique identifier for the remote object. */
@property(nonatomic, readonly) NSString *processUUID;
/** Whether this remote proxy represents a counted vend tracked on the host service. */
@property(nonatomic, readonly, getter=isCountedByHost) BOOL countedByHost;
/** Whether this local proxy represents a fresh host vend eligible to encode @c edoCountedByHost. */
@property(nonatomic) BOOL hostCountedVend;
@end

@implementation EDOObject
Expand All @@ -72,6 +77,8 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder {
_local = NO;
_processUUID = [aDecoder decodeObjectOfClass:[NSString class]
forKey:kEDOObjectCoderProcessUUIDKey];
_countedByHost = [aDecoder decodeBoolForKey:kEDOObjectCoderCountedByHostKey];
_hostCountedVend = NO;
return self;
}

Expand Down Expand Up @@ -117,9 +124,15 @@ - (instancetype)edo_initWithLocalObject:(id)target port:(EDOServicePort *)port {
_className = NSStringFromClass(object_getClass(target));
_processUUID = [EDOObject edo_processUUID];
_local = YES;
_countedByHost = NO;
_hostCountedVend = NO;
return self;
}

- (void)edo_enableHostCountedVend {
self.hostCountedVend = YES;
}

#pragma mark - Class method proxy

- (instancetype)alloc {
Expand Down Expand Up @@ -158,7 +171,8 @@ - (void)dealloc {
[EDOClientService removeDistantObjectReferenceForObject:self];
@try {
EDOObjectReleaseRequest *request =
[EDOObjectReleaseRequest requestWithRemoteAddress:_remoteAddress];
_countedByHost ? [EDOObjectReleaseRequest requestWithCountedRemoteAddress:_remoteAddress]
: [EDOObjectReleaseRequest requestWithRemoteAddress:_remoteAddress];
[EDOClientService sendSynchronousRequest:request onPort:_servicePort.hostPort];
} @catch (NSException *e) {
// There's an error with the service or most likely it's dead.
Expand All @@ -179,6 +193,9 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt64:self.remoteClass forKey:kEDOObjectCoderRemoteClassKey];
[aCoder encodeObject:self.className forKey:kEDOObjectCoderClassNameKey];
[aCoder encodeObject:self.processUUID forKey:kEDOObjectCoderProcessUUIDKey];
if (self.hostCountedVend) {
[aCoder encodeBool:YES forKey:kEDOObjectCoderCountedByHostKey];
}
}

- (BOOL)isEqual:(id)object {
Expand Down
8 changes: 8 additions & 0 deletions Service/Sources/EDOObjectReleaseMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ NS_ASSUME_NONNULL_BEGIN
*/
+ (instancetype)requestWithRemoteAddress:(EDOPointerType)remoteAddress;

/**
* Creates an object release request that decrements one counted vend on the host.
*
* @param remoteAddress The remote object address to decrement.
* @return An instance of @c EDOObjectReleaseRequest.
*/
+ (instancetype)requestWithCountedRemoteAddress:(EDOPointerType)remoteAddress;

/**
* Creates an EDOObjectReleaseRequest for weak EDOObjects.
*
Expand Down
21 changes: 18 additions & 3 deletions Service/Sources/EDOObjectReleaseMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@

static NSString *const kEDOObjectReleaseCoderWeaklyReferencedKey = @"weaklyReferenced";
static NSString *const kEDOObjectReleaseCoderRemoteAddressKey = @"remoteAddress";
static NSString *const kEDOObjectReleaseCoderCountedReleaseKey = @"countedRelease";

@interface EDOObjectReleaseRequest ()

@property(nonatomic, readonly) EDOPointerType remoteAddress;

/** Indicates whether the object to be released is a weakly referenced object. */
@property(nonatomic, readonly, getter=isWeaklyReferenced) BOOL weaklyReferenced;
/** Indicates whether this request decrements one counted vend rather than force-removing. */
@property(nonatomic, readonly, getter=isCountedRelease) BOOL countedRelease;

@end

Expand All @@ -41,28 +44,35 @@ + (BOOL)supportsSecureCoding {
}

- (instancetype)initWithRemoteAddress:(EDOPointerType)remoteAddress
weaklyReferenced:(BOOL)weaklyReferenced {
weaklyReferenced:(BOOL)weaklyReferenced
countedRelease:(BOOL)countedRelease {
self = [super init];
if (self) {
_remoteAddress = remoteAddress;
_weaklyReferenced = weaklyReferenced;
_countedRelease = countedRelease;
}
return self;
}

+ (instancetype)requestWithRemoteAddress:(EDOPointerType)remoteAddress {
return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:NO];
return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:NO countedRelease:NO];
}

+ (instancetype)requestWithCountedRemoteAddress:(EDOPointerType)remoteAddress {
return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:NO countedRelease:YES];
}

+ (instancetype)requestWithWeakRemoteAddress:(EDOPointerType)remoteAddress {
return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:YES];
return [[self alloc] initWithRemoteAddress:remoteAddress weaklyReferenced:YES countedRelease:NO];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_remoteAddress = [aDecoder decodeInt64ForKey:kEDOObjectReleaseCoderRemoteAddressKey];
_weaklyReferenced = [aDecoder decodeBoolForKey:kEDOObjectReleaseCoderWeaklyReferencedKey];
_countedRelease = [aDecoder decodeBoolForKey:kEDOObjectReleaseCoderCountedReleaseKey];
}
return self;
}
Expand All @@ -71,6 +81,9 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeInt64:self.remoteAddress forKey:kEDOObjectReleaseCoderRemoteAddressKey];
[aCoder encodeBool:self.weaklyReferenced forKey:kEDOObjectReleaseCoderWeaklyReferencedKey];
if (self.countedRelease) {
[aCoder encodeBool:YES forKey:kEDOObjectReleaseCoderCountedReleaseKey];
}
}

+ (EDORequestHandler)requestHandler {
Expand All @@ -79,6 +92,8 @@ + (EDORequestHandler)requestHandler {
EDOPointerType edoRemoteAddress = releaseRequest.remoteAddress;
if (releaseRequest.weaklyReferenced) {
[service removeWeakObjectWithAddress:edoRemoteAddress];
} else if (releaseRequest.countedRelease) {
[service releaseObjectWithAddress:edoRemoteAddress];
} else {
[service removeObjectWithAddress:edoRemoteAddress];
}
Expand Down
Loading
Loading