Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return value support for BYOB (v2.x) #1243

Merged
merged 1 commit into from
Jul 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Indexers;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Timers;
Expand Down Expand Up @@ -537,7 +538,7 @@ internal static async Task InvokeAsync(IFunctionInvoker invoker, ParameterHelper
// b. If !throwOnTimeout, wait for the task to complete.

// Start the invokeTask.
Task invokeTask = invoker.InvokeAsync(invokeParameters);
Task<object> invokeTask = invoker.InvokeAsync(invokeParameters);

// Combine #1 and #2 with a timeout task (handled by this method).
// functionCancellationTokenSource.Token is passed to each function that requests it, so we need to call Cancel() on it
Expand All @@ -551,7 +552,9 @@ internal static async Task InvokeAsync(IFunctionInvoker invoker, ParameterHelper
await TryHandleTimeoutAsync(invokeTask, CancellationToken.None, throwOnTimeout, timeoutTokenSource.Token, timerInterval, instance, null);
}

await invokeTask;
object returnValue = await invokeTask;

parameterHelper.SetReturnValue(returnValue);
}

/// <summary>
Expand Down Expand Up @@ -723,10 +726,15 @@ internal class ParameterHelper : IDisposable
private IReadOnlyDictionary<string, IWatcher> _parameterWatchers;

// ValueProviders for the parameters. These are produced from binding.
// This includes a possible $return for the return value.
private IReadOnlyDictionary<string, IValueProvider> _parameters;

// ordered parameter names of the underlying physical MethodInfo that will be invoked.
private IReadOnlyList<string> _parameterNames;
// This litererally matches the ParameterInfo[] and does not include return value.
private IReadOnlyList<string> _parameterNames;

// the return value of the function
private object _returnValue;

public ParameterHelper(IReadOnlyList<string> parameterNames)
{
Expand All @@ -738,7 +746,9 @@ public ParameterHelper(IReadOnlyList<string> parameterNames)
public object[] InvokeParameters { get; internal set; }

public IDictionary<string, ParameterLog> ParameterLogCollector => _parameterLogCollector;


public object ReturnValue => _returnValue;

public IReadOnlyDictionary<string, IWatcher> CreateParameterWatchers()
{
if (_parameterWatchers != null)
Expand Down Expand Up @@ -871,7 +881,8 @@ public async Task ProcessOutputParameters(CancellationToken cancellationToken)

if (binder != null)
{
object argument = this.InvokeParameters[this.GetParameterIndex(name)];
bool isReturn = name == FunctionIndexer.ReturnParamName;
object argument = isReturn ? this._returnValue : this.InvokeParameters[this.GetParameterIndex(name)];

try
{
Expand Down Expand Up @@ -929,6 +940,11 @@ private string[] SortParameterNamesInStepOrder()
return parameterNames;
}

internal void SetReturnValue(object returnValue)
{
_returnValue = returnValue;
}

// IDisposable on any of the IValueProviders in our parameter list.
public void Dispose()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ internal class FunctionInstanceFactoryContext
public Guid? ParentId { get; set; }
public ExecutionReason ExecutionReason { get; set; }
public IDictionary<string, object> Parameters { get; set; }
public Func<Func<Task>, Task> InvokeHandler { get; set; }
public Func<Func<Task<object>>, Task<object>> InvokeHandler { get; set; }
}
}
14 changes: 8 additions & 6 deletions src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class FunctionInvoker<TReflected> : IFunctionInvoker
internal class FunctionInvoker<TReflected, TReturnValue> : IFunctionInvoker
{
private readonly IReadOnlyList<string> _parameterNames;
private readonly IFactory<TReflected> _instanceFactory;
private readonly IMethodInvoker<TReflected> _methodInvoker;
private readonly IMethodInvoker<TReflected, TReturnValue> _methodInvoker;

public FunctionInvoker(IReadOnlyList<string> parameterNames, IFactory<TReflected> instanceFactory,
IMethodInvoker<TReflected> methodInvoker)
public FunctionInvoker(
IReadOnlyList<string> parameterNames,
IFactory<TReflected> instanceFactory,
IMethodInvoker<TReflected, TReturnValue> methodInvoker)
{
if (parameterNames == null)
{
Expand Down Expand Up @@ -46,7 +48,7 @@ public IReadOnlyList<string> ParameterNames
get { return _parameterNames; }
}

public async Task InvokeAsync(object[] arguments)
public async Task<object> InvokeAsync(object[] arguments)
{
// Return a task immediately in case the method is not async.
await Task.Yield();
Expand All @@ -55,7 +57,7 @@ public async Task InvokeAsync(object[] arguments)

using (instance as IDisposable)
{
await _methodInvoker.InvokeAsync(instance, arguments);
return await _methodInvoker.InvokeAsync(instance, arguments);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Microsoft.Azure.WebJobs.Host.Executors
{
Expand All @@ -27,25 +28,34 @@ public static IFunctionInvoker Create(MethodInfo method, IJobActivator activator
MethodInfo genericMethodDefinition = typeof(FunctionInvokerFactory).GetMethod("CreateGeneric",
BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(genericMethodDefinition != null);
MethodInfo genericMethod = genericMethodDefinition.MakeGenericMethod(reflectedType);

Type returnType;
if (!TypeUtility.TryGetReturnType(method, out returnType))
{
returnType = typeof(object);
}

MethodInfo genericMethod = genericMethodDefinition.MakeGenericMethod(reflectedType, returnType);
Debug.Assert(genericMethod != null);
Func<MethodInfo, IJobActivator, IFunctionInvoker> lambda =
(Func<MethodInfo, IJobActivator, IFunctionInvoker>)Delegate.CreateDelegate(
typeof(Func<MethodInfo, IJobActivator, IFunctionInvoker>), genericMethod);
return lambda.Invoke(method, activator);
}

private static IFunctionInvoker CreateGeneric<TReflected>(MethodInfo method, IJobActivator activator)
private static IFunctionInvoker CreateGeneric<TReflected, TReturnValue>(
MethodInfo method,
IJobActivator activator)
{
Debug.Assert(method != null);

List<string> parameterNames = method.GetParameters().Select(p => p.Name).ToList();

IMethodInvoker<TReflected> methodInvoker = MethodInvokerFactory.Create<TReflected>(method);
IMethodInvoker<TReflected, TReturnValue> methodInvoker = MethodInvokerFactory.Create<TReflected, TReturnValue>(method);

IFactory<TReflected> instanceFactory = CreateInstanceFactory<TReflected>(method, activator);

return new FunctionInvoker<TReflected>(parameterNames, instanceFactory, methodInvoker);
return new FunctionInvoker<TReflected, TReturnValue>(parameterNames, instanceFactory, methodInvoker);
}

private static IFactory<TReflected> CreateInstanceFactory<TReflected>(MethodInfo method,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ internal interface IFunctionInvoker
IReadOnlyList<string> ParameterNames { get; }

// The cancellation token, if any, is provided along with the other arguments.
Task InvokeAsync(object[] arguments);
Task<object> InvokeAsync(object[] arguments);
}
}
4 changes: 2 additions & 2 deletions src/Microsoft.Azure.WebJobs.Host/Executors/IMethodInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal interface IMethodInvoker<TReflected>
internal interface IMethodInvoker<TReflected, TReturnValue>
{
// The cancellation token, if any, is provided along with the other arguments.
Task InvokeAsync(TReflected instance, object[] arguments);
Task<TReturnValue> InvokeAsync(TReflected instance, object[] arguments);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,16 @@ public static async Task<JobHostContext> CreateJobHostContextAsync(

if (functionIndexProvider == null)
{
functionIndexProvider = new FunctionIndexProvider(services.GetService<ITypeLocator>(), triggerBindingProvider, bindingProvider, activator, functionExecutor, extensions, singletonManager, trace, loggerFactory);
functionIndexProvider = new FunctionIndexProvider(
services.GetService<ITypeLocator>(),
triggerBindingProvider,
bindingProvider,
activator,
functionExecutor,
extensions,
singletonManager,
trace,
loggerFactory);

// Important to set this so that the func we passed to DynamicHostIdProvider can pick it up.
services.AddService<IFunctionIndexProvider>(functionIndexProvider);
Expand Down
49 changes: 33 additions & 16 deletions src/Microsoft.Azure.WebJobs.Host/Executors/MethodInvokerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,13 @@ namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal static class MethodInvokerFactory
{
public static IMethodInvoker<TReflected> Create<TReflected>(MethodInfo method)
public static IMethodInvoker<TReflected, TReturnValue> Create<TReflected, TReturnValue>(MethodInfo method)
{
if (method == null)
{
throw new ArgumentNullException("method");
}

Type returnType = method.ReturnType;

if (returnType != typeof(void) && returnType != typeof(Task))
{
throw new NotSupportedException("Methods may only return void or Task.");
}

if (typeof(TReflected) != method.ReflectedType)
{
throw new InvalidOperationException("The Type must match the method's ReflectedType.");
Expand All @@ -49,6 +42,7 @@ public static IMethodInvoker<TReflected> Create<TReflected>(MethodInfo method)
// If the method returns a value: T returnValue
ParameterExpression returnValue;

Type returnType = method.ReturnType;
if (returnType == typeof(void))
{
returnValue = null;
Expand Down Expand Up @@ -153,19 +147,42 @@ public static IMethodInvoker<TReflected> Create<TReflected>(MethodInfo method)
if (call.Type == typeof(void))
{
// for: public void JobMethod()
Expression<Action<TReflected, object[]>> lambda =
Expression.Lambda<Action<TReflected, object[]>>(block, instanceParameter, argumentsParameter);
var lambda = Expression.Lambda<Action<TReflected, object[]>>(
block,
instanceParameter,
argumentsParameter);
Action<TReflected, object[]> compiled = lambda.Compile();
return new VoidMethodInvoker<TReflected>(compiled);
return new VoidMethodInvoker<TReflected, TReturnValue>(compiled);
}
else
else if (call.Type == typeof(Task))
{
// for: public Task JobMethod()
Debug.Assert(call.Type == typeof(Task));
Expression<Func<TReflected, object[], Task>> lambda =
Expression.Lambda<Func<TReflected, object[], Task>>(block, instanceParameter, argumentsParameter);
var lambda = Expression.Lambda<Func<TReflected, object[], Task>>(
block,
instanceParameter,
argumentsParameter);
Func<TReflected, object[], Task> compiled = lambda.Compile();
return new TaskMethodInvoker<TReflected>(compiled);
return new VoidTaskMethodInvoker<TReflected, TReturnValue>(compiled);
}
else if (typeof(Task).IsAssignableFrom(call.Type))
{
// for: public Task<TReturnValue> JobMethod()
var lambda = Expression.Lambda<Func<TReflected, object[], Task<TReturnValue>>>(
block,
instanceParameter,
argumentsParameter);
Func<TReflected, object[], Task<TReturnValue>> compiled = lambda.Compile();
return new TaskMethodInvoker<TReflected, TReturnValue>(compiled);
}
else
{
// for: public TReturnValue JobMethod()
var lambda = Expression.Lambda<Func<TReflected, object[], TReturnValue>>(
block,
instanceParameter,
argumentsParameter);
Func<TReflected, object[], TReturnValue> compiled = lambda.Compile();
return new MethodInvokerWithReturnValue<TReflected, TReturnValue>(compiled);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Threading.Tasks;

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class MethodInvokerWithReturnValue<TReflected, TReturnValue> : IMethodInvoker<TReflected, TReturnValue>
{
private readonly Func<TReflected, object[], TReturnValue> _lambda;

public MethodInvokerWithReturnValue(Func<TReflected, object[], TReturnValue> lambda)
{
_lambda = lambda;
}

public Task<TReturnValue> InvokeAsync(TReflected instance, object[] arguments)
{
TReturnValue result = _lambda.Invoke(instance, arguments);
return Task.FromResult(result);
}
}
}
10 changes: 5 additions & 5 deletions src/Microsoft.Azure.WebJobs.Host/Executors/TaskMethodInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class TaskMethodInvoker<TReflected> : IMethodInvoker<TReflected>
internal class TaskMethodInvoker<TReflected, TReturnType> : IMethodInvoker<TReflected, TReturnType>
{
private readonly Func<TReflected, object[], Task> _lambda;
private readonly Func<TReflected, object[], Task<TReturnType>> _lambda;

public TaskMethodInvoker(Func<TReflected, object[], Task> lambda)
public TaskMethodInvoker(Func<TReflected, object[], Task<TReturnType>> lambda)
{
_lambda = lambda;
}

public Task InvokeAsync(TReflected instance, object[] arguments)
public Task<TReturnType> InvokeAsync(TReflected instance, object[] arguments)
{
Task task = _lambda.Invoke(instance, arguments);
Task<TReturnType> task = _lambda.Invoke(instance, arguments);
ThrowIfWrappedTaskInstance(task);
return task;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,28 @@ public async Task<FunctionResult> TryExecuteAsync(TriggeredFunctionData input, C
var context = new FunctionInstanceFactoryContext<TTriggerValue>()
{
TriggerValue = (TTriggerValue)input.TriggerValue,
ParentId = input.ParentId,
InvokeHandler = input.InvokeHandler
ParentId = input.ParentId
};

// To support return value handling by triggers without breaking back-compat in 2.x,
// we do some gymnastics to flow the return value through the trigger-provided handler.
// This will likely be removed in 3.x in favor of a contract that supports return values.
if (input.InvokeHandler != null)
{
context.InvokeHandler = async next =>
{
object returnValue = null;
Func<Task<object>> nextWapper = async () =>
{
returnValue = await next();
return returnValue;
};

await input.InvokeHandler(nextWapper);
return returnValue;
};
}

IFunctionInstance instance = _instanceFactory.Create(context);
IDelayedException exception = await _executor.TryExecuteAsync(instance, cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class VoidMethodInvoker<TReflected> : IMethodInvoker<TReflected>
internal class VoidMethodInvoker<TReflected, TReturnValue> : IMethodInvoker<TReflected, TReturnValue>
{
private readonly Action<TReflected, object[]> _lambda;

Expand All @@ -15,10 +15,10 @@ public VoidMethodInvoker(Action<TReflected, object[]> lambda)
_lambda = lambda;
}

public Task InvokeAsync(TReflected instance, object[] arguments)
public Task<TReturnValue> InvokeAsync(TReflected instance, object[] arguments)
{
_lambda.Invoke(instance, arguments);
return Task.FromResult(0);
return Task.FromResult(default(TReturnValue));
}
}
}
Loading