Skip to content

ListenerToken's Behavior is Abnormal #3395

@bdkjones

Description

@bdkjones

Summary:

On Apple platforms, a "token" such as NSKeyValueObservation automatically releases associated resources and cancels associated activities when that token is deallocated. You do not manually call remove() on the token.

Couchbase's Swift SDK does not follow this pattern. In your SDK, you MUST call remove() before allowing the token to go out of scope, or you have a permanent leak and Couchbase activities will continue infinitely in the background.

Why This Design Should Change:

  1. It does not follow platform conventions, which makes it likely that developers who are used to the mental model of: "Activity X will continue as long as I hold a strong reference to this token and X will stop when I release that reference" are set up for failure.

  2. The current design makes it difficult to reliably shut down Couchbase. You store a ListenerToken as an iVar on some object such as DatabaseController. You can't just release DatabaseController, which in turn releases the ListenerToken iVars and shuts down Couchbase activities. You have to manually check for the tokens, call remove() on each, etc. You cannot do that in deinit, so you must add some kind of -closeCouchbase() method that cleans up all the tokens and remember to call that method before you nil your DatabaseController. That's fragile and complicated.

How ListenerToken Should Work:

The ListenerToken should work like other "tokens" in Swift: you keep a strong reference to the token for as long as you want the associated activity to continue. To stop/cancel the activity, you merely release the strong reference. In short, the work in remove() should be done automatically when the ListenerToken is deallocated.

Backwards Compatibility

This change is technically breaking, but only if someone is currently discarding ListenerTokens and purposefully allowing Couchbase to run infinitely without the ability to stop it. Any developer who is keeping a reference to the ListenerToken in order to call remove() at some point would be unaffected by this change.

Context

When the Replicator fails to connect to Sync Gateway, it retries at increasing intervals. I was surprised to see these retries continue even though every reference I held was nil-ed. I discovered that the ListenerToken was the culprit: you cannot simply let it go out of scope.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions