Scheduled signal to Durable Entity fires immediately in Azure Durable Functions

440 views Asked by At

I want to have a very lightweight approach where I expose an HTTP endpoint (trigger) that somewhat exposes a Durable Entity (actor) and has the actor perform work on a frequent basis.

The logic in my HTTP trigger is like this:

  1. Check if entity with uid already exists
  2. If not, signal entity and fire InitializeMonitoringAsync method on the actor
  3. If yes, call entity state on the actor and return some values
  4. Once initialized, the actor (durable entity) should frequently call the RefreshActionAsync on its own.

The issue I have is that the Refresh method seems to keep executing constantly, without the configured delays to be taken into account. What am I missing?

Update (workaround):

Apparently, when I change the interface methods (that now return a Task) to methods that return void (and I change the signal call to : e => e.RefreshActionAsync()), then it seems to work. But I don't understand why, and this doesn't seem to be a documented limitation either.

The code below:

Entity interface

public interface IReproEntity
{
    Task RefreshActionAsync();
    Task InitializeMonitoringAsync();
}

Entity function

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class ReproEntity : IReproEntity
{
    [JsonProperty] public bool Activated { get; set; }
    [JsonProperty] public int LoopCount { get; set; }

    public Task InitializeMonitoringAsync()
    {
        ScheduleNext(DateTime.UtcNow.AddSeconds(10));
        Activated = true;
        return Task.CompletedTask;
    }

    public Task RefreshActionAsync()
    {
        LoopCount += 1;
        ScheduleNext();
        return Task.CompletedTask;
    }

    private void ScheduleNext(DateTime nextSchedule = default)
    {
        if (nextSchedule == default) nextSchedule = DateTime.UtcNow.AddSeconds(30);
        Entity.Current.SignalEntity<IReproEntity>(Entity.Current.EntityId, nextSchedule,
            async (e) => await e.RefreshActionAsync());
    }

    [FunctionName(nameof(ReproEntity))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
    {
        return ctx.DispatchAsync<ReproEntity>();
    }
}

HTTP Trigger function

[FunctionName("Repro_HttpStart")]
public async Task<IActionResult> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "repro/{uid}")]
    HttpRequest req, string uid,
    [DurableClient] IDurableEntityClient entityClient,
    ILogger log)
{
    var entityId = new EntityId(nameof(ReproEntity), uid);
    var entityState = await entityClient.ReadEntityStateAsync<ReproEntity>(entityId);
    if (!(entityState.EntityExists && entityState.EntityState.Activated))
    {
        await entityClient.SignalEntityAsync(entityId, nameof(IReproEntity.InitializeMonitoringAsync));

        return new NotFoundObjectResult(new {error = $"The actor {uid} does not exist"});
    }

    return new OkObjectResult(new
    {
        entityState.EntityState.LoopCount, entityState.EntityState.Activated
    });
}
0

There are 0 answers