azure-functions-host icon indicating copy to clipboard operation
azure-functions-host copied to clipboard

Add support for app level initialization logic

Open davidebbo opened this issue 8 years ago • 53 comments

e.g. in this thread, the user needs to call ThreadPool.SetMinThreads, and currently we have no good place to make App Domain init time calls.

Of course, that gives user potential to make some bad calls, but that's a small concern compared to the scenarios that can unblock (and the could make the same calls in a function anyway).

davidebbo avatar Aug 17 '16 16:08 davidebbo

We might implement this as a special Function. E.g. the user can implement an "init" function (either by naming convention, or perhaps flagged via a metadata property. We'll invoke that function first, before starting the host.

mathewc avatar Aug 17 '16 17:08 mathewc

Right, that's the kind of thing I had in mind, some special function.

One interesting design issue is knowing when to call it given that this function can change while the app domain is up. Do we give user any guarantee that it can only be called once? It's probably simpler if the contract is that they need to make their init code idempotent, so that we can keep calling it on the same domain/host as needed.

davidebbo avatar Aug 17 '16 19:08 davidebbo

If we decide to call it when the host starts (as opposed the application, unless that's what Mathew means), that function could be called multiple times within the Process/AppDomain life span. We could, however, make sure that only happens once, but depending on the scenario, that may not be the desired behavior.

fabiocav avatar Aug 17 '16 22:08 fabiocav

Yeah, I think the requirement is to have something that gets called when the Function App starts (I mispoke above and said "host").

mathewc avatar Aug 17 '16 22:08 mathewc

Yes, but that would then imply a full App Domain cycle (and not just host cycle) if the user edits it.

davidebbo avatar Aug 18 '16 00:08 davidebbo

I am doing this and it kind of works like initialization. I setup db connection and schema setup after my node_module requires. I put all that code above the exported function

var mongoose = require("mongoose"); var redis = require("redis"); //gets the object json from a json file defined by the name var offerSchema = require("./schemas/offer.js"); var offer = new mongoose.Schema(offerSchema); //creates the model with the mongodb collection name and the schema var OfferModel = mongoose.model('Offer', offer); // Database setup mongoose.connect('mongodb://xxxxxx:[email protected]:xxxx/creditcards-dev'); module.exports = function(context, req) {.....

my understanding is that the entire module would be woken up and would execute the init code (above) and then persist and continue to field additional requests (through the exported function). If the goes cold after not getting requests it would fall back asleep. Further calls would get a longer cold start when they were brought back in by requests.

I am new to Azure Functions and am just playing around but the tests I have done seem to support my theory, but I could be wrong.

contractorwolf avatar Sep 11 '16 03:09 contractorwolf

is there something wrong with my approach @davidebbo ?

contractorwolf avatar Sep 11 '16 03:09 contractorwolf

@contractorwolf We probably had more C# in mind in the above discussion, and we need to think about Node as well.

In your approach, I think you'll end up having this code execute each time you modify your function.

davidebbo avatar Sep 11 '16 16:09 davidebbo

what do you mean "each time you modify your function"? whenever I change my code? is that not where I want it to initialize?

contractorwolf avatar Sep 11 '16 18:09 contractorwolf

is there some documentation of the Azure Function lifecycle? I am imagining that there is a server that is hosting the individual functions and the first request loads your function (executing the require statements, the code inline before the export and then instantiating the export) and then holds that function up for an indeterminate amount of time (AWS was 2-5 minutes with their Lambda service). When a second request comes in if there is a live function running it can respond to the request (using that instance initialization parameters) it does, if not it starts a new one up. Please correct me if I am mistaken, I want to understand this better.

contractorwolf avatar Sep 11 '16 18:09 contractorwolf

what do you mean "each time you modify your function"? whenever I change my code? is that not where I want it to initialize?

Yes, I mean whenever you modify that function. And no, that is not when you want to run the initialize code, since we're talking about App Level init logic. i.e. logic that is global to all Functions in the Function App.

The lifecycle is at the level of the Function App and not the individual Functions.

davidebbo avatar Sep 11 '16 19:09 davidebbo

@davidebbo can you show an example of instantiating a database connection (somewhere outside the function code) that gets used in an Azure Function?

contractorwolf avatar Sep 13 '16 01:09 contractorwolf

We don't have good support for this right now. That's why we have this issue :)

davidebbo avatar Sep 13 '16 01:09 davidebbo

@davidebbo I am not understanding, is it not possible or you aren't sure if it is possible? Did Microsoft would release this as a product without proper examples or documentation?

contractorwolf avatar Sep 13 '16 01:09 contractorwolf

Azure Functions is still in pre-release, so there are still rough edges. We don't currently have a clean place to write app level initialization logic, hence the look for potential workarounds in the meantime.

davidebbo avatar Sep 13 '16 01:09 davidebbo

@davidebbo @mathewc Has any more thought been given to this? App level init seems like a great place to add in hooks for DI like we get with WebJob's JobHostConfiguration.JobActivator.

markanewman avatar Dec 28 '16 15:12 markanewman

Hey - just wanted to revisit the discussion to see if an app level Azure Function init hook feature is on the horizon?

erikschlegel avatar Mar 25 '17 13:03 erikschlegel

I am also really interested in this. I have a problem with scaling on consumption plan since it always start with 4 threads (as it seems) and it is 500ms delay for each new thread to come up together with the cpu load to start it. Since I get in alot of messages in batches on a service bus Topic it really cant keep up at all. If I preload the servicebus with 10 000 messages and start the azure function it handle around 45 messages per second on Consumtion plan. But If I host it in my own App Service Plan and initiate it with 12 io- and worker- threads per instance I easily handle 1000 of the same messages per second. I think it is a very important thing to address, so we can handle high load rapidly. Would be so important to have somewhere to handle this.

JonasGranlund avatar Jun 11 '17 09:06 JonasGranlund

Any progress on this?

Without it can't easily migrate anything I have from WebJobs as they typically use a bunch of services which are configured in a container

phatcher avatar Jul 20 '17 12:07 phatcher

Also interested if anything is on the horizon.

rhythmnewt avatar Aug 07 '17 23:08 rhythmnewt

Only efficient way I have found so far for that scenario is to rely on some Bootstrapping.Init static method which triggers a static ctor in that same class (init once), which does the heavy lifting and delegate initialization to one or several bootstrap specific classes. That Init method is called by each and every function.

SimonLuckenuik avatar Aug 21 '17 13:08 SimonLuckenuik

could u provide any sample code? is this init method called for every instance of function execution?

pmaheshgupta avatar Aug 21 '17 13:08 pmaheshgupta

Yes called for every execution of every function, something similar to that:

    public static class Bootstrapping
    {
        static Bootstrapping()
        {
            // Init here, called once because of static ctor. Assign to static members if required.
        }

        public static void Init()
        {
        }
    }

    public static class Function1
    {
        [FunctionName("Function1")]
        public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "HttpTriggerCSharp/name/{name}")]HttpRequestMessage req, string name, TraceWriter log)
        {
            Bootstrapping.Init();
            return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
        }
    }

SimonLuckenuik avatar Aug 21 '17 23:08 SimonLuckenuik

I too would like this, or at least the recommended pattern. We're doing lots of API requests using HttpClient under the covers, which according to these Microsoft antipatterns it's a bad thing to initialize this multiple times.

Is the suggested thing to do, adding a static constructor here to initialize it?

codymullins avatar Sep 11 '17 19:09 codymullins

@pmaheshgupta here is what I've done so far:

    public class Bootsrapper
    {
        private static IConnectionMultiplexer lazyRedisContext;

        public static IConnectionMultiplexer RedisContext => lazyRedisContext ?? CreateRedisContext();
        public static IDatabase DefaultRedisDb => RedisContext.GetDatabase();

        protected Bootsrapper() {}

        private static IConnectionMultiplexer CreateRedisContext()
        {
            return lazyRedisContext = ConnectionMultiplexer.Connect(ConfigurationHelper.RedisConnectionString);
        }
    }

And now simple inherit your functions from Bootstapper.cs and the object will be shared across the same azure function. Basically it is the same thing @SimonLuckenuik described above but you don't need to use static constructor and initialize everything in every function of your app if you don't need to. It definitely don't work for all of your functions simply because they run on the different servers. I know it is all the way tricky-hacky but it works and seems there is no alternative ways of doing that at the moment.

ddizh avatar Oct 11 '17 11:10 ddizh

Would appreciate if we can have init function so we can use Newtonsoft json's schema library key for function app instead of registering into all functions' code.

sasolanki avatar Oct 30 '17 09:10 sasolanki

I want to augment described scenarios with the following. I have need to use some environment information to initialize a function/function app on start. For example, I want to use .Net Core config system and need to know base directory to specify where to look for appsettings.json, so I need ExecutionContext object. Or I'd like to take function's TraceWriter (with some wrapper or accept an ILogger) and inject it as a logger using DI. However, these objects are only provided on function call. Worse, I can't be sure they are the same for each call (no doc on this), so I can't use just what the first call provides. Last, I'd also like to handle app shutdown to gracefully dispose my resources (flush some loggers etc). The only idea which comes to mind is handling AppDomain unload, but I'm not sure its correct and doesn't handle host reloading. Any advice on this?

andriysavin avatar Nov 01 '17 15:11 andriysavin

Threads like this one are the most depressing type of Microsoft issues threads. Unfortunately there are literally hundreds of them scattered amongst the more poorly managed projects.

Year one: "It's a beta product, we pushed it out early to get feedback. And hey, look at me, I'm playing on GitHub!"

Year two: "We're still thinking how to plug the obvious architectural holes and problems we created with our half-baked initial release. And wow, GitHub sure is a time sink."

Year three: Crickets.

@davidebbo You haven't responded to valid customer requests since 2016.

chadwackerman avatar Dec 19 '17 12:12 chadwackerman

Any updates on this ? My two cents :as there are two Pricing plan

  1. Consumption plan - When you're using a Consumption plan, instances of the Azure Functions host are dynamically added and removed based on the number of incoming events. So having static implementation of resources to be used is not going to help always.

2.App Service plan - Dedicated VMs are allocated to your App Service apps, which means the functions host is always running. So static implementation of resources to be used is going to help

surenderssm avatar Jan 08 '18 14:01 surenderssm

Hi, I have not had the same problem anymore, when I preload a SB Queue or Topic with +10 000 messages, and increase the maxConcurrentCalls from the default=16 I can handle around 1000 messages per second now on Consumtionplan, compaired from the 45 I handled around 8 months ago. And regarding the http calls, if you do fast increase of calls you need to handle 503 errors during the seconds the consumtionplan scales out, so a retry with backoff and circuit breaker (like Polly nuget) can be a good implementation.

JonasGranlund avatar Jan 08 '18 14:01 JonasGranlund