Multiple NSXPCConnections, Invalidation, and Reconnection to launchd-managed service

I have small integration test that is confirming behavior in XPC communications (leveraging NSXPCConnection) between a dummy XPC service and an XPC client. My test is flaky, which is indicating to me that I don't fully understand the nature of connections between services and clients, and I was hoping to get clarity as to what's happening in my test.

My test involves the following steps.

  1. XCode Pre-Action: Load the plist for a dummy XPC service into launchctl.
  2. Create 2 XPC client objects (in the same client process), each with their own NSXPCConnection to the dummy service, and connect.
  3. Tell client 1 to disconnect, which calls NSXPCConnection.invalidate()
  4. Using client 2, send a message to the same dummy XPC service over it's own NSXPCConnection object.
  5. Wait for the echo response from the dummy XPC service
  6. XCode Post-Action: Unload the plist for the dummy XPC service from launchctl
   func testMultipleConnections() {
    let delegate1 = MockClientDelegate()
    let delegate2 = MockClientDelegate()
    let client1 = XPCMessagingClientFacade(withServiceName: serviceName, andXPCErrorHandler: {error in })
    let client2 = XPCMessagingClientFacade(withServiceName: serviceName, andXPCErrorHandler: {error in })
    client1.processMessageDelegate = delegate1
    client2.processMessageDelegate = delegate2
     
    _ = client1.connect()
    _ = client2.connect()
     
    _ = client1.disconnect()
     
    delegate2.expectation = XCTestExpectation(description: "Message received from echo service")
    _ = client2.sendMessage(ofMessageType: eMTAction_Uninstall, withData: ["dummy": "data"])
    wait(for: [delegate2.expectation!], timeout: timeout)
  }

This test sometimes succeeds and sometimes fails. Sometimes, the test expectation at the bottom is fulfilled, and sometimes the timeout is hit. I have tested with excessively long timeouts to rule-out processing-time as as factor.

I am suspecting that calling invalidate() on one NSXPCConnection object is somehow causing a separate connection between the same client and service process to also be invalidated.

  1. Is it even a valid use-case to have multiple NSXPCConnection objects between a single XPC Service and XPC Client?
  2. When NSXPCConnection.invalidate() is called, does it inherently mean nothing can connect and communicate anymore, or is it just for that specific connection?
  3. When invalidate() is called, what does launchctl do to manage that service? Does it shut it down and allow subsequent connection attempts to spool the service up again? Does it prevent from any connections from ever being made again?
Accepted Answer

Is it even a valid use-case to have multiple NSXPCConnection objects between a single XPC Service and XPC Client?

Yes.

When NSXPCConnection.invalidate() is called, does it inherently mean nothing can connect and communicate anymore, or is it just for that specific connection?

The latter.

When invalidate() is called, what does [launchd] do to manage that service?

Nothing particularly notable. launchd doesn’t track XPC connections, it tracks XPC transactions. If a job has no outstanding transactions launchd may, depending on system state and the job’s properties, stop the job. It does not, however, unload the job. This allows the system to reclaim resources from idle launchd jobs, starting them again on demand if necessary.

See the discussion of KeepAlive, EnablePressuredExit, and EnableTransactions in the launchd.plist man page.

Note We’re talking launchd not launchctl here. The launchctl process is transient; it just sends messages to launchd to manipulate its state and then terminates.


Coming back to the big picture, what is your test trying to test? Does your XPC abstraction have shared state that could potentially fail in the presence of multiple connections? If so, there are better ways to test this (more on that below). OTOH, if you don’t have such shared state then it looks like your test is actually testing launchd and XPC, which is a pretty big scope for a unit test (-:

As to how I’d test the handling of shared state, presuming you have any, I’d do that entirely in process using an anonymous listener. I’ve mentioned this technique here on DevForums a few times, but I figured it’s time to write it up in some detail. See Testing and Debugging XPC Code With an Anonymous Listener.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Quinn,

Thank you for your response. This does confirm my understanding of NSXPCConnections.

This question was actually the result of attempting to debug contention between two "unit" tests in our code that would each succeed by themselves when run, but then sometimes fail when run in the suite. I think the crux of my issue here is that these are indeed not unit tests because we are using a dummy XPC service to facilitate responses to our XPC client abstraction test cases. Thank you for recommending the testing with the anonymous listener; this approach would remove our need of spooling-up the dummy XPC service in the first place.

Multiple NSXPCConnections, Invalidation, and Reconnection to launchd-managed service
 
 
Q