How many instances of the same NEFilterDataProvider can there be in a running NE?

[Q] How many instances of the same NEFilterDataProvider subclass can there be in a single running Network Extension at any given time?

I would expect that there can be only 1 instance but I'm looking at a memgraph where 2 instances are listed.

As it's the Network Extension framework that is responsible for creating, starting and stopping these instances, this is rather strange.

Answered by DTS Engineer in 847349022

OK. With a sysex all of your providers will be instantiated within the same process. That can result in multiple instances of a provider showing up. For example:

  1. The system creates an instance and starts it.

  2. It then stops it. That should release the last reference to the provider object but that’s not guaranteed. More on that below.

  3. The system decides it needs your provider running, and so it creates a second instance and starts that.

Regarding step 2, what can cause this instance to persist? Well, in general that’d be caused by a bug, but there’s no guarantee that it’s a system bug:

  • It’s possible that the system has failed to drop a reference.

  • It’s also possible that your code has created its own reference.

Either of these could be a leak, or a circular reference, or abandoned memory [1].

So, two questions:

  • Does your logging show that only one instance is started? If you had two started instances, that’d be super weird.

  • If you explore the memgraph, what’s holding the stopped instance in memory?

Share and Enjoy

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

[1] For an example of how easy this is to get this wrong, consider this code:

class FilterDataProvider: NEFilterDataProvider {

    deinit {
        self.myEventHandler = nil
    }

    var myEventHandler: (() -> Void)?

    override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
        if someCriteria() {
            self.myEventHandler = {
                … work with `self` …
            }
        }
        return .allow()
    }
}

This forms a retain loop if someCriteria() returns true: The class holds a reference to the myEventHandler closure and myEventHandler closure holds a reference to the class.

IMPORTANT Folks often break these sorts of retain loops with the ‘weak self dance’. I’m not a fan of that. In this case you want to break the retain loop by setting myEventHandler to nil in stopFilter(…).

Is your NE provider packaged as an appex or a sysex?

Share and Enjoy

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

It is a System Extension with only one data filter.

OK. With a sysex all of your providers will be instantiated within the same process. That can result in multiple instances of a provider showing up. For example:

  1. The system creates an instance and starts it.

  2. It then stops it. That should release the last reference to the provider object but that’s not guaranteed. More on that below.

  3. The system decides it needs your provider running, and so it creates a second instance and starts that.

Regarding step 2, what can cause this instance to persist? Well, in general that’d be caused by a bug, but there’s no guarantee that it’s a system bug:

  • It’s possible that the system has failed to drop a reference.

  • It’s also possible that your code has created its own reference.

Either of these could be a leak, or a circular reference, or abandoned memory [1].

So, two questions:

  • Does your logging show that only one instance is started? If you had two started instances, that’d be super weird.

  • If you explore the memgraph, what’s holding the stopped instance in memory?

Share and Enjoy

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

[1] For an example of how easy this is to get this wrong, consider this code:

class FilterDataProvider: NEFilterDataProvider {

    deinit {
        self.myEventHandler = nil
    }

    var myEventHandler: (() -> Void)?

    override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
        if someCriteria() {
            self.myEventHandler = {
                … work with `self` …
            }
        }
        return .allow()
    }
}

This forms a retain loop if someCriteria() returns true: The class holds a reference to the myEventHandler closure and myEventHandler closure holds a reference to the class.

IMPORTANT Folks often break these sorts of retain loops with the ‘weak self dance’. I’m not a fan of that. In this case you want to break the retain loop by setting myEventHandler to nil in stopFilter(…).

I don't have logs, only a memgraph, so I can't say whether there were 2 starts logged.

The 2 instances are referenced by a collection object (owned by a different class instance).

The data filter instance adds itself to the collection from the startFilterWithCompletionHandler: method and removed itself from the collection from the stopFilterWithReason: method.

So the most probable hypothesis is that the stopFilterWithReason: was not called. Which would be more a bug in the NetworkExtension framework.

I haven't been able to reproduce this case so far (like by disabling/enabling the Network filter from the System Settings).

How many instances of the same NEFilterDataProvider can there be in a running NE?
 
 
Q