Bug 1754004 - Part 18: Ensure AsyncWait callbacks are cleared when NS_AsyncCopy completes, r=xpcom-reviewers,mccr8

When the NS_AsyncCopy completes, there may still be outstanding
AsyncWait callbacks which have not been invoked yet, due to two
AsyncWait callbacks being registered each time Process() yields (one to
wait for the blocked stream, and the other with WAIT_CLOSURE_ONLY to
handle errors), and only one callback being needed to resume processing.

This change ensures that any outstanding AsyncWait callbacks are
cancelled, breaking references from the streams to the
nsAStreamCallback. Some streams (such as nsPipe and DataPipe) may also
leak if there are oustanding AsyncWait callbacks, due to the need to
keep the stream alive so it can be passed into the callback, which this
helps avoid.

Previously leaks were largely avoided due to the call to `Close()`
cancelling the callbacks for us, however not all callsites specify to
close the source/sink.

This should also help avoid an unnecessary dispatch after the copy
completes due to our AsyncWait callback being invoked when the
source/sink stream is closed.

Differential Revision: https://phabricator.services.mozilla.com/D146130
This commit is contained in:
Nika Layzell 2022-05-13 14:16:16 +00:00
parent b82512de01
commit 873e958e4f
2 changed files with 22 additions and 7 deletions

View File

@ -154,6 +154,10 @@ class BlockingAsyncStream final : public nsIAsyncInputStream {
NS_IMETHOD NS_IMETHOD
AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override {
if (!aCallback) {
return NS_OK;
}
RefPtr<BlockingAsyncStream> self = this; RefPtr<BlockingAsyncStream> self = this;
nsCOMPtr<nsIInputStreamCallback> callback = aCallback; nsCOMPtr<nsIInputStreamCallback> callback = aCallback;

View File

@ -318,41 +318,52 @@ class nsAStreamCopier : public nsIInputStreamCallback,
// more source data, be sure to observe failures on output end. // more source data, be sure to observe failures on output end.
mAsyncSource->AsyncWait(this, 0, 0, nullptr); mAsyncSource->AsyncWait(this, 0, 0, nullptr);
if (mAsyncSink) if (mAsyncSink) {
mAsyncSink->AsyncWait(this, nsIAsyncOutputStream::WAIT_CLOSURE_ONLY, mAsyncSink->AsyncWait(this, nsIAsyncOutputStream::WAIT_CLOSURE_ONLY,
0, nullptr); 0, nullptr);
}
break; break;
} else if (sinkCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSink) { }
if (sinkCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSink) {
// need to wait for more room in the sink. while waiting for // need to wait for more room in the sink. while waiting for
// more room in the sink, be sure to observer failures on the // more room in the sink, be sure to observer failures on the
// input end. // input end.
mAsyncSink->AsyncWait(this, 0, 0, nullptr); mAsyncSink->AsyncWait(this, 0, 0, nullptr);
if (mAsyncSource) if (mAsyncSource) {
mAsyncSource->AsyncWait( mAsyncSource->AsyncWait(
this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, nullptr); this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, nullptr);
}
break; break;
} }
} }
if (copyFailed || canceled) { if (copyFailed || canceled) {
if (mAsyncSource) {
// cancel any previously-registered AsyncWait callbacks to avoid leaks
mAsyncSource->AsyncWait(nullptr, 0, 0, nullptr);
}
if (mCloseSource) { if (mCloseSource) {
// close source // close source
if (mAsyncSource) if (mAsyncSource) {
mAsyncSource->CloseWithStatus(canceled ? cancelStatus mAsyncSource->CloseWithStatus(canceled ? cancelStatus
: sinkCondition); : sinkCondition);
else { } else {
mSource->Close(); mSource->Close();
} }
} }
mAsyncSource = nullptr; mAsyncSource = nullptr;
mSource = nullptr; mSource = nullptr;
if (mAsyncSink) {
// cancel any previously-registered AsyncWait callbacks to avoid leaks
mAsyncSink->AsyncWait(nullptr, 0, 0, nullptr);
}
if (mCloseSink) { if (mCloseSink) {
// close sink // close sink
if (mAsyncSink) if (mAsyncSink) {
mAsyncSink->CloseWithStatus(canceled ? cancelStatus mAsyncSink->CloseWithStatus(canceled ? cancelStatus
: sourceCondition); : sourceCondition);
else { } else {
// If we have an nsISafeOutputStream, and our // If we have an nsISafeOutputStream, and our
// sourceCondition and sinkCondition are not set to a // sourceCondition and sinkCondition are not set to a
// failure state, finish writing. // failure state, finish writing.