Xamarin Public Jenkins (auto-signing) 7d7f676260 Imported Upstream version 5.16.0.100
Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
2018-08-07 15:19:03 +00:00

350 lines
9.4 KiB
C#

//
// WebOperation.cs
//
// Author:
// Martin Baulig <mabaul@microsoft.com>
//
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System.IO;
using System.Collections;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.ExceptionServices;
using System.Diagnostics;
namespace System.Net
{
class WebOperation
{
public HttpWebRequest Request {
get;
}
public WebConnection Connection {
get;
private set;
}
public ServicePoint ServicePoint {
get;
private set;
}
public BufferOffsetSize WriteBuffer {
get;
}
public bool IsNtlmChallenge {
get;
}
#if MONO_WEB_DEBUG
static int nextID;
internal readonly int ID = ++nextID;
internal string ME => $"WO({ID},{Connection?.ID ?? -1})";
#else
internal readonly int ID;
internal string ME => null;
#endif
public WebOperation (HttpWebRequest request, BufferOffsetSize writeBuffer, bool isNtlmChallenge, CancellationToken cancellationToken)
{
Request = request;
WriteBuffer = writeBuffer;
IsNtlmChallenge = isNtlmChallenge;
cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
requestTask = new WebCompletionSource<WebRequestStream> ();
requestWrittenTask = new WebCompletionSource<WebRequestStream> ();
responseTask = new WebCompletionSource<WebResponseStream> ();
finishedTask = new WebCompletionSource<(bool, WebOperation)> ();
}
CancellationTokenSource cts;
WebCompletionSource<WebRequestStream> requestTask;
WebCompletionSource<WebRequestStream> requestWrittenTask;
WebCompletionSource<WebResponseStream> responseTask;
WebCompletionSource<(bool, WebOperation)> finishedTask;
WebRequestStream writeStream;
WebResponseStream responseStream;
ExceptionDispatchInfo disposedInfo;
ExceptionDispatchInfo closedInfo;
WebOperation priorityRequest;
int requestSent;
int finished;
public bool Aborted {
get {
if (disposedInfo != null || Request.Aborted)
return true;
if (cts != null && cts.IsCancellationRequested)
return true;
return false;
}
}
public bool Closed {
get {
return Aborted || closedInfo != null;
}
}
public void Abort ()
{
var (exception, disposed) = SetDisposed (ref disposedInfo);
if (!disposed)
return;
cts?.Cancel ();
SetCanceled ();
Close ();
}
public void Close ()
{
var (exception, closed) = SetDisposed (ref closedInfo);
if (!closed)
return;
var stream = Interlocked.Exchange (ref writeStream, null);
if (stream != null) {
try {
stream.Close ();
} catch { }
}
}
void SetCanceled ()
{
WebConnection.Debug ($"{ME} SET CANCELED");
var error = new OperationCanceledException ();
requestTask.TrySetCanceled (error);
requestWrittenTask.TrySetCanceled (error);
responseTask.TrySetCanceled (error);
Finish (false, error);
}
void SetError (Exception error)
{
WebConnection.Debug ($"{ME} SET ERROR: error={error.GetType ().Name}");
requestTask.TrySetException (error);
requestWrittenTask.TrySetException (error);
responseTask.TrySetException (error);
Finish (false, error);
}
(ExceptionDispatchInfo, bool) SetDisposed (ref ExceptionDispatchInfo field)
{
var wexc = new WebException (SR.GetString (SR.net_webstatus_RequestCanceled), WebExceptionStatus.RequestCanceled);
var exception = ExceptionDispatchInfo.Capture (wexc);
var old = Interlocked.CompareExchange (ref field, exception, null);
return (old ?? exception, old == null);
}
internal ExceptionDispatchInfo CheckDisposed (CancellationToken cancellationToken)
{
if (Aborted || cancellationToken.IsCancellationRequested)
return CheckThrowDisposed (false, ref disposedInfo);
return null;
}
internal void ThrowIfDisposed ()
{
ThrowIfDisposed (CancellationToken.None);
}
internal void ThrowIfDisposed (CancellationToken cancellationToken)
{
if (Aborted || cancellationToken.IsCancellationRequested)
CheckThrowDisposed (true, ref disposedInfo);
}
internal void ThrowIfClosedOrDisposed ()
{
ThrowIfClosedOrDisposed (CancellationToken.None);
}
internal void ThrowIfClosedOrDisposed (CancellationToken cancellationToken)
{
if (Closed || cancellationToken.IsCancellationRequested)
CheckThrowDisposed (true, ref closedInfo);
}
ExceptionDispatchInfo CheckThrowDisposed (bool throwIt, ref ExceptionDispatchInfo field)
{
var (exception, disposed) = SetDisposed (ref field);
if (disposed)
cts?.Cancel ();
if (throwIt)
exception.Throw ();
return exception;
}
internal void RegisterRequest (ServicePoint servicePoint, WebConnection connection)
{
if (servicePoint == null)
throw new ArgumentNullException (nameof (servicePoint));
if (connection == null)
throw new ArgumentNullException (nameof (connection));
lock (this) {
if (Interlocked.CompareExchange (ref requestSent, 1, 0) != 0)
throw new InvalidOperationException ("Invalid nested call.");
ServicePoint = servicePoint;
Connection = connection;
}
cts.Token.Register (() => {
Request.FinishedReading = true;
SetDisposed (ref disposedInfo);
});
}
public void SetPriorityRequest (WebOperation operation)
{
lock (this) {
if (requestSent != 1 || ServicePoint == null || finished != 0)
throw new InvalidOperationException ("Should never happen.");
if (Interlocked.CompareExchange (ref priorityRequest, operation, null) != null)
throw new InvalidOperationException ("Invalid nested request.");
}
}
public async Task<Stream> GetRequestStream ()
{
return await requestTask.WaitForCompletion ().ConfigureAwait (false);
}
internal Task<WebRequestStream> GetRequestStreamInternal ()
{
return requestTask.WaitForCompletion ();
}
public Task WaitUntilRequestWritten ()
{
return requestWrittenTask.WaitForCompletion ();
}
public WebRequestStream WriteStream {
get {
ThrowIfDisposed ();
return writeStream;
}
}
public Task<WebResponseStream> GetResponseStream ()
{
return responseTask.WaitForCompletion ();
}
internal WebCompletionSource<(bool, WebOperation)> Finished => finishedTask;
internal async void Run ()
{
try {
WebConnection.Debug ($"{ME} RUN");
ThrowIfClosedOrDisposed ();
var requestStream = await Connection.InitConnection (this, cts.Token).ConfigureAwait (false);
ThrowIfClosedOrDisposed ();
writeStream = requestStream;
await requestStream.Initialize (cts.Token).ConfigureAwait (false);
ThrowIfClosedOrDisposed ();
requestTask.TrySetCompleted (requestStream);
var stream = new WebResponseStream (requestStream);
responseStream = stream;
await stream.InitReadAsync (cts.Token).ConfigureAwait (false);
WebConnection.Debug ($"{ME} RUN COMPLETE");
responseTask.TrySetCompleted (stream);
} catch (OperationCanceledException) {
WebConnection.Debug ($"{ME} RUN CANCELED");
SetCanceled ();
} catch (Exception e) {
WebConnection.Debug ($"{ME} RUN ERROR: {e.GetType ().Name}");
SetError (e);
}
}
internal void CompleteRequestWritten (WebRequestStream stream, Exception error = null)
{
WebConnection.Debug ($"{ME} COMPLETE REQUEST WRITTEN: {error != null}");
if (error != null)
SetError (error);
else
requestWrittenTask.TrySetCompleted (stream);
}
internal void Finish (bool ok, Exception error = null)
{
if (Interlocked.CompareExchange (ref finished, 1, 0) != 0)
return;
#if MONO_WEB_DEBUG
var me = $"{ME} FINISH";
#else
string me = null;
#endif
WebConnection.Debug ($"{me}: ok={ok} error={error?.GetType ()}");
WebResponseStream stream;
WebOperation next;
lock (this) {
stream = Interlocked.Exchange (ref responseStream, null);
next = Interlocked.Exchange (ref priorityRequest, null);
Request.FinishedReading = true;
}
if (error != null) {
if (next != null)
next.SetError (error);
WebConnection.Debug ($"{me} SET ERROR: {error.GetType ().Name}");
finishedTask.TrySetException (error);
return;
}
WebConnection.Debug ($"{me}: ok={ok} stream={stream != null} next={next != null}");
var keepAlive = !Aborted && ok && (stream?.KeepAlive ?? false);
if (next != null && next.Aborted) {
next = null;
keepAlive = false;
}
finishedTask.TrySetCompleted ((keepAlive, next));
WebConnection.Debug ($"{me} DONE: {keepAlive} next={next?.ID}");
}
}
}