Skip to content

Commit

Permalink
Support for custom FunctionInvokers
Browse files Browse the repository at this point in the history
  • Loading branch information
mathewc committed Jun 20, 2017
1 parent 97da0e1 commit 198fcf4
Show file tree
Hide file tree
Showing 20 changed files with 221 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,7 @@ internal static async Task ExecuteWithWatchersAsync(IFunctionInstance instance,
CancellationTokenSource functionCancellationTokenSource)
{
IFunctionInvoker invoker = instance.Invoker;
IReadOnlyList<string> parameterNames = invoker.ParameterNames;

IReadOnlyList<string> parameterNames = instance.FunctionDescriptor.Method.GetParameters().Select(p => p.Name).ToList();
Tuple<object[], IDelayedException> preparedParameters = await PrepareParametersAsync(parameterNames, parameters);

object[] invokeParameters = preparedParameters.Item1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// 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.Collections.Generic;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Triggers;

namespace Microsoft.Azure.WebJobs.Host.Executors
{
Expand All @@ -21,10 +20,10 @@ public FunctionInstanceFactory(IFunctionBinding binding, IFunctionInvoker invoke
_descriptor = descriptor;
}

public IFunctionInstance Create(Guid id, Guid? parentId, ExecutionReason reason, IDictionary<string, object> parameters)
public IFunctionInstance Create(FunctionInstanceFactoryContext context)
{
IBindingSource bindingSource = new BindingSource(_binding, parameters);
return new FunctionInstance(id, parentId, reason, bindingSource, _invoker, _descriptor);
IBindingSource bindingSource = new BindingSource(_binding, context.Parameters);
return new FunctionInstance(context.Id, context.ParentId, context.ExecutionReason, bindingSource, _invoker, _descriptor);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Protocols;

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class FunctionInstanceFactoryContext
{
public Guid Id { get; set; }
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; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class FunctionInstanceFactoryContext<TTriggerValue> : FunctionInstanceFactoryContext
{
public TTriggerValue TriggerValue { get; set; }
}
}
15 changes: 1 addition & 14 deletions src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,11 @@ namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal class FunctionInvoker<TReflected> : IFunctionInvoker
{
private readonly IReadOnlyList<string> _parameterNames;
private readonly IFactory<TReflected> _instanceFactory;
private readonly IMethodInvoker<TReflected> _methodInvoker;

public FunctionInvoker(IReadOnlyList<string> parameterNames, IFactory<TReflected> instanceFactory,
IMethodInvoker<TReflected> methodInvoker)
public FunctionInvoker(IFactory<TReflected> instanceFactory, IMethodInvoker<TReflected> methodInvoker)
{
if (parameterNames == null)
{
throw new ArgumentNullException("parameterNames");
}

if (instanceFactory == null)
{
throw new ArgumentNullException("instanceFactory");
Expand All @@ -31,7 +24,6 @@ public FunctionInvoker(IReadOnlyList<string> parameterNames, IFactory<TReflected
throw new ArgumentNullException("methodInvoker");
}

_parameterNames = parameterNames;
_instanceFactory = instanceFactory;
_methodInvoker = methodInvoker;
}
Expand All @@ -41,11 +33,6 @@ public IFactory<TReflected> InstanceFactory
get { return _instanceFactory; }
}

public IReadOnlyList<string> ParameterNames
{
get { return _parameterNames; }
}

public async Task InvokeAsync(object[] arguments)
{
// Return a task immediately in case the method is not async.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace Microsoft.Azure.WebJobs.Host.Executors
Expand Down Expand Up @@ -39,13 +37,10 @@ private static IFunctionInvoker CreateGeneric<TReflected>(MethodInfo method, IJo
{
Debug.Assert(method != null);

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

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

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

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

private static IFactory<TReflected> CreateInstanceFactory<TReflected>(MethodInfo method,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Storage.Queue;
using Microsoft.Azure.WebJobs.Host.Triggers;
using Newtonsoft.Json;

namespace Microsoft.Azure.WebJobs.Host.Executors
Expand Down Expand Up @@ -108,7 +109,14 @@ private IFunctionInstance CreateFunctionInstance(CallAndOverrideMessage message)
}
}

return function.InstanceFactory.Create(message.Id, message.ParentId, message.Reason, objectParameters);
var context = new FunctionInstanceFactoryContext
{
Id = message.Id,
ParentId = message.ParentId,
ExecutionReason = message.Reason,
Parameters = objectParameters
};
return function.InstanceFactory.Create(context);
}

private async Task ProcessCallAndOverrideMessage(CallAndOverrideMessage message, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
// 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.Collections.Generic;
using Microsoft.Azure.WebJobs.Host.Protocols;

namespace Microsoft.Azure.WebJobs.Host.Executors
{
internal interface IFunctionInstanceFactory
{
IFunctionInstance Create(Guid id, Guid? parentId, ExecutionReason reason, IDictionary<string, object> parameters);
IFunctionInstance Create(FunctionInstanceFactoryContext context);
}
}
19 changes: 13 additions & 6 deletions src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInvoker.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Threading.Tasks;

namespace Microsoft.Azure.WebJobs.Host.Executors
namespace Microsoft.Azure.WebJobs.Host
{
internal interface IFunctionInvoker
/// <summary>
/// Provides an interface for invoking a job function.
/// </summary>
public interface IFunctionInvoker
{
IReadOnlyList<string> ParameterNames { get; }

// The cancellation token, if any, is provided along with the other arguments.
/// <summary>
/// Invoke the function.
/// </summary>
/// <param name="arguments">The arguments to invoke the function with.</param>
/// <returns>A <see cref="Task"/> representing the invocation.</returns>
/// <remarks>
/// The cancellation token (if any) is provided along with the arguments.
/// </remarks>
Task InvokeAsync(object[] arguments);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Azure.WebJobs.Host
{
/// <summary>
/// Defines an interface for the creation of <see cref="IFunctionInvoker"/> instances.
/// </summary>
public interface IFunctionInvokerProvider
{
/// <summary>
/// Creates a <see cref="IFunctionInvoker"/>.
/// </summary>
/// <param name="invoker">The default inner invoker.</param>
/// <returns>The <see cref="IFunctionInvoker"/>.</returns>
IFunctionInvoker Create(IFunctionInvoker invoker);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// 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;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Triggers;

namespace Microsoft.Azure.WebJobs.Host.Executors
{
Expand All @@ -32,7 +30,12 @@ public FunctionDescriptor Function

public async Task<FunctionResult> TryExecuteAsync(TriggeredFunctionData input, CancellationToken cancellationToken)
{
IFunctionInstance instance = _instanceFactory.Create((TTriggerValue)input.TriggerValue, input.ParentId);
var context = new FunctionInstanceFactoryContext<TTriggerValue>()
{
TriggerValue = (TTriggerValue)input.TriggerValue,
ParentId = input.ParentId
};
IFunctionInstance instance = _instanceFactory.Create(context);
IDelayedException exception = await _executor.TryExecuteAsync(instance, cancellationToken);

FunctionResult result = exception != null ?
Expand Down
18 changes: 16 additions & 2 deletions src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using Microsoft.Azure.WebJobs.Host.Bindings.Invoke;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Loggers;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Triggers;
using Microsoft.Azure.WebJobs.Logging;
Expand Down Expand Up @@ -277,7 +276,7 @@ internal async Task IndexMethodAsyncCore(MethodInfo method, IFunctionIndexCollec

string triggerParameterName = triggerParameter != null ? triggerParameter.Name : null;
FunctionDescriptor functionDescriptor = CreateFunctionDescriptor(method, triggerParameterName, triggerBinding, nonTriggerBindings);
IFunctionInvoker invoker = FunctionInvokerFactory.Create(method, _activator);
IFunctionInvoker invoker = CreateFunctionInvoker(method, triggerBinding, _activator);
IFunctionDefinition functionDefinition;

if (triggerBinding != null)
Expand All @@ -300,6 +299,21 @@ internal async Task IndexMethodAsyncCore(MethodInfo method, IFunctionIndexCollec
index.Add(functionDefinition, functionDescriptor, method);
}

internal static IFunctionInvoker CreateFunctionInvoker(MethodInfo method, ITriggerBinding binding, IJobActivator activator)
{
// create the default invoker
var invoker = FunctionInvokerFactory.Create(method, activator);

// if the binding implements the provider interface, call it
var invokerProvider = binding as IFunctionInvokerProvider;
if (invokerProvider != null)
{
invoker = invokerProvider.Create(invoker);
}

return invoker;
}

private FunctionDefinition CreateTriggeredFunctionDefinition<TTriggerValue>(
ITriggerBinding triggerBinding, string parameterName, FunctionDescriptor descriptor,
IReadOnlyDictionary<string, IBinding> nonTriggerBindings, IFunctionInvoker invoker)
Expand Down
9 changes: 8 additions & 1 deletion src/Microsoft.Azure.WebJobs.Host/JobHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,14 @@ public void Dispose()

private static IFunctionInstance CreateFunctionInstance(IFunctionDefinition func, IDictionary<string, object> parameters)
{
return func.InstanceFactory.Create(Guid.NewGuid(), null, ExecutionReason.HostCall, parameters);
var context = new FunctionInstanceFactoryContext
{
Id = Guid.NewGuid(),
ParentId = null,
ExecutionReason = ExecutionReason.HostCall,
Parameters = parameters
};
return func.InstanceFactory.Create(context);
}

private static IFunctionDefinition ResolveFunctionDefinition(MethodInfo method, IFunctionIndexLookup functionLookup)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// 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 Microsoft.Azure.WebJobs.Host.Executors;

namespace Microsoft.Azure.WebJobs.Host.Triggers
namespace Microsoft.Azure.WebJobs.Host
{
internal interface ITriggeredFunctionInstanceFactory<TTriggerValue> : IFunctionInstanceFactory
{
IFunctionInstance Create(TTriggerValue value, Guid? parentId);
IFunctionInstance Create(FunctionInstanceFactoryContext<TTriggerValue> context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Protocols;

Expand All @@ -22,18 +23,52 @@ public TriggeredFunctionInstanceFactory(ITriggeredFunctionBinding<TTriggerValue>
_descriptor = descriptor;
}

public IFunctionInstance Create(TTriggerValue value, Guid? parentId)
public IFunctionInstance Create(FunctionInstanceFactoryContext<TTriggerValue> context)
{
IBindingSource bindingSource = new TriggerBindingSource<TTriggerValue>(_binding, value);
return new FunctionInstance(Guid.NewGuid(), parentId, ExecutionReason.AutomaticTrigger, bindingSource,
_invoker, _descriptor);
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}

IBindingSource bindingSource = new TriggerBindingSource<TTriggerValue>(_binding, context.TriggerValue);
var invoker = _invoker;
if (context.InvokeHandler != null)
{
invoker = new InvokeWrapper(_invoker, context.InvokeHandler);
}

return new FunctionInstance(Guid.NewGuid(), context.ParentId, ExecutionReason.AutomaticTrigger, bindingSource, invoker, _descriptor);
}

public IFunctionInstance Create(Guid id, Guid? parentId, ExecutionReason reason,
IDictionary<string, object> parameters)
public IFunctionInstance Create(FunctionInstanceFactoryContext context)
{
IBindingSource bindingSource = new BindingSource(_binding, parameters);
return new FunctionInstance(id, parentId, reason, bindingSource, _invoker, _descriptor);
IBindingSource bindingSource = new BindingSource(_binding, context.Parameters);

var invoker = _invoker;
if (context.InvokeHandler != null)
{
invoker = new InvokeWrapper(_invoker, context.InvokeHandler);
}

return new FunctionInstance(context.Id, context.ParentId, context.ExecutionReason, bindingSource, invoker, _descriptor);
}

private class InvokeWrapper : IFunctionInvoker
{
private readonly IFunctionInvoker _inner;
private readonly Func<Func<Task>, Task> _handler;

public InvokeWrapper(IFunctionInvoker inner, Func<Func<Task>, Task> handler)
{
_inner = inner;
_handler = handler;
}

public Task InvokeAsync(object[] arguments)
{
Func<Task> inner = () => _inner.InvokeAsync(arguments);
return _handler(inner);
}
}
}
}
Loading

0 comments on commit 198fcf4

Please sign in to comment.