< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Dispatcher.TaskMethodInvoker
Assembly: CoreWCF.Primitives
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/Dispatcher/TaskMethodInvoker.cs
Line coverage
64%
Covered lines: 50
Uncovered lines: 27
Coverable lines: 77
Total lines: 285
Line coverage: 64.9%
Branch coverage
52%
Covered branches: 24
Total branches: 46
Branch coverage: 52.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)66.66%66100%
AllocateInputs()100%11100%
Invoke(...)100%110%
InvokeBegin(...)100%110%
InvokeAsync()43.75%161652.38%
InvokeAsyncCore(...)44.44%181866.66%
ConvertExceptionForFaultedTask(...)0%440%
EnsureIsInitialized()100%22100%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/Dispatcher/TaskMethodInvoker.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Diagnostics;
 6using System.Reflection;
 7using System.Runtime.ExceptionServices;
 8using System.Security;
 9using System.Threading.Tasks;
 10using CoreWCF.Description;
 11using CoreWCF.Diagnostics;
 12using CoreWCF.Runtime;
 13using Microsoft.Extensions.DependencyInjection;
 14using Microsoft.Extensions.Logging;
 15
 16namespace CoreWCF.Dispatcher
 17{
 18    internal class TaskMethodInvoker : IOperationInvoker
 19    {
 20        private const string ResultMethodName = "Result";
 21        private InvokeDelegate _invokeDelegate;
 22        private int _inputParameterCount;
 23        private int _outputParameterCount;
 24        private readonly MethodInfo _taskTResultGetMethod;
 25        private readonly bool _isGenericTask;
 26        private readonly IServiceProvider _serviceProvider;
 27
 828        public TaskMethodInvoker(IServiceProvider serviceProvider, MethodInfo taskMethod, Type taskType)
 29        {
 830            _serviceProvider = serviceProvider ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameo
 831            TaskMethod = taskMethod ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(taskMetho
 32
 833            if (taskType != ServiceReflector.VoidType)
 34            {
 435                _taskTResultGetMethod = ((PropertyInfo)taskMethod.ReturnType.GetMember(ResultMethodName)[0]).GetGetMetho
 436                _isGenericTask = true;
 37            }
 838        }
 39
 640        public MethodInfo TaskMethod { get; }
 41
 42        public object[] AllocateInputs()
 43        {
 644            EnsureIsInitialized();
 45
 646            return EmptyArray<object>.Allocate(_inputParameterCount);
 47        }
 48
 49        public object Invoke(object instance, object[] inputs, out object[] outputs)
 50        {
 051            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException());
 52        }
 53
 54        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
 55        {
 056            return InvokeAsync(instance, inputs).ToApm(callback, state);
 57        }
 58
 59        public async ValueTask<(object returnValue, object[] outputs)> InvokeAsync(object instance, object[] inputs)
 60        {
 661            if (instance == null)
 62            {
 063                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.SFxNoServiceO
 64            }
 65            //bool callFailed = true;
 66            //bool callFaulted = false;
 67            //ServiceModelActivity activity = null;
 68            //Activity boundOperation = null;
 69
 70            try
 71            {
 72                //AsyncMethodInvoker.GetActivityInfo(ref activity, ref boundOperation);
 73
 74                // This code would benefith from a rewrite to call TaskHelpers.ToApmEnd<Tuple<object, object[]>>
 75                // When doing so make sure there is enought test coverage se PR comment at link below for a good startin
 76                // https://github.com/CoreWCF/CoreWCF/pull/54/files/8db6ff9ad6940a1056363defd1f6449adee56e1a#r333826132
 677                (Task returnValue, object[] outputs) tupleResult = await InvokeAsyncCore(instance, inputs);
 78
 679                AggregateException ae = null;
 80                object[] outputs;
 681                if (!(tupleResult.returnValue is Task task))
 82                {
 083                    outputs = tupleResult.outputs;
 084                    return (null, outputs);
 85                }
 86
 687                if (task.IsFaulted)
 88                {
 89                    Fx.Assert(task.Exception != null, "Task.IsFaulted guarantees non-null exception.");
 090                    ae = task.Exception;
 91                }
 92
 693                if (ae != null && ae.InnerException != null)
 94                {
 095                    if (ae.InnerException is FaultException)
 96                    {
 97                        // If invokeTask.IsFaulted we produce the 'callFaulted' behavior below.
 98                        // Any other exception will retain 'callFailed' behavior.
 99                        //callFaulted = true;
 100                        //callFailed = false;
 101                    }
 102
 0103                    if (ae.InnerException is SecurityException)
 104                    {
 0105                        DiagnosticUtility.TraceHandledException(ae.InnerException, TraceEventType.Warning);
 0106                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(AuthorizationBehavior.CreateAccessDeni
 107                    }
 108
 109                    // Rethrow inner exception as is
 0110                    ExceptionDispatchInfo.Capture(ae.InnerException).Throw();
 111                }
 112
 113                // Task cancellation without an exception indicates failure but we have no
 114                // additional information to provide.  Accessing Task.Result will throw a
 115                // TaskCanceledException.   For consistency between void Tasks and Task<T>,
 116                // we detect and throw here.
 6117                if (task.IsCanceled)
 118                {
 0119                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TaskCanceledException(task));
 120                }
 121
 6122                outputs = tupleResult.outputs;
 123
 6124                object returnVal = _isGenericTask ? _taskTResultGetMethod.Invoke(task, Type.EmptyTypes) : null;
 125                //callFailed = false;
 126
 6127                return (returnVal, outputs);
 128            }
 129            finally
 130            {
 131                //if (boundOperation != null)
 132                //{
 133                //    ((IDisposable)boundOperation).Dispose();
 134                //}
 135
 136                //ServiceModelActivity.Stop(activity);
 137                //AsyncMethodInvoker.StopOperationInvokeTrace(callFailed, callFaulted, TaskMethod.Name);
 138                //AsyncMethodInvoker.StopOperationInvokePerformanceCounters(callFailed, callFaulted, TaskMethod.Name);
 139            }
 6140        }
 141
 142        public ValueTask<(Task returnValue, object[] outputs)> InvokeAsyncCore(object instance, object[] inputs)
 143        {
 6144            EnsureIsInitialized();
 145
 6146            if (instance == null)
 147            {
 0148                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.SFxNoServiceO
 149            }
 150
 6151            if (inputs == null)
 152            {
 0153                if (_inputParameterCount > 0)
 154                {
 0155                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(SR
 156                }
 157            }
 6158            else if (inputs.Length != _inputParameterCount)
 159            {
 0160                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(SR.SFx
 161            }
 162
 6163            object[] outputs = EmptyArray<object>.Allocate(_outputParameterCount);
 164
 165            //AsyncMethodInvoker.StartOperationInvokePerformanceCounters(taskMethod.Name);
 166
 167            object returnValue;
 168            //bool callFailed = true;
 169            //bool callFaulted = false;
 170            //ServiceModelActivity activity = null;
 171            //Activity boundActivity = null;
 172
 173            try
 174            {
 175                //AsyncMethodInvoker.CreateActivityInfo(ref activity, ref boundActivity);
 176                //AsyncMethodInvoker.StartOperationInvokeTrace(taskMethod.Name);
 177
 178                //if (DiagnosticUtility.ShouldUseActivity)
 179                //{
 180                //    string activityName = SR.Format(SR.ActivityExecuteMethod, taskMethod.DeclaringType.FullName, taskM
 181                //    ServiceModelActivity.Start(activity, activityName, ActivityType.ExecuteUserCode);
 182                //}
 183
 6184                returnValue = _invokeDelegate(instance, inputs, outputs);
 185
 6186                if (returnValue == null)
 187                {
 0188                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(Task));
 189                }
 190
 6191                var returnValueTask = returnValue as Task;
 192
 6193                if (returnValueTask != null)
 194                {
 195                    // Return ValueTask which comletes once the task has completed
 6196                    if (returnValueTask.IsCompleted)
 197                    {
 2198                        if (returnValueTask.IsFaulted)
 199                        {
 0200                            return new ValueTask<(Task returnValue, object[] outputs)>(Task.FromException<(Task returnVa
 201                        }
 202                        else
 203                        {
 2204                            return new ValueTask<(Task returnValue, object[] outputs)>((returnValueTask, outputs));
 205                        }
 206                    }
 207                    else
 208                    {
 4209                        Task<(Task returnValue, object[] outputs)> completionTask = returnValueTask.ContinueWith(anteced
 4210                        {
 4211                            if (returnValueTask.IsFaulted)
 4212                            {
 0213                                ExceptionDispatchInfo.Capture(ConvertExceptionForFaultedTask(antecedant)).Throw();
 4214                            }
 4215
 4216                            return (returnValue: antecedant, outputs);
 4217
 4218                        });
 219
 4220                        return new ValueTask<(Task returnValue, object[] outputs)>(completionTask);
 221                    }
 222                    //await returnValueTask;
 223                }
 224
 225                // returnValue is null
 0226                return new ValueTask<(Task returnValue, object[] outputs)>((returnValueTask, outputs));
 227            }
 228            finally
 229            {
 230                // TODO: When brining boundActivity back, make sure it executes in the correct order with relation to
 231                // called task completing.
 232                //if (boundActivity != null)
 233                //{
 234                //    ((IDisposable)boundActivity).Dispose();
 235                //}
 236
 237                //ServiceModelActivity.Stop(activity);
 238
 239                // Any exception above means InvokeEnd will not be called, so complete it here.
 240                //if (callFailed || callFaulted)
 241                //{
 242                //AsyncMethodInvoker.StopOperationInvokeTrace(callFailed, callFaulted, TaskMethod.Name);
 243                //AsyncMethodInvoker.StopOperationInvokePerformanceCounters(callFailed, callFaulted, TaskMethod.Name);
 244                //}
 245            }
 246        }
 247
 248        private Exception ConvertExceptionForFaultedTask(Task task)
 249        {
 0250            Exception exception = task.Exception.InnerException;
 251            //bool callFaulted;
 0252            if (exception is SecurityException)
 253            {
 0254                DiagnosticUtility.TraceHandledException(exception, TraceEventType.Warning);
 0255                exception = DiagnosticUtility.ExceptionUtility.ThrowHelperError(AuthorizationBehavior.CreateAccessDenied
 256            }
 0257            else if (exception is FaultException)
 258            {
 259                //callFaulted = true;
 260            }
 261            else
 262            {
 0263                TraceUtility.TraceUserCodeException(exception, TaskMethod);
 264            }
 265            //AsyncMethodInvoker.StopOperationInvokeTrace(true, callFaulted, TaskMethod.Name);
 266            //AsyncMethodInvoker.StopOperationInvokePerformanceCounters(true, callFaulted, TaskMethod.Name);
 267
 0268            return exception;
 269        }
 270
 271        private void EnsureIsInitialized()
 272        {
 12273            if (_invokeDelegate == null)
 274            {
 275                // Only pass locals byref because InvokerUtil may store temporary results in the byref.
 276                // If two threads both reference this.count, temporary results may interact.
 6277                ILogger<InvokeDelegate> logger = _serviceProvider.GetRequiredService<ILogger<InvokeDelegate>>();
 6278                InvokeDelegate invokeDelegate = InvokerUtil.GenerateInvokeDelegate(logger, TaskMethod, out int inputPara
 6279                _inputParameterCount = inputParameterCount;
 6280                _outputParameterCount = outputParameterCount;
 6281                _invokeDelegate = invokeDelegate;  // must set this last due to race
 282            }
 12283        }
 284    }
 285}