Two Obj-C processes A and B, communicating via XPC, using NSXPCConnection (the connection is created from an endpoint, unnamed).
The method signature is this:
- (void)userAction:(NSString *)identifier
update:(OITNFWPreventionStage)stage
eventInfo:(NSDictionary * _Nonnull)actionInfo
withError:(NSError * _Nullable)error
reply:(void (^ _Nullable)(BOOL))reply;
I'm using a normal asynchronous proxy
id<myProtocol> monitorProxy = [self.monitorConnection remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
NSLog( @"Monitoring XPC proxy call failed: %@", error);
}];
Since the actionInfo I'm using is NSMutableDictionary the gets updated frequently from concurrent queues and thread - I synchronize ALL my calls from process A to process B on an NSOperationQueue
NSOperationQueue *monitorUpdateQueue = [[NSOperationQueue alloc] init];
monitorUpdateQueue.name = @"monitoring queue";
monitorUpdateQueue.maxConcurrentOperationCount = 1;
monitorUpdateQueue.qualityOfService = NSQualityOfServiceUtility;
My calls typically look like this:
[monitorUpdateQueue addOperationWithBlock:^{
actionInfo[@"Files"] = [fileEvents valueForKeyPath:@"dictionary"]; // some NSArray of NSDictionaries
actionInfo[@"stage"] = ActionStagePreblocked;
[monitorProxy userAction:userActionIdentifier update:ActionStagePreblocked eventInfo:actionInfo withError:nil reply:^(BOOL reported) {
NSLog(@"Action reported");
}];
}];
Now every now and then, Process A (the caller) crashes inside this remote call...
I
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00004c52f8b94400
Exception Codes: 0x0000000000000001, 0x00004c52f8b94400
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace SIGNAL, Code 11 Segmentation fault: 11
Terminating Process: exc handler [83282]
VM Region Info: 0x4c52f8b94400 is not in any region. Bytes after previous region: 83438207583233 Bytes before following region: 21633872346112
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
commpage (reserved) 1000000000-7000000000 [384.0G] ---/--- SM=NUL ...(unallocated)
---> GAP OF 0x5f9000000000 BYTES
MALLOC_NANO 600000000000-600008000000 [128.0M] rw-/rwx SM=PRV
and the thread's stack looks like this:
Thread 10 Crashed:: Dispatch queue: monitoring queue (QOS: UTILITY)
0 libobjc.A.dylib 0x18a7e8310 objc_retain + 16
1 Foundation 0x18b8eb4a8 -[NSDictionary(NSDictionary) encodeWithCoder:] + 596
2 Foundation 0x18b8ba5f4 -[NSXPCEncoder _encodeObject:] + 520
3 Foundation 0x18b8b9ae4 _NSXPCSerializationAddInvocationArgumentsArray + 276
4 Foundation 0x18b8b95fc -[NSXPCEncoder _encodeInvocation:isReply:into:] + 256
5 Foundation 0x18b8b8798 -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:] + 1356
6 CoreFoundation 0x18aa08040 ___forwarding___ + 1088
7 CoreFoundation 0x18aa07b40 _CF_forwarding_prep_0 + 96
8 myproc 0x10041370c __84-[myproc scanContentOfFilesInEvents:userActionInfo:monitorProxy:monitoringQueue:]_block_invoke_2.1588 + 1064 (myproc.m:3690)
9 Foundation 0x18b8e0600 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 24
10 Foundation 0x18b8e04a8 -[NSBlockOperation main] + 104
11 Foundation 0x18b8e0438 __NSOPERATION_IS_INVOKING_MAIN__ + 24
12 Foundation 0x18b8df67c -[NSOperation start] + 804
13 Foundation 0x18b8df350 __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 24
14 Foundation 0x18b8df204 __NSOQSchedule_f + 184
15 libdispatch.dylib 0x18a7ad990 _dispatch_block_async_invoke2 + 148
16 libdispatch.dylib 0x18a79ebac _dispatch_client_callout + 20
17 libdispatch.dylib 0x18a7a2080 _dispatch_continuation_pop + 504
18 libdispatch.dylib 0x18a7a16dc _dispatch_async_redirect_invoke + 596
19 libdispatch.dylib 0x18a7b031c _dispatch_root_queue_drain + 396
20 libdispatch.dylib 0x18a7b0b58 _dispatch_worker_thread2 + 164
21 libsystem_pthread.dylib 0x18a959574 _pthread_wqthread + 228
22 libsystem_pthread.dylib 0x18a9582c4 start_wqthread + 8
Sorry for the terrible formatting, I could not attach the .ips file, but I attached its full text.
My question: When I'm passing an NSMutableDictionary to the remote proxy. Is it received "mutable" on the other side? and while it is being worked in on the receiving side, what happens if I modify it on the calling side (process A) ?
How do Mutable objects behave on XPC calls?
The Dictionary I'm moving only contains basic "plist approved" entries - NSString, NSNumber, NSDate, and collections (NSArray, NSDictionary). That's all. No custom classes there.
I will be most grateful for any idea or hint.
I found the problem to be this: My NSMutableDictionary
(actionInfo) sometimes contained values that were of the class NSSet.
Sadly, the Cocoa/ObjC implementation of NSXPCConnection
does not allow passing just any type of object "on the wire" to the remote process. I DO NOT completely understand the issue - because SOMETIMES it does work, and sometimes it crashes, but the moment I restrict the objects passed on the wire to those basic Foundation classes one finds in Plists (The sacred NSArray
, NSDicationary
, NSString
, NSDate
, NSNumber
and (maybe - haven't verified - NSData
) The sporadic crashes disappeared completely.
I verified also that all the objects in the NSSet I was passing were from this list of "standard" objects - so - the cause is definitely the NSSet.
What I do now, is pass [mySet allObjects]
(yielding an NSArray) to the other side, where I convert it back to [NSSet setWithArray:] and
live like that.
Maybe I missed it in the documentation - but I think the only "no no" I have seen in the docs is about classes that can't be securely encoded - and as far as I know NSSet is a honorable and securely-encodable object...
So...
I don't understand, but at least I resolved my crash.
Hope this helps anyone.