Azure / azure-functions-java-worker

Java runtime and core types for Microsoft Azure Functions
MIT License
87 stars 54 forks source link

Main method when starting an azure-function-java-worker #732

Closed fdomoreno closed 7 months ago

fdomoreno commented 11 months ago

In a java azure function, is there any way to execute initial configurations when the function is initialized, such as the startup (Program.cs) in the azure function in c#? Same as a main method I would not like to use spring-cloud

kaibocai commented 11 months ago

Hi @fdomoreno , thanks for reaching out. In Java we don't have a similar strategy as Program.cs in dotnet, but we do have some other ways for initial configuration. If you can provide a detailed use case for configuring your function, we can try to figure out a way or add a new feature for Java worker to accommodate your request. Thanks.

fdomoreno commented 11 months ago

Member

Hi, thank you very much for the quick response. Currently we need to initialize a method at the start of the function execution that allows to cache some of the connections to different storage accounts, the problem is that these connections are taken from a keyvault.

ConnectionManager.InitializeAsyncShardingClients();

 public static final  HashMap<String, TableServiceClient> _tableConnections = new HashMap<>();

    public static final HashMap<String, BlobServiceClient> _blobConnections = new HashMap<>();

    private static final String KEY_VAULT_SERVER = "keyvaulturl";
    private static final String TYPE_TABLE = "table";
    private static final String TYPE_BLOB = "blob";

    private static int MAX_REINTENTOS=5;
    private static int SLEEP=100;

    public static void InitializeAsyncShardingClients(){

        ManagedIdentityCredential managedIdentityCredential = new ManagedIdentityCredentialBuilder().build();
        AzureCliCredential cliCredential = new AzureCliCredentialBuilder().build();
        ChainedTokenCredential credential = new ChainedTokenCredentialBuilder().addFirst(managedIdentityCredential).addLast(cliCredential).build();
        SecretClient secretClient = new SecretClientBuilder()
                .vaultUrl(System.getenv(KEY_VAULT_SERVER))
                .credential(credential)
                .buildClient();

        List<ShardingEntity> shardEndPoints = ConfigManager.getShardingsCache();
        for (ShardingEntity shard : shardEndPoints) {
            try{
                KeyVaultSecret secret=getSecret(secretClient,shard.getEndPoint());
                if (secret!=null) {
                    String stringConnection = secret.getValue();
                    if (TYPE_TABLE.equals(shard.getType())) {
                        TableServiceAsyncClient client = new TableServiceClientBuilder().connectionString(stringConnectionRA).buildAsyncClient();
                        _tableAsyncConnections.put(shard.getEndPoint(), client);              
                    } else if (TYPE_BLOB.equals(shard.getType())) {
                        BlobServiceClient client = new BlobServiceClientBuilder()
                                .connectionString(stringConnection)
                                .buildClient();
                        _blobConnections.put(shard.getEndPoint(), client);
                    }
                }
            }catch(Exception e) {
                throw new RuntimeException("Falló la carga de clientes! "+e);
            }
        }
    }

    private static KeyVaultSecret getSecret(SecretClient secretClient,String endpoint ){
        int reintento=0;
        while(true){
            try {
                KeyVaultSecret secret = secretClient.getSecret(endpoint);
                return secret;
            }catch (Exception e){
              reintento++;
              if(reintento>MAX_REINTENTOS){
                  throw new RuntimeException("Se alcanzó la cantidad máxima de reintentos de leer los secretos  "+reintento);
              }
              try {
                  Thread.sleep(SLEEP);
              }catch (InterruptedException ex){
                  logger.warning("Error haciendo el sleep "+ ex.toString());
              }
            }
        }
    }

The method is called when the function application is executed for the first time with an Http trigger, we have some problems:

  1. With high concurrency the function gets responses with 429 errors from the keyvault: KeyVaultErrorException : too many requests.
  2. For java when we try to get the secrets from the keyvault, it generates an initial 401 response and a delay time while MSI authentication is done.

We have other methods that we want to execute from the beginning: .getRules() and getTypesList(), both use Redis cache, like the previous example 429 when the function is called with high concurrency from the initial load.

We use a warmup trigger but it is not as expected.

We need to initialize 3 methods that load memory and redis cache.

kaibocai commented 11 months ago

Hi @fdomoreno ,

The method is called when the function application is executed for the first time with an Http trigger

Does this mean your initialization methods got triggered every time your function is triggered and because your function is invoked for quite a high volume causing the resources initialization methods to get 429 response. If this is the case, have you tried putting the initialization methods in a static block so it will be triggered once we initialize the function class. Thanks.

fdomoreno commented 11 months ago

Hi @kaibocai

Hi @fdomoreno ,

The method is called when the function application is executed for the first time with an Http trigger

Does this mean your initialization methods got triggered every time your function is triggered and because your function is invoked for quite a high volume causing the resources initialization methods to get 429 response. If this is the case, have you tried putting the initialization methods in a static block so it will be triggered once we initialize the function class. Thanks.

Not really, the initialization method is being executed only once, it is called inside a static block, the problem is that the number of concurrent initial requests is too high.

I also want to clarify that this function was migrated from c# where there was a main method in Program.cs.

before


static async Task Main(string[] args)
{
    FunctionsDebugger.Enable();
    ServicePointManager.DefaultConnectionLimit = 500;

    try
    {
        ConnectionManager.InitializeShardingClients();
    }
    catch (Exception e)
    {
        System.Diagnostics.Trace.TraceError(e.Message);
        System.Diagnostics.Trace.TraceError(e.StackTrace);
        throw;
    }
    try
    {
        // Get all crls
        _ = CertificateManager.Instance.GetCrls();

        // Get all crt certificates
        _ = CertificateManager.Instance.GetRootCertificates();
    }
    catch (Exception e)
    {
        System.Diagnostics.Trace.TraceError(e.Message);                
    }

    var host = new HostBuilder()
        .ConfigureFunctionsWorkerDefaults()
        .Build();

    await host.RunAsync();
}

now:


static {
    ConnectionManager.InitializeAsyncShardingClients();
    RuleEngine.getRules();
    RuleEngine.GetTypesList();
}
kaibocai commented 11 months ago

That's interesting. The static block should only be executed once during the invocation process. I am curious why it's been executed multiple times. Is it possible to add an AtomicInteger counter to see how many times the static block got executed? From my test I only see it got executed once. For example

    private static final AtomicInteger count = new AtomicInteger(0);
    static {
        count.getAndIncrement();
    }

    @FunctionName("HttpExample")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET, HttpMethod.POST},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<Optional<String>> request,
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");
        return request.createResponseBuilder(HttpStatus.OK).body("Static block executed time: " + count.get()).build();
    }
shreyas-gopalakrishna commented 7 months ago

@fdomoreno Is this issue resolved? Let us know if we can help in any way

microsoft-github-policy-service[bot] commented 7 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.