Search code examples
c#azureazure-functionsazure-cosmosdbinfinite-loop

Modifying CosmosDb document in Azure Functions triggers infinite loop


I try to update an document in Azure Functions when a CosmosDB document inserted/updated.

However when I update the document inside the function, the function is triggered again and causes an infinite loop.

private static DocumentClient _documentClient = new DocumentClient(new Uri(serviceEndpoint), key);

[FunctionName(nameof(MyFunction))]
public static async Task RunAsync([CosmosDBTrigger(
    databaseName: "MyDatabase",
    collectionName: "MyCollection",
    ConnectionStringSetting = "MyDbConnectionString",
    LeaseCollectionName = "leases",
    CreateLeaseCollectionIfNotExists = true,
    LeaseCollectionPrefix = nameof(MyFunction))]IReadOnlyList<Document> input,
    ILogger log)
{
    var replacementsTasks = new List<Task>();

    foreach (var item in input)
    {
        item.SetPropertyValue("Updated", true);
        replacementsTasks.Add(_documentClient.ReplaceDocumentAsync(item));
    }

    await Task.WhenAll(replacementsTasks);
}

How can I prevent this? Can I use an CosmosDB out result with the modified document from the trigger?

Update 1

I do not want to use another collection. This would double the pricing for CosmosDB. I tried the following with an CosmosDB in- and output. I get the same result however. Infinite loop.

[FunctionName(nameof(DownloadImages))]
public static void Run(
    [CosmosDBTrigger(
    databaseName: database,
    collectionName: collection,
    ConnectionStringSetting = connectionStringName,
    LeaseCollectionName = "leases",
    CreateLeaseCollectionIfNotExists = true,
    LeaseCollectionPrefix = nameof(MyFunction))]IReadOnlyList<Document> input,
    [CosmosDB(database, collection, Id = "id", ConnectionStringSetting = connectionStringName)] out dynamic document,
    ILogger log)
{

    if(input.Count != 1) throw new ArgumentException();

    document = input.Single();
    document.myValue = true;
}

Solution

  • If you cannot use another collection then there is really no other option. The Trigger effectively triggers when a new document is inserted or updated. If your Trigger is updating / inserting documents in the same collection it is monitoring, it will effectively create a loop.

    This is like using a QueueTrigger and inserting messages in the same Queue, the infinite loop applies to any Trigger mechanism.

    One thing you could do though, is to filter those already updated:

    private static DocumentClient _documentClient = new DocumentClient(new Uri(serviceEndpoint), key);
    
    [FunctionName(nameof(MyFunction))]
    public static async Task RunAsync([CosmosDBTrigger(
        databaseName: "MyDatabase",
        collectionName: "MyCollection",
        ConnectionStringSetting = "MyDbConnectionString",
        LeaseCollectionName = "leases",
        CreateLeaseCollectionIfNotExists = true,
        LeaseCollectionPrefix = nameof(MyFunction))]IReadOnlyList<Document> input,
        ILogger log)
    {
        var replacementsTasks = new List<Task>();
    
        foreach (var item in input)
        {
            if (!item.GetPropertyValue<bool>("Updated")) {
                item.SetPropertyValue("Updated", true);
                replacementsTasks.Add(_documentClient.ReplaceDocumentAsync(item));
            }
        }
    
        await Task.WhenAll(replacementsTasks);
    }