Azure / azure-functions-dotnet-worker

Azure Functions out-of-process .NET language worker
MIT License
419 stars 181 forks source link

[Question] Reflecting the called function from the middleware #938

Open SeanFeldman opened 2 years ago

SeanFeldman commented 2 years ago

I'm trying to figure out if the called function code can be reflected or not. The passed into middleware FunctionContext has a FunctionDefinition property. The property contains a string that represents the class and the function in the code. But I cannot use reflection to gain access to the type/method to reflect custom attributes.

My question is does middleware suppose to be able to access the type hosting the function and if yes, would it be possible to break out the type and the method into two properties under FunctionDefinition instead of a single context.FunctionDefinition.EntryPoint.

BenjaBobs commented 2 years ago

I've had some success trying to reflect it. Here's a snippet of my fiddling in an attempt to re-create some singleton functionality for single-instance hosts (since https://github.com/Azure/azure-functions-dotnet-worker/issues/274 seems to be moving along very slowly).

class SingletonFunctionMiddleware : IFunctionsWorkerMiddleware
{
    static readonly SemaphoreSlim _mutex = new(1);

    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        var workerAssembly = Assembly.GetExecutingAssembly();

        // EntryPoint is namespace + type + method, we need to isolate the namespace+type and method separately
        var entryPointParts = context.FunctionDefinition.EntryPoint.Split(".");

        // after splitting on "." the typename should be every part except that last
        var workerTypeName = string.Join(".", entryPointParts[..^1]);
        // which is the method name
        var workerFunctionName = entryPointParts.Last();

        // use reflection to get the MethodInfo (should be cached for performance)
        var workerType = workerAssembly.GetType(workerTypeName);
        var workerFunction = workerType.GetMethod(workerFunctionName);

        if (workerFunction.GetCustomAttribute<SingletonAttribute>() is not null)
        {
            var logger = context.GetLogger<SingletonFunctionMiddleware>();

            logger.LogTrace($"Singleton invocation '{context.InvocationId}' waiting for semaphore...");
            await _mutex.WaitAsync();
            logger.LogTrace($"Singleton invocation '{context.InvocationId}' entered sempahore");

            try
            {
                await next(context);
                _mutex.Release();
                logger.LogTrace($"Singleton invocation '{context.InvocationId}' released sempahore");
            }
            catch (Exception)
            {
                _mutex.Release();
                logger.LogTrace($"Singleton invocation '{context.InvocationId}' released sempahore");
                throw;
            }
        }
        else
        {
            await next(context);
        }
    }
}

In short, the idea is to get the context.FunctionDefinition.EntryPoint and use that to look up the type/method in the executing assembly (assuming the function is from the same assembly, otherwise you'd need a reference to that assembly).

fabiocav commented 2 years ago

@SeanFeldman wanted to add a comment here. This is something we do intend to expose. We're hoping we can prioritize this work soon and will share the design/experience for feedback ahead of implementation.

wouter-b commented 8 months ago

Any progress on this?

laurentAstonIf commented 8 months ago

Thanks you BenjaBobs We will use your code and this lib https://github.com/madelson/DistributedLock/blob/master/docs/Semaphores.md to replace this missing SingletonAttribute

chullybun commented 8 months ago

@laurentAstonIf / @BenjaBobs - I believe this may not give you the existing experience you are looking for. For me, I am using a ServiceBusTrigger and I want singleton-like behavior so that the messages are processed absolutely in order; therefore, only a single function instance should process the queue, which the existing Singleton attributed enabled.

The code above is implemented as middleware, which will get invoked once a message has been peek+lock, so will either semaphore lock that message, or wait until it can, either way the message is peeked. Any additional process in running in parallel will peek+lock the next regardless. If the semaphore lock time is greater than max for message the lock will be released and another process can get it. Please correct me if I am wrong?

Therefore, there is a need for a worker-based Singleton that ensures single execution before a lock dequeue/lock occurs. Maybe, it could be added as a property on the ServiceBusTrigger (and others) if a generic Singleton is not possible?

Finally, @fabiocav is there any progress? 🙏

SeanFeldman commented 8 months ago

To add to what @chullybun said, it will negatively impact all triggers. This approach is buffering requests/messages/etc, which will drive functions to scale out, translating into cost increase and errors. Also, for triggers such as ASB, for some it will result in unnecessary message dead-lettering. This is not a problem for middleware but for the host.

wouter-b commented 8 months ago

@laurentAstonIf @BenjaBobs FYI we're using the same solution and hope there will be a solid solution in the host in the near future.

BenjaBobs commented 8 months ago

I'm add a point where I'm avoiding azure functions where I can, and use MassTransit and Quartz.net instead to fill the same role. We mostly use timer triggers and queue triggers, so it was an easy jump for us.

wouter-b commented 6 months ago

We've implemented a singleton functions middleware like above (https://github.com/Azure/azure-functions-dotnet-worker/issues/938#issuecomment-1167329390). As chullybun and SeanFeldman stated this doesn't play nice with existing triggers and in the end only works on a servicebustrigger with a NewBatchThreshhold of 0.

@fabiocav is there any progress or guidance how to solve this? Is it possible to implement a custom servicebustrigger?

kmatthews-dev commented 5 months ago

Also looking for a singleton implementation as the last piece of our Azure function migration

mattchenderson commented 5 months ago

This item is for behavior within the middleware pipeline and accessing function metadata. Please track #274 for singleton discussions.