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

fix(ext/web): Prevent (De-)CompressionStream resource leak on stream cancellation #21199

Merged

Conversation

egfx-notifications
Copy link
Contributor

Based on #21074 and #20741 I was looking for further potential use cases of TransformStream cancel() method, so here go CompressionStream and DecompressionStream.

Fixes #14212

@egfx-notifications egfx-notifications force-pushed the fix/compressionstream-resource-leak branch 2 times, most recently from 0d20541 to a7fd8f9 Compare November 14, 2023 13:11
Comment on lines 492 to 495
await new DecompressionStream("gzip").writable.getWriter().abort();
},
TypeError,
"corrupt gzip stream does not have a matching checksum",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should throw on cancellation. This probably means we need a separate Rust op (or a second parameter) to just throw away the results.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually started work on a separate Rust op, but then decided that we should probably give the user the chance to handle this case as they see fit. Thinking of intentional abortion on the user side where the user wants to continue work with the results received so far, it would be good to let the user know if they need to perform some manual cleanup on the last bytes received or can use the slice as is. This being said, I don't know the specifics of gzip, deflate and deflate-raw enough to say if a decompressed slice is even usable, this being implemented as a stream I suppose it is. If it is, I'd at least give the user the option to request error bubbling, maybe with an options object to the CompressionStream and DecompressionStream constructors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure here... I might have to hand this one off to someone with a better intuition.

Cancellation on a TextDecoderStream in Firefox appears to reject the read with the cancellation rather than throwing from the writer abort:

(async () => { let td = new TextDecoderStream(); td.writable.abort("hmm"); await td.readable.getReader().read() })()
Promise { <state>: "rejected", <reason>: "hmm" }

I wonder if this is the right way to do things here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should follow browser behaviour here (.cancel() should not throw)

cancel() should just close() the underlying resource I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I pushed the necessary changes.

@@ -88,6 +91,9 @@ class DecompressionStream {
const output = ops.op_compression_finish(rid);
maybeEnqueue(controller, output);
},
cancel: (_reason) => {
ops.op_compression_finish(rid);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be ops.op_compression_finish(rid, false), and we could use ops.op_compression_finish(rid, true) for the success case right above.

@egfx-notifications egfx-notifications force-pushed the fix/compressionstream-resource-leak branch 2 times, most recently from 5a017c4 to 9c096c2 Compare November 30, 2023 12:31
@egfx-notifications
Copy link
Contributor Author

Just added two more tests to verify that my changes did not break flush() for both successful and erroneous close() of the stream.

@mmastrac mmastrac force-pushed the fix/compressionstream-resource-leak branch from 9c096c2 to cd53e0b Compare February 13, 2024 21:04
Copy link
Member

@mmastrac mmastrac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I rebased it on the latest main.

@mmastrac mmastrac enabled auto-merge (squash) February 13, 2024 21:07
@mmastrac mmastrac force-pushed the fix/compressionstream-resource-leak branch from cd53e0b to 87ac9b7 Compare February 13, 2024 21:18
@mmastrac mmastrac merged commit 082f812 into denoland:main Feb 13, 2024
17 checks passed
littledivy pushed a commit that referenced this pull request Feb 15, 2024
…cancellation (#21199)

Based on #21074 and #20741 I was looking for further potential use cases
of `TransformStream` `cancel()` method, so here go `CompressionStream`
and `DecompressionStream`.

Fixes #14212
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.

Aborting a CompressionStream/DecompressionStream leaks a resource
3 participants