[Mono.Android] Prevent ObjectDisposedException while reading HTTP response from InputStreamInvoker #9789
+10
−0
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #9039
TODO:
The issue manifests in the following code snippet, where we're downloading a large file from the server:
My best attempt at explaining the bug is the following:
the
AndroidMessageHandler
returns an instance ofAndroidHttpResponseMessage
which keeps a reference to theHttpURLConnection
MCW which internally keeps a reference to theInputStream
(wrapped byAndroid.Runtime.InputStreamInvoker
)after line
stream = await response.Content.ReadAsStreamAsync(cancellationToken)
...stream
is referencing the response'sInputStreamInvoker
which keeps a reference to theInputStream
response
can be collected by GCwhen the download is long enough for several .NET and Java GCs to occur, this happens:
response
is collected on the .NET sideHttpURLConnection
is collected on the Java sidewhen the next .NET GC runs, our Java GC bridge will change the strong gref
Handle
inInputStream
to a weak ref and initiate Java GCJava GC will collect the
InputStream
since there are no more references to it from the Java sidethe GC bridge will test if the weak ref is still valid after the Java GC and it notices it isn't, so it replaces the handle with
IntPtr.Zero
the .NET
InputStream
object won't be collected though, becauseInputStreamInvoker
is still holding a reference to itthe next time we try to read from the
InputStreamInvoker
, it's internalInputStream
is disposed and reading from it will throw theObjectDisposedException
we are observingThe problem can be prevented by keeping a gref to the underlying
InputStream
inInputStreamInvoker
. This way, the gc bridge cannot dispose theInputStream
while the only reference to it is on the .NET side in an object which isn't a Java class wrapper. Without this patch, the app will fail every single time when using a local debug build. With the patch applied, the exception is not thrown anymore./cc @grendello @jonathanpeppers @jonpryor @AaronRobinsonMSFT