Skip to content

Debounce silently swallows thrown errors and stalls iteration #269

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

Closed
pocketpixels opened this issue Jun 13, 2023 · 7 comments
Closed

Debounce silently swallows thrown errors and stalls iteration #269

pocketpixels opened this issue Jun 13, 2023 · 7 comments

Comments

@pocketpixels
Copy link

I discovered while debugging an issue in my app that the async sequence returned by AsyncThrowingStream.debounce() silently swallows thrown exceptions, and does not terminate, instead just stalling iteration.

struct MyError: Error {}

let (stream, continuation) = AsyncThrowingStream.makeStream(of: Int.self)

// consume the stream
Task {
    do {
        for try await value in stream.debounce(for: .seconds(0.1)) {
            print(value)
        }
        print("Stream ended normally")
    }
    catch {
        print("Stream ended by throwing")
    }
}

// produce the values (and exception)
Task {
    for i in 1...10 {
        if i == 5 {
            continuation.finish(throwing: MyError())
        }
        else {
            continuation.yield(i)
        }
        try await Task.sleep(for: .seconds(0.3))
    }
    continuation.finish()
}

Running this in a playground (see attached project below), the iteration prints 1, 2, 3, 4 and then just stalls.
Removing the debounce, iteration ends by throwing as expected.

DebounceDebug.zip

@FranzBusch FranzBusch added the v1.0 Work leading up first API stable version label Jun 15, 2023
@FranzBusch
Copy link
Member

Just coming back to and I just wrote a test for this to see if I can reproduce it.

func test() async throws {
    struct MyError: Error {}

    let (stream, continuation) = AsyncThrowingStream.makeStream(of: Int.self)

    try await withThrowingTaskGroup(of: Void.self) { group in
        group.addTask {
            for i in 1...10 {
                if i == 5 {
                    continuation.finish(throwing: MyError())
                }
                else {
                    continuation.yield(i)
                }
                try await Task.sleep(for: .seconds(0.03))
            }
            continuation.finish()
        }

        do {
            for try await value in stream.debounce(for: .seconds(0.01)) {
                print(value)
            }
            print("Stream ended normally")
        }
        catch {
            print("Stream ended by throwing")
        }

    }
}

I wasn't able to reproduce it with this. Your attached project also doesn't contain the playground so I couldn't look at your code. Could you provide more information here?

@pocketpixels
Copy link
Author

Sorry for the missing playground file.
Please try this fixed version:
DebounceDebug.zip

Your code is different from my playground code in that you are using a TaskGroup and yielding or throwing everything simultaneously instead of sequentially.

Try the Playground in the attached project. I can still reproduce the behaviour with it in the latest Xcode beta.

@FranzBusch
Copy link
Member

I can reproduce it in the playground but I am a bit skeptical of the playground environment here. I wasn't able to repro this in a test case even with just using your code. Are you able to reproduce this in a non-playground environment?

@FranzBusch FranzBusch removed the v1.0 Work leading up first API stable version label Aug 25, 2023
@pocketpixels
Copy link
Author

pocketpixels commented Aug 25, 2023

I did encounter this issue in production code and fixed it by removing the debounce and using a different approach.

I just hacked the playground example code into the ContentView template code, and can reproduce the issue both in the Simulator and on device.

DebounceDebug.zip

@FranzBusch
Copy link
Member

Ah I think I know what the problem is and why I wasn't able to reproduce it. This was a change in behaviour in 5.8 for group.waitForAll which I have fixed in this PR: #254.

We haven't done a release since but could you try to depend on main instead of the 0.1.0 tag and test it again?

@pocketpixels
Copy link
Author

pocketpixels commented Aug 25, 2023

I just tried that and can confirm that switching to main fixes this issue.
And for me it also fixes it in the Simulator.

@FranzBusch
Copy link
Member

Thanks so much!

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

No branches or pull requests

2 participants