Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Fix assert and leak of WinHttpRequestState object#4001

Merged
stephentoub merged 1 commit into
dotnet:masterfrom
davidsh:async_crash
Oct 21, 2015
Merged

Fix assert and leak of WinHttpRequestState object#4001
stephentoub merged 1 commit into
dotnet:masterfrom
davidsh:async_crash

Conversation

@davidsh
Copy link
Copy Markdown
Contributor

@davidsh davidsh commented Oct 20, 2015

Discovered during testing with DEBUG version of CoreCLR running internal ToF tests. The design of the WinHttpRequestState is such that it stays alive due to a GCHandle.Alloc(this) in its constructor. This keeps the object alive during any of the native WinHTTP callbacks. However, the object was being prematurely disposed during the Dispose() of the WinHttpResponseStream. Instead, it should have been explicitly disposed during the final HANDLE_CLOSING callback from WinHTTP when the request handle was disposed. The early disposing of the WinHttpRequestState object was doing a GCHandle.Free() of the state object. This later caused an Assert() in the DEBUG version of the CoreCLR when doing GCHandle.FromIntPtr() indicating that the handle was already deallocated.

@davidsh
Copy link
Copy Markdown
Contributor Author

davidsh commented Oct 20, 2015

@CIPop @stephentoub PTAL

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you pass any args to Trace, the compiler is going to allocate an array to hold them even if !IsTraceEnabled. For now you could add a [Conditional("DEBUG")] to these Trace methods, as they're just calling Debug.WriteLine if enabled and so they're only useful if DEBUG is set. Longer term, I know you're planning on converting the tracing to using an EventSource, at which point you'd want some other solution to ensure that such allocations don't happen if tracing isn't enabled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't realize that using params object[] would cause that extra allocation. I think for now I'll just revise the method to have explicit arguments. That is what methods like string.Format do for common overload signatures in addition to the params object[] argument.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think for now I'll just revise the method to have explicit arguments

Sounds good (noting of course that if you make those separate args typed as object, you'll get boxing for any value types you pass).

I didn't realize that using params object[] would cause that extra allocation

Yeah, the compiler needs to allocate the object[] in which to pass the values. Roslyn has an optimization that'll let it use Array.Empty<object>() instead of allocating an array, but that only applies for the case where you don't pass any args.

@stephentoub
Copy link
Copy Markdown
Member

One perf comment, otherwise LGTM.

@stephentoub
Copy link
Copy Markdown
Member

@davidsh, the PR job at:
http://dotnet-ci.cloudapp.net/job/dotnet_corefx_windows_release_prtest/5381/console
failed due to this unhandled exception:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Object[]' to type 'System.Net.Http.WinHttpRequestState'.
     at System.Net.Http.WinHttpRequestState.FromIntPtr(IntPtr gcHandle)
     at System.Net.Http.WinHttpRequestCallback.WinHttpCallback(IntPtr handle, IntPtr context, UInt32 internetStatus, IntPtr statusInformation, UInt32 statusInformationLength)

From your description of the problem in this PR, seems like this could be the cause of this exception, right? i.e. we disposed of the GCHandle too early, it got recycled, and the handle that was for a WinHttpRequestState is now for an object[]?

@davidsh
Copy link
Copy Markdown
Contributor Author

davidsh commented Oct 21, 2015

@stephentoub yes, I do think that the bug I'm fixing is the cause of the unhandled exception from that other PR. I'll try to get my PR merged in soon in the morning.

@stephentoub
Copy link
Copy Markdown
Member

yes, I do think

Great, one less failure to worry about 😄 Thanks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I alluded to this in my previous response before seeing the revamped change, so just commenting on it here again in case the previous comment didn't make sense. Now the caller that's passing in an IntPtr, a Boolean, and a Boolean is going to be allocating three objects when calling Trace, one for each of the boxed value type arguments, even if IsTraceEnabled is false. You might just want to guard the call site with a check for IsTraceEnabled, rather than mucking with the Trace signature, e.g.

if (WinHttpTraceHelper.IsTraceEnabled())
{
    WinHttpTraceHelper.Trace(
        "WinHttpRequestState.Dispose, GCHandle=0x{0:X}, disposed={1}, disposing={2}",
        ToIntPtr(),
        _disposed,
        disposing);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good points. I'll wrap the call site for now. Although the wrapping code doesn't look as good, it will serve as a reminder to keep perf good when I'm converting this to the final tracing logic

…onse stream

Discovered during testing with DEBUG version of CoreCLR running internal ToF tests. The design of the WinHttpRequestState is such that it stays alive due to a GCHandle.Alloc(this) in its constructor. This keeps the object alive during any of the native WinHTTP callbacks. However, the object was being prematurely disposed during the Dispose() of the WinHttpResponseStream. Instead, it should have been explicitly disposed during the final HANDLE_CLOSING callback from WinHTTP when the request handle was disposed. The early disposing of the WinHttpRequestState object was doing a GCHandle.Free() of the state object. This later caused an Assert() in the DEBUG version of the CoreCLR when doing GCHandle.FromIntPtr() indicating that the handle was already deallocated.
@stephentoub
Copy link
Copy Markdown
Member

@dotnet-bot test this please

stephentoub added a commit that referenced this pull request Oct 21, 2015
Fix assert and leak of WinHttpRequestState object
@stephentoub stephentoub merged commit ceb1623 into dotnet:master Oct 21, 2015
@davidsh davidsh deleted the async_crash branch October 21, 2015 19:46
@karelz karelz modified the milestone: 1.0.0-rtm Dec 3, 2016
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Fix assert and leak of WinHttpRequestState object

Commit migrated from dotnet/corefx@ceb1623
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants