Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flatMap keeps its selector alive beyond inner observable completion #2605

Conversation

nikolaykasyanov
Copy link
Contributor

@nikolaykasyanov nikolaykasyanov commented May 28, 2024

MergeSinkIter retains MergeSink, or to be precise FlatMapSink, that retains the selector.

It's a bit similar to #2604, however I haven't yet figured out how to address it.

flatMapFirst and flatMapLast should be prone to the same issue.

@danielt1263
Copy link
Collaborator

danielt1263 commented May 28, 2024

This is a bogus test. The flatMap should not complete or free any resources in this use case.

Keep in mind, the Observable returned by flatMap does not emit a completion event until its source Observable and all the Observables it has created emit completion events. In this use case, the inner Observable never completes.

@nikolaykasyanov
Copy link
Contributor Author

nikolaykasyanov commented May 28, 2024

I respectfully disagree, in my opinion resources of the inner observable shouldn't live longer than the subscription to it (which ends with its completion, not with the completion of the observable resulting from flatMap). In the light of your argument, I wonder how #2604 is different.

@nikolaykasyanov
Copy link
Contributor Author

nikolaykasyanov commented May 28, 2024

In this use case, the inner Observable never completes.

If it did, I would have to check if object deallocated some time between the inner observable completion and the outer observable completion instead of just in the end of the test case, which would seem like a a less readable test.

@nikolaykasyanov
Copy link
Contributor Author

@danielt1263 my apologies, the test was indeed bogus, the object should be captured by the inner observable, not by flatMap's closure. I pushed a fix.

@danielt1263
Copy link
Collaborator

Note that this test, which is more like the Completable test you referenced, passes...

func testFlatMap_Complete_OuterNotComplete_DoesNotKeepSelectorAlive() {
    let scheduler = TestScheduler(initialClock: 0)

    let xs = scheduler.createHotObservable([
        .next(100, 1),
        .completed(200)
    ])

    var object = Optional.some(TestObject())

    let disposable =  xs
        .do(onCompleted: { [object] in
            _ = object // this is happening on completion now instead of inside the flatMap
        })
        .flatMap { _ in
            Observable<Never>.never()
        }
        .subscribe()
    defer {
        disposable.dispose()
    }

    scheduler.advanceTo(300) // we have to advance the scheduler
    weak var weakObject = object
    object = nil

    XCTAssertNil(weakObject)
}

@nikolaykasyanov
Copy link
Contributor Author

nikolaykasyanov commented May 28, 2024

@danielt1263 this does not pass though: nvm it does, no more late evening PRs for me 😞 Thanks for your patience.

@nikolaykasyanov
Copy link
Contributor Author

@danielt1263 however, getting back to the original test, when the inner one completes, we know that the selector closure will no longer be called, don't we?

@danielt1263
Copy link
Collaborator

danielt1263 commented May 28, 2024

Actually no, we don't know that the selector will no longer be called. If there is a new subscription to the flatMap's Observable, then the flatMap will resubscribe to its source which will produce a new set of elements. So the FlatMap object needs to retain that closure.

@nikolaykasyanov
Copy link
Contributor Author

@danielt1263 yes, but particular subscription's FlatMapSink doesn't have to.

@danielt1263
Copy link
Collaborator

The FlatMapSink represents the subscription itself. When you call subscribe the Sink will exist until either a completion event happens or the subscription is disposed. In your test, you don't dispose of the subscription until after the assertion and as we have already shown, the subscription (correctly) never completes...

@nikolaykasyanov
Copy link
Contributor Author

nikolaykasyanov commented May 28, 2024

@danielt1263 can't subscriptions "shed" no longer necessary pieces, like the selector closure upon inner subscription's completion in this example, is it a design choice?

@danielt1263
Copy link
Collaborator

I guess it could null out the closure at that point, but that seems like a needless optimization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants