Skip to content

Non-catchable ObjectDisposedException when using NSUrlSessionHandler #20123

Closed

Description

@tipa

I am seeing a crash in one of my app when doing web requests and using an NSUrlSessionHandler.

var httpClient = new HttpClient(new NSUrlSessionHandler())

I have noticed this crash being reported for months already, but with the last update I have added a button to the apps UI that allows users to cancel web requests (using CancellationToken) and it appears like as if this change has increased the occurence of the exception even more. I have all my web requests in a try-catch clause, however this exception crashes my app nevertheless.

ObjectDisposed_StreamClosed (System.ObjectDisposedException)
   at System.ThrowHelper.ThrowObjectDisposedException_StreamClosed(String) + 0x3c
   at System.IO.MemoryStream.Read(Byte[], Int32, Int32) + 0x124
   at System.Net.Http.MultipartContent.ContentReadStream.Read(Byte[], Int32, Int32) + 0x78
   at System.Net.Http.NSUrlSessionHandler.WrappedNSInputStream.Read(IntPtr buffer, UIntPtr len) + 0x58
   at MyApp!<BaseAddress>+0x7082f8

According to Sentry, the crash happens in the com.apple.NSURLConnectionLoader thread (and that's also likely the reason I am unable to catch the exception)

Environment

.NET 8, NativeAot-compiled
iOS 17.3.1

Activity

  1. rolfbjarne commented on Feb 16, 2024

    @rolfbjarne
    Member

    So there are two problems here:

    1. The exception itself, an ObjectDisposedException doesn't seem quite right, it's an indication something else is wrong. This might be related to App crash due to NullReferenceException in NSUrlSessionHandlerDelegate.SetResponse #9132 somehow.
    2. The fact that the exception crashes the process. This shouldn't happen.

    I'll look into this to see what I can do to at the very least not make the process crash.

  2. added
    bugIf an issue is a bug or a pull request a bug fix
    on Feb 16, 2024
  3. added this to the Future milestone on Feb 16, 2024
  4. self-assigned this
    on Feb 16, 2024
  5. rolfbjarne commented on Feb 21, 2024

    @rolfbjarne
    Member

    I've merged an attempt at fixing the crash itself (we'll now return -1 from the Read method and set the Error property on the NSInputStream when Read throws an exception). I can't reproduce the problem, so I can't say what will happen if this scenario occurs, but it will probably not be worse than crashing the process. Hopefully the error will bubble up and show up in a place where it can be handled/reported properly.

    The fix will be released with our release matching Xcode 15.3.

  6. removed their assignment
    on Feb 21, 2024
  7. tipa commented on Feb 21, 2024

    @tipa
    Author

    Thank you!
    Looking again at the stack trace, I might have an idea what was going wrong.
    In my app, I am sending a MultipartContent in an Http request. If that request fails (e.g. because there's no internet connection), I am retrying the request. When doing that, I did not create a new MultipartContent, but instead used the previous one - which had already been disposed (normal HttpClient behavior). In the second request, the NSUrlSession then attempts to read from the disposed content stream and throws an ObjectDisoposedException.

    However, I just tried to reproduced that crash in a Debug build and it produces a catchable exception (without any mention of WrappedNSInputStream) with the following stack trace:

    System.ObjectDisposedException: Cannot access a disposed object.
    System.Net.Http.MultipartContent'.
       at System.Net.Http.HttpContent.CheckDisposed()
       at System.Net.Http.HttpContent.ReadAsStreamAsync(CancellationToken cancellationToken)
       at System.Net.Http.HttpContent.ReadAsStreamAsync()
       at System.Net.Http.NSUrlSessionHandler.CreateRequest(HttpRequestMessage request) in /Users/builder/azdo/_work/1/s/xamarin-macios/src/Foundation/NSUrlSessionHandler.cs:line 481
       at System.Net.Http.NSUrlSessionHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/builder/azdo/_work/1/s/xamarin-macios/src/Foundation/NSUrlSessionHandler.cs:line 511
       at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
    

    Maybe the http requests & threading behave differently in Debug and Release (with NativeAot)?
    Or some timing difference does not have it crash here, but instead returns the MemoryStream that is handed over to WrappedNSInputStream here but then disposed shortly after. This might happen when sending two http requests with the same HttpContent concurrently

  8. rolfbjarne commented on Feb 21, 2024

    @rolfbjarne
    Member

    When doing that, I did not create a new MultipartContent, but instead used the previous one - which had already been disposed (normal HttpClient behavior).

    Looks like that changed in HttpClient:

    dotnet/corefx#19082

    https://github.com/dotnet/runtime/blob/8fb9f4b9fadb8060a7cf5cb991d526a669a2f03c/src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs#L665-L680

    So I'm not sure what's exactly disposing the MultipartContent.

    It's still good that you were able to reproduce something similar: would you be able to provide a test project I can use to replicate this?

    Maybe the http requests & threading behave differently in Debug and Release (with NativeAor)?

    I think it's more likely just a timing issue, depending on exactly when the network goes down, and potentially also when the GC runs.

  9. tipa commented on Feb 21, 2024

    @tipa
    Author

    In this project you see how I retry my http requests and how to reproduce the exception: http_test.zip

    The crash happens in this case because I manually dispose the HttpRequestMessage using the using keyword.
    I edited my comment above after you've quoted it with a possible explanation of how the crash could happen in WrappedNSInputStream.Read instead of in HttpContent.ReadAsStreamAsync

    Edit: I tried refactoring the logic that it uses a while-loop to retry requests using the same HttpRequestMessage without disposing it instead of calling SendRequest recusrively, but that doesn't work as that will throw:

    System.InvalidOperationException: 'The request message was already sent. Cannot send the same request message multiple times.'
    
       at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
       at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
       at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    
  10. rolfbjarne commented on Mar 7, 2024

    @rolfbjarne
    Member

    In this project you see how I retry my http requests and how to reproduce the exception: http_test.zip

    The crash happens in this case because I manually dispose the HttpRequestMessage using the using keyword. I edited my comment above after you've quoted it with a possible explanation of how the crash could happen in WrappedNSInputStream.Read instead of in HttpContent.ReadAsStreamAsync

    Edit: I tried refactoring the logic that it uses a while-loop to retry requests using the same HttpRequestMessage without disposing it instead of calling SendRequest recusrively, but that doesn't work as that will throw:

    System.InvalidOperationException: 'The request message was already sent. Cannot send the same request message multiple times.'
    
       at System.Net.Http.HttpClient.CheckRequestMessage(HttpRequestMessage request)
       at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
       at System.Net.Http.HttpClient.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    

    I tried to stress test this by:

    • Using the httpbin.org/delay end point to delay response for 10 seconds
    • Post a lot of data (100MB) to this end point
    • Cancel at a random moment during those 10 seconds (using a cancellation token)
    • Do this 100 times in parallel.

    Source code: https://gist.github.com/rolfbjarne/e415f1bc4ca9b98964041c28a37ad311

    And there were no exceptions on a background thread, only the expected TaskCanceledException.

    So I'm not sure what to do here without a test project I can use to reproduce the ObjectDisposedException.

  11. added
    need-reproWaiting for a test case before the bug can be investigated
    on Mar 7, 2024
  12. tipa commented on Mar 7, 2024

    @tipa
    Author

    Since there have been two fixes to the NSUrlSessionHandler in the meantime (#20131 & #16341), it's possible that the crash I observed would no longer occur. Once those PRs have been released (perhaps together with Xcode 15.3 support) as well as this one on the Android side, I will refactor my HTTP retry-logic to use the same HttpContent for multiple HttpRequestMessages - and if I continue to see some HTTP-related crash, I can open a new issue

  13. added
    need-attentionAn issue requires our attention/response
    and removed
    need-reproWaiting for a test case before the bug can be investigated
    on Mar 7, 2024
  14. added
    need-infoWaiting for more information before the bug can be investigated
    and removed
    need-attentionAn issue requires our attention/response
    on Mar 8, 2024
  15. tipa commented on Mar 11, 2024

    @tipa
    Author

    @rolfbjarne I was able to create a test project with which I was able to reproduce this possibly related crash 4 times within just a couple of minutes: #11799 (comment)
    It's a long standing bug that was reported by dozens of users and it would be amazing if it could also be fixed

  16. added
    need-attentionAn issue requires our attention/response
    and removed
    need-infoWaiting for more information before the bug can be investigated
    on Mar 11, 2024
  17. rolfbjarne commented on Mar 12, 2024

    @rolfbjarne
    Member

    I can reproduce a crash with your sample; I'll have a look.

  18. added
    networkingIf an issue or pull request is related to networking
    on Mar 19, 2024
  19. tipa commented on Apr 9, 2025

    @tipa
    Author

    I haven't seen this issue show up in my crash reports any more after it has been seemingly fixed here, I think it can be closed...?

  20. rolfbjarne commented on Apr 9, 2025

    @rolfbjarne
    Member

    That's great! Thanks for following up, I'll close this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIf an issue is a bug or a pull request a bug fixnetworkingIf an issue or pull request is related to networking

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions