Skip to content

Commit

Permalink
Return value support for BYOB (v2.x)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgillum committed Jul 20, 2017
1 parent c4a43d5 commit 8a2c4e2
Show file tree
Hide file tree
Showing 29 changed files with 607 additions and 240 deletions.
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

0 comments on commit 8a2c4e2

Please sign in to comment.