XPCSession.cancel does not immediately cancel

I'm using an XPCSession to send a message to a service/agent. When calling session.cancel(reason: "stop") the connection is not immediately canceled. The session seems to wait for any pending replies from the service.

According to the docs for XPCSession.cancel:

When you cancel a session, it discards any unsent messages and invalidates its connection. If there are messages awaiting replies, the session calls the reply handlers with an appropriate XPCRichError.

This does seem to imply that messages waiting for replies are immediately canceled, however that's not the case. The service may be busy and may take a while to respond, or it may not reply at all. The session is only canceled after all enqueued messages get a reply from the service.

let session = try XPCSession(xpcService: "com.example.listener", cancellationHandler: {
    print("client session canceled")
})

try session.send("Test", replyHandler: { reply in
    print("reply", reply)
})

print("cancel")
session.cancel(reason: "cancel") // calls cancellationHandler *after* receiving reply

Is that expected behaviour? If the service does not respond at all the XPCService will never be canceled.

Compared to xpc_connection_create + xpc_connection_cancel(connection) this does seem to immediately cancel the connection and call the reply_handler and event_handler. This does not wait for pending replies from the service.

Is there an alternative way to force cancel an XPCSession?

Main goal is to protect against two potential issues:

  1. The service is busy and doesn't respond in time. The client fires a timeout and wants to disconnect/reconnect, but the cancellationHandler is not called until service responds. So the timeout is not useful, client needs to wait for a reply anyway.

  2. The service does not respond at all. This could for example happen if a Launch Agent is improperly configured. The mach service is registered, but the agent may not launch. Sending a message on the XPCSession will never receive a reply.

Answered by DTS Engineer in 907969022

Well, that’s interesting. I asked about this internally and, yeah, this is supposed to work as documented but, for Reasons™ (r. 86443002), it does not.

I’d appreciate you filing a bug about this. Once you’re done, reply here with the bug number and I’ll inject that into the internal conversation I’m having about this issue.

Unfortunately I don’t see a good workaround for this. One of the primary justifications for this to work as documented is because, without that, there’s no way to achieve the goal you’re trying to achieve )-:

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

Well, that’s interesting. I asked about this internally and, yeah, this is supposed to work as documented but, for Reasons™ (r. 86443002), it does not.

I’d appreciate you filing a bug about this. Once you’re done, reply here with the bug number and I’ll inject that into the internal conversation I’m having about this issue.

Unfortunately I don’t see a good workaround for this. One of the primary justifications for this to work as documented is because, without that, there’s no way to achieve the goal you’re trying to achieve )-:

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

XPCSession.cancel does not immediately cancel
 
 
Q