I know, and I even sometimes implement my own iterators this way when it's convenient to me. But this is a core library function, and it feels a bit heavy-handed for the library to be forcing this upon me.
Right - this came up during the review of typed throws as an example of why sum types are needed, so that it could be declared throws(Failure | CancellationError)
.
I don't think this is AsyncThrowingStream
's problem, if it happens - that's coder-error. It's no different to any other situation where cancellation isn't correctly handled (or isn't handled at all).
In a nutshell, I'm downloading files. As a stream of byte buffers. It's important to distinguish between "this is actually the whole file" and "this is not the whole file", because it may even be possible to parse the data in either case and not get any apparent errors. e.g. NSImage
is super forgiving about corrupt / truncated files - it'll just render the missing part of the image as opaque grey - which is great, I suppose, if you're trying to model classical web browser behaviour, but it's really super frustrating when you actually need the whole, correct image for your program to function.
Strictly-speaking I just need it to throw on cancellation - it doesn't particularly matter what it throws. But it would be nice if it lets me throw, because then I can let URLSession
produce the error and it fits neatly into my existing error handling (since there's plenty of other cases I have to handle anyway, and they're all wrapped up in Cocoa error types - it'd be nice to not have heterogeneity in how I extract the actual error reason).
Incidentally, I've also realise that in this specific case another workaround is to not actually cancel the Task
, but instead to expose the underlying URLSession
task and cancel that instead. Arguably that's better than my prior workaround, but it's still a pretty gross - and unreliable - abstraction violation (now instead of returning just the AsyncThrowingStream
from my download
function, I'll have to return a tuple with the implementation detail URLSession
task in it too, and cross my fingers the caller never screws up / forgets about this contract and cancels the Task
instead).
Is that the overriding goal, though?
Cancelling promptly is of course always a goal, because it's nice to be responsive and to not waste resources. But, clean cancellation is surely more important, right? Having library code just abruptly - and silently - kill itself in the middle of your data flow graph is surprising and hampers clean cancellation, and clean-up.
In any case, the crux of the problem isn't really about how much 'initiative' AsyncThrowingStream
shows re. cancellation, it's that it just silently ends the iteration, rather than actually signalling the cancellation. I wouldn't be that bothered, as noted earlier, if AsyncThrowingStream
did just immediately throw an error. That it's not even letting me throw an error, that's a problem.