< Summary

Class:Microsoft.Azure.Batch.Protocol.BatchRequest`3
Assembly:Microsoft.Azure.Batch
File(s):C:\Git\azure-sdk-for-net\sdk\batch\Microsoft.Azure.Batch\src\Protocol\BatchRequest.cs
Covered lines:7
Uncovered lines:0
Coverable lines:7
Total lines:371
Line coverage:100% (7 of 7)
Covered branches:0
Total branches:0

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
get_Parameters()-100%100%
set_Parameters(...)-100%100%
.ctor(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\batch\Microsoft.Azure.Batch\src\Protocol\BatchRequest.cs

#LineLine coverage
 1namespace Microsoft.Azure.Batch.Protocol
 2{
 3    using System;
 4    using System.Collections.Generic;
 5    using System.Diagnostics;
 6    using System.Runtime.ExceptionServices;
 7    using System.Threading;
 8    using System.Threading.Tasks;
 9    using Common;
 10    using Microsoft.Rest.Azure;
 11    using Models;
 12
 13    /// <summary>
 14    /// A base class for all Batch service requests. Represents the information required to make a particular call with 
 15    /// </summary>
 16    /// <typeparam name="TOptions">The type of the parameters passed outside the request body associated with the reques
 17    /// <typeparam name="TResponse">The response type expected from the request.</typeparam>
 18    public abstract class BatchRequestBase<TOptions, TResponse> : IBatchRequest<TResponse>
 19        where TOptions : IOptions, new()
 20        where TResponse : IAzureOperationResponse
 21    {
 22        private volatile bool hasRequestExecutionStarted;
 23        private Func<CancellationToken, Task<TResponse>> serviceRequestFunc;
 24        private TOptions options;
 25        private IRetryPolicy retryPolicy;
 26        private TimeSpan timeout;
 27        private CancellationToken cancellationToken;
 28        private ClientRequestIdProvider clientRequestIdProvider;
 29
 30        /// <summary>
 31        /// Gets the headers used for the request.
 32        /// </summary>
 33        public Dictionary<string, List<string>> CustomHeaders { get; private set; }
 34
 35        /// <summary>
 36        /// Gets or sets the function which will create a <see cref="Task"/> calling the Batch service.
 37        /// </summary>
 38        public Func<CancellationToken, Task<TResponse>> ServiceRequestFunc
 39        {
 40            get { return this.serviceRequestFunc; }
 41            set
 42            {
 43                this.ThrowIfRequestExecutionHasStarted();
 44                this.serviceRequestFunc = value;
 45            }
 46        }
 47
 48        /// <summary>
 49        /// Gets or sets the options used for the request.
 50        /// </summary>
 51        public TOptions Options
 52        {
 53            get { return this.options; }
 54            set
 55            {
 56                this.ThrowIfRequestExecutionHasStarted();
 57                this.options = value;
 58            }
 59        }
 60
 61        /// <summary>
 62        /// Gets the options needed by the REST proxy for the current request.
 63        /// </summary>
 64        IOptions IBatchRequest.Options { get { return Options; } }
 65
 66        /// <summary>
 67        /// Gets the REST client that will be used for this request.
 68        /// </summary>
 69        public Protocol.BatchServiceClient RestClient { get; private set; }
 70
 71        /// <summary>
 72        /// Gets or sets the retry policy to be applied.
 73        /// Null means no retries will be attempted.
 74        /// </summary>
 75        public IRetryPolicy RetryPolicy
 76        {
 77            get { return this.retryPolicy; }
 78            set
 79            {
 80                this.ThrowIfRequestExecutionHasStarted();
 81                this.retryPolicy = value;
 82            }
 83        }
 84
 85        /// <summary>
 86        /// Gets the operation context associated with this <see cref="IBatchRequest"/>.
 87        /// </summary>
 88        public OperationContext OperationContext { get; private set; }
 89
 90        /// <summary>
 91        /// Gets or sets the client side timeout for a request to the Batch service.
 92        /// </summary>
 93        /// <remarks>
 94        /// <para>
 95        /// This timeout applies to a single Batch service request; if a retry policy is specified, then each retry will
 96        /// full duration of this value.
 97        /// </para>
 98        /// </remarks>
 99        public TimeSpan Timeout
 100        {
 101            get { return this.timeout; }
 102            set
 103            {
 104                this.ThrowIfRequestExecutionHasStarted();
 105                this.timeout = value;
 106            }
 107        }
 108
 109        /// <summary>
 110        /// Gets or sets the <see cref="CancellationToken"/> associated with this <see cref="IBatchRequest"/>.
 111        /// </summary>
 112        /// <remarks>
 113        /// <para>
 114        /// Cancelling this token will cancel the currently ongoing request. This applies to the initial request as well
 115        /// as any subsequent requests created due to <see cref="RetryPolicy"/>. Cancelling this token also forbids all
 116        /// future retries of this <see cref="IBatchRequest"/>.
 117        /// </para>
 118        /// </remarks>
 119        public CancellationToken CancellationToken
 120        {
 121            get { return this.cancellationToken; }
 122            set
 123            {
 124                this.ThrowIfRequestExecutionHasStarted();
 125                this.cancellationToken = value;
 126            }
 127        }
 128
 129        /// <summary>
 130        /// Gets or sets the <see cref="ClientRequestIdProvider"/> used by this request to generate client request ids.
 131        /// </summary>
 132        public ClientRequestIdProvider ClientRequestIdProvider
 133        {
 134            get { return this.clientRequestIdProvider; }
 135            set
 136            {
 137                this.ThrowIfRequestExecutionHasStarted();
 138                this.clientRequestIdProvider = value;
 139            }
 140        }
 141
 142        /// <summary>
 143        /// Initializes a new instance of the <see cref="BatchRequestBase{TOptions,TResponse}"/> class.
 144        /// </summary>
 145        /// <param name="restClient">The REST client to use.</param>
 146        /// <param name="cancellationToken">The cancellationToken to use.</param>
 147        protected BatchRequestBase(Protocol.BatchServiceClient restClient, CancellationToken cancellationToken)
 148        {
 149            //Construct a default set of options
 150            this.Options = new TOptions();
 151            this.OperationContext = new OperationContext();
 152            this.RestClient = restClient;
 153            this.Timeout = Constants.DefaultSingleRestRequestClientTimeout;
 154            this.CancellationToken = cancellationToken;
 155            this.CustomHeaders = new Dictionary<string, List<string>>();
 156            this.hasRequestExecutionStarted = false;
 157        }
 158
 159        /// <summary>
 160        /// Executes the request.
 161        /// </summary>
 162        /// <returns>An asynchronous operation of return type <typeparamref name="TResponse"/>.</returns>
 163        public async Task<TResponse> ExecuteRequestAsync()
 164        {
 165            this.hasRequestExecutionStarted = true;
 166
 167            bool shouldRetry;
 168            do
 169            {
 170                shouldRetry = false; //Start every request execution assuming there will be no retry
 171                Task<TResponse> serviceRequestTask = null;
 172                Exception capturedException;
 173
 174                //Participate in cooperative cancellation
 175                this.CancellationToken.ThrowIfCancellationRequested();
 176
 177                try
 178                {
 179                    this.ApplyClientRequestIdBehaviorToParams();
 180
 181                    serviceRequestTask = this.ExecuteRequestWithCancellationAsync(this.ServiceRequestFunc);
 182
 183                    TResponse response = await serviceRequestTask.ConfigureAwait(continueOnCapturedContext: false);
 184
 185                    //TODO: It would be nice if we could add to OperationContext.RequestResults here
 186                    return response;
 187                }
 188                catch (Exception e)
 189                {
 190                    //If the caught exception was a BatchErrorException, we wrap it in the object model exception type
 191                    BatchException batchException = null;
 192                    if (e is BatchErrorException)
 193                    {
 194                        batchException = new BatchException(e as BatchErrorException);
 195                    }
 196
 197                    Exception wrappedException = batchException ?? e;
 198
 199                    if (this.RetryPolicy != null &&
 200                        //If cancellation is requested at this point, just skip calling the retry policy and throw
 201                        //This is the most honest thing to do since we will not be issuing another request on the users 
 202                        //(since the cancellation token has already been set) and thus calling their retry policy would 
 203                        //be confusing.
 204                        !this.CancellationToken.IsCancellationRequested)
 205                    {
 206                        RequestInformation requestInformation;
 207
 208                        if (batchException != null)
 209                        {
 210                            //If there is a BatchException, extract the RequestInformation and capture it
 211                            requestInformation = batchException.RequestInformation;
 212                        }
 213                        else
 214                        {
 215                            requestInformation = new RequestInformation()
 216                            {
 217                                ClientRequestId = this.Options.ClientRequestId
 218                            };
 219                        }
 220
 221                        this.OperationContext.RequestResults.Add(new RequestResult(requestInformation, wrappedException)
 222                        capturedException = wrappedException;
 223                    }
 224                    else
 225                    {
 226                        if (batchException != null)
 227                        {
 228                            throw batchException; //Just forward the wrapped exception if there was one
 229                        }
 230                        else
 231                        {
 232                            throw;
 233                        }
 234                    }
 235                }
 236
 237                if (capturedException != null)
 238                {
 239                    //On an exception, invoke the retry policy
 240                    RetryDecision retryDecision = await this.RetryPolicy.ShouldRetryAsync(capturedException, this.Operat
 241                    shouldRetry = retryDecision.ShouldRetry;
 242
 243                    if (!shouldRetry)
 244                    {
 245                        //Rethrow the exception and explicitly preserve the stack trace
 246                        ExceptionDispatchInfo.Capture(capturedException).Throw();
 247                    }
 248                    else
 249                    {
 250                        if (retryDecision.RetryDelay.HasValue)
 251                        {
 252                            await Task.Delay(retryDecision.RetryDelay.Value).ConfigureAwait(continueOnCapturedContext: f
 253                        }
 254                        else
 255                        {
 256                            Debug.Assert(false, "RetryDecision.ShouldRetry = true but RetryDelay has no value");
 257                        }
 258                    }
 259                }
 260
 261            } while (shouldRetry);
 262
 263            //Reaching here is a bug, by now the request should have either thrown or returned
 264            const string errorMessage = "Exited ExecuteRequestAsync without throwing or returning";
 265            Debug.Assert(false, errorMessage);
 266            throw new InvalidOperationException(errorMessage);
 267        }
 268
 269        /// <summary>
 270        /// Throws an exception if request execution has started.
 271        /// </summary>
 272        protected void ThrowIfRequestExecutionHasStarted()
 273        {
 274            if (this.hasRequestExecutionStarted)
 275            {
 276                throw new InvalidOperationException(BatchErrorMessages.BatchRequestCannotBeModified);
 277            }
 278        }
 279
 280        /// <summary>
 281        /// Executes the specified function with a cancellation token which is a composite token for the <see cref="IBat
 282        /// and the <see cref="IBatchRequest.CancellationToken"/>.
 283        /// </summary>
 284        /// <param name="func">The function defining what work to be called in a cancellable way</param>
 285        /// <returns>A task with the async state of the func</returns>
 286        private async Task<TResponse> ExecuteRequestWithCancellationAsync(Func<CancellationToken, Task<TResponse>> func)
 287        {
 288            //CancellationToken(from outside) should cancel BOTH the retries and the currently ongoing REST request
 289            //CancellationToken(from timeout) should cancel just the currently ongoing REST request
 290
 291            using (CancellationTokenSource timeoutCancellationTokenSource = new CancellationTokenSource(this.Timeout))
 292            {
 293                CancellationToken timeoutToken = timeoutCancellationTokenSource.Token;
 294                using (CancellationTokenSource linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeo
 295                {
 296                    return await func(linkedTokenSource.Token).ConfigureAwait(continueOnCapturedContext: false);
 297                }
 298            }
 299        }
 300
 301        private void ApplyClientRequestIdBehaviorToParams()
 302        {
 303            if (this.ClientRequestIdProvider == null)
 304            {
 305                Guid clientRequestId = Guid.NewGuid();
 306                this.Options.ClientRequestId = clientRequestId;
 307            }
 308            else
 309            {
 310                this.Options.ClientRequestId = this.ClientRequestIdProvider.GenerateClientRequestIdFunc(this);
 311            }
 312        }
 313    }
 314
 315    /// <summary>
 316    /// Represents the information required to make a particular call with no request body to the Batch service REST API
 317    /// </summary>
 318    /// <typeparam name="TOptions">The type of the parameters passed outside the request body associated with the reques
 319    /// <typeparam name="TResponse">The response type expected from the request.</typeparam>
 320    public class BatchRequest<TOptions, TResponse> : BatchRequestBase<TOptions, TResponse>
 321        where TOptions : IOptions, new()
 322        where TResponse : IAzureOperationResponse
 323    {
 324        /// <summary>
 325        /// Initializes a new instance of the <see cref="BatchRequest{TOptions,TResponse}"/> class.
 326        /// </summary>
 327        /// <param name="restClient">The REST client to use.</param>
 328        /// <param name="cancellationToken">The cancellationToken to use.</param>
 329        public BatchRequest(BatchServiceClient restClient, CancellationToken cancellationToken)
 330            : base(restClient, cancellationToken)
 331        {
 332        }
 333    }
 334
 335    /// <summary>
 336    /// Represents the information required to make a particular call with a request body of type <typeparamref name="TB
 337    /// </summary>
 338    /// <typeparam name="TBody">The type of the body parameters associated with the request.</typeparam>
 339    /// <typeparam name="TOptions">The type of the parameters passed outside the request body associated with the reques
 340    /// <typeparam name="TResponse">The response type expected from the request.</typeparam>
 341    public class BatchRequest<TBody, TOptions, TResponse> : BatchRequestBase<TOptions, TResponse>
 342        where TOptions: IOptions, new()
 343        where TResponse : IAzureOperationResponse
 344    {
 345        private TBody parameters;
 346
 347        /// <summary>
 348        /// Gets or sets the parameters passed in the REST API request body.
 349        /// </summary>
 350        public TBody Parameters
 351        {
 25352            get { return this.parameters; }
 353            set
 354            {
 61355                this.ThrowIfRequestExecutionHasStarted();
 60356                this.parameters = value;
 60357            }
 358        }
 359
 360        /// <summary>
 361        /// Initializes a new instance of the <see cref="BatchRequest{TParameters,TOptions,TResponse}"/> class.
 362        /// </summary>
 363        /// <param name="restClient">The REST client to use.</param>
 364        /// <param name="parameters">The parameters to use.</param>
 365        /// <param name="cancellationToken">The cancellationToken to use.</param>
 60366        public BatchRequest(Protocol.BatchServiceClient restClient, TBody parameters, CancellationToken cancellationToke
 367        {
 60368            Parameters = parameters;
 60369        }
 370    }
 371}