NWConnection/NWListener: .failed, .cancelled, and State Handler Semantics

Hi,

I am using Apple's Network.framework APIs, specifically NWConnection and NWListener over UDP, in my project. Both APIs use state-update handlers, and I would like to clarify a few aspects of their lifecycle and callback-delivery semantics.

  1. Are .failed and .cancelled delivered only once during the lifecycle?

For a given NWConnection or NWListener instance, my understanding is that .failed is a terminal state and the object does not recover from it.

Is .failed guaranteed to be delivered at most once during the lifetime of an instance?

If the object enters .failed, can I call cancel() on that same instance? If so, will the state-update handler subsequently receive .cancelled?

Is .cancelled guaranteed to be delivered at most once per instance, even if cancel() is called multiple times?

If the object is in the .waiting state and I call cancel(), causing it to transition to .cancelled, is it guaranteed that .failed will not subsequently be delivered for that instance? Since there is no transition from .cancelled -> .failed?

  1. What happens when stateUpdateHandler is replaced while callbacks are pending?

Consider the following scenario:

I create an NWConnection and assign a stateUpdateHandler (Handler A).

The connection's state changes, and a callback for Handler A is enqueued on the connection's associated DispatchQueue.

Before that queued callback executes, I replace stateUpdateHandler with Handler B.

If the callback for Handler A was already enqueued before the handler was replaced, will Handler A still be invoked? My understanding is Yes, since it was already queued.
If the object is in the .waiting state and I call cancel(), causing it to transition to .cancelled, is it guaranteed that .failed will not subsequently be delivered for that instance
Can the same state notification be delivered to both Handler A and Handler B, or is it delivered to only one of them i.e., Handler A, since during the state-change Handler A was the one as stateUpdateHandler?

Thanks!

Answered by DTS Engineer in 911076022
Written by veer_dutta07 in 852737021

Is .failed guaranteed to be delivered at most once during the lifetime of an instance?

Yes.

Written by veer_dutta07 in 852737021

If the object enters .failed, can I call cancel() on that same instance?

Yes.

Written by veer_dutta07 in 852737021

If so, will the state-update handler subsequently receive .cancelled?

Yes.

Written by veer_dutta07 in 852737021

Is .cancelled guaranteed to be delivered at most once per instance, even if cancel() is called multiple times?

Yes.

Written by veer_dutta07 in 852737021

If the object is in the .waiting state and I call cancel(), causing it to transition to .cancelled, is it guaranteed that .failed will not subsequently be delivered for that instance?

Yes, but you have to be careful here. Calls to your state update handler are serialised and it’s possible that the .failed(_:) could be delivered before you cancellation takes effect.


Written by veer_dutta07 in 852737021
  1. What happens when stateUpdateHandler is replaced while callbacks are pending?

My advice is that you avoid writing code that does that. Rather:

  • Set your handlers before you start you connection.
  • Avoid changing them at all…
  • Except to nil them out as you cancel the connection.

If you want to do this sort of switcheroo, do it inside your handler, in code that you control, and for which you can guarantee the semantics.

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

Written by veer_dutta07 in 852737021

Is .failed guaranteed to be delivered at most once during the lifetime of an instance?

Yes.

Written by veer_dutta07 in 852737021

If the object enters .failed, can I call cancel() on that same instance?

Yes.

Written by veer_dutta07 in 852737021

If so, will the state-update handler subsequently receive .cancelled?

Yes.

Written by veer_dutta07 in 852737021

Is .cancelled guaranteed to be delivered at most once per instance, even if cancel() is called multiple times?

Yes.

Written by veer_dutta07 in 852737021

If the object is in the .waiting state and I call cancel(), causing it to transition to .cancelled, is it guaranteed that .failed will not subsequently be delivered for that instance?

Yes, but you have to be careful here. Calls to your state update handler are serialised and it’s possible that the .failed(_:) could be delivered before you cancellation takes effect.


Written by veer_dutta07 in 852737021
  1. What happens when stateUpdateHandler is replaced while callbacks are pending?

My advice is that you avoid writing code that does that. Rather:

  • Set your handlers before you start you connection.
  • Avoid changing them at all…
  • Except to nil them out as you cancel the connection.

If you want to do this sort of switcheroo, do it inside your handler, in code that you control, and for which you can guarantee the semantics.

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

@DTS Engineer

Our state update handler uses a resource that is released when .cancelled is delivered. With a concurrent queue passed to start(queue:), the following can happen:

  • .waiting and .failed are both queued.
  • The handler receives .waiting and calls cancel(), so .cancelled is queued after .failed.
  • Thread T1 starts running the handler for .failed and gets pre-empted.
  • Thread T2 runs the handler for .cancelled and releases the resource.
  • T1 resumes and uses the released resource.
    Would it be handled if, on .waiting, we don't call cancel() and let the connection move from .waiting to .failed, since .failed is delivered only once? Or is there another solution you would recommend?
    Also, we are not going to change stateUpdateHandler at runtime. But if someone does, what all can go wrong?