CombineCommunity / CombineExt

CombineExt provides a collection of operators, publishers and utilities for Combine, that are not provided by Apple themselves, but are common in other Reactive Frameworks and standards.
https://combine.community
MIT License
1.73k stars 155 forks source link

Issues with RetryWhen #148

Open Kn1kt opened 1 year ago

Kn1kt commented 1 year ago

Now RetryWhen subscribes to upstream twice. This is because the Sink itself subscribes to the upstream in .init. Test testSuccessfulRetry() checks the number of subscriptions, but not correctly. It should check that the error publisher produces at least one item.

The correct test should look like this:

func testSuccessfulRetry() {
        var times = 0
        var retriesCount = 0

        var expectedOutput: Int?

        var completion: Subscribers.Completion<RetryWhenTests.MyError>?

        subscription = Deferred(createPublisher: { () -> AnyPublisher<Int, MyError> in
            defer { times += 1 }
            if times == 0 {
                return Fail<Int, MyError>(error: MyError.someError).eraseToAnyPublisher()
            }
            else {
                return Just(5).setFailureType(to: MyError.self).eraseToAnyPublisher()
            }
        })
        .retryWhen { error in
            error
                .handleEvents(receiveOutput: { _ in retriesCount += 1})
                .map { _ in }
        }
        .sink(
            receiveCompletion: { completion = $0 },
            receiveValue: { expectedOutput = $0 }
        )

        XCTAssertEqual(
            expectedOutput,
            5
        )
        XCTAssertEqual(completion, .finished)
        XCTAssertEqual(times, 2)
        XCTAssertEqual(retriesCount, 1)
    }

A possible solution is to remove upstream subscription from Sink.init and setting upstreamIsCancelled cancelUpstream function.

private let lock = NSRecursiveLock()

/// ...

func cancelUpstream() {
    lock.lock()
    guard !upstreamIsCancelled else {
        lock.unlock()
        return
    }
    upstreamIsCancelled = true
    lock.unlock()

    upstreamSubscription.kill()
}
freak4pc commented 1 year ago

Mind taking a look? @danielt1263

danielt1263 commented 1 year ago

I'll have a look this weekend.

danielt1263 commented 1 year ago

Good catch @Kn1kt. I have updated the test based on your suggestion and updated the code to make the test pass while still passing the other tests. See PR #150

Kn1kt commented 1 year ago

Good job. I also made a couple of fixes that can be merged after your PR. #151

BerkeleyTrue commented 1 year ago

Just had this bite me in the rear with API requests. @danielt1263 's PR fixed my issue.

emixb commented 1 year ago

I also spent some time debugging this issue, can we merge @danielt1263's PR? Thanks!

doxuto commented 2 months ago

@freak4pc Pls check @danielt1263 's PR. Thanks

Kn1kt commented 1 month ago

Looks like this PR stuck. I have improved this operator, you can check out it here: CombineKit. Hope this helps