2

I'm trying to add automated deletions to expired reliable dictionary objects and it looks like I have to implement my own way according to this: https://stackoverflow.com/a/36466890/7293543

My approach was to use the "RunAsync" task and have it constantly running a while loop. It worked for the first few times but I'm getting a weird error once I added a few more objects to my dictionary. Is this a stupid approach? How are other people automatically clearing their reliable dictionary objects?

protected override async Task RunAsync(CancellationToken cancellationToken)
{
    while (true)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await deleteExpired("MyDictionaryName", cancellationToken);
    }
}

private async Task deleteExpired(string dictionaryName, CancellationToken cancellationToken)
{
    var myDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<string, CacheObject>>(dictionaryName);
    Dictionary<string, CacheObject> ret = new Dictionary<string, CacheObject>();

    using (var tx = StateManager.CreateTransaction())
    {
        var count = myDictionary.GetCountAsync(tx);

        if (count.Result > 0)
        {
            IAsyncEnumerator<KeyValuePair<string, CacheObject>> e = (await myDictionary.CreateEnumerableAsync(tx)).GetAsyncEnumerator();

            while (await e.MoveNextAsync(cancellationToken))
            {
                if (e.Current.Value.expiration <= DateTime.Now)
                {
                    await myDictionary.TryRemoveAsync(tx, e.Current.Key);
                    await tx.CommitAsync();
                    ServiceEventSource.Current.ServiceMessage(this.Context, String.Format("Object deleted at {0} - key: {1} expired at {2}", DateTime.Now.ToString(), e.Current.Key, e.Current.Value.expiration.ToString()));
                }
            }
        }
    }
}

Error: The error occurs on "while(true)" after I added a few reliable dictionary objects into my dictionary.

Managed Debugging Assistant 'FatalExecutionEngineError' has detected a problem in 'C:\SfDevCluster\Data_App_Node_2\CacheApplicationType_App339\CachePkg.Code.1.0.0\Cache.exe'.

Additional information: The runtime has encountered a fatal error. The address of the error was at 0x8c46ed90, on thread 0x1980. The error code is 0x80131623. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

Community
  • 1
  • 1
TheSugoiBoi
  • 160
  • 1
  • 13
  • I don't see any `while (true)` in your code and how do you know that the error occurs at `while (true)` when it apparently is a fatal error in your process and not a .NET exception with a stack trace that allows you to understand what part of your code that failed? I don't think there is anything in your code that can create an error like that so my guess is that the error is not related to the code that you have posted or you have hit a bug in Service Fabric. – Martin Liversage Dec 28 '16 at 23:45
  • Not related to error, but 1.`CommitAsync` should be after inner while loop (you can't use transaction after commit). 2. Add some delay `await Task.Delay(...)` after `await deleteExpired..`. 3. It is not recommended to mix single entity and multi entity operations withing same transaction. – Aleksey L. Dec 29 '16 at 11:44
  • @MartinLiversage - "While (true)" is on the first line in the RunAsync method. I believe the errors occurs there because that's where visual studio is showing the error (I'll attach a screenshot later). – TheSugoiBoi Dec 29 '16 at 16:48
  • @AlekseyL.: Thanks for the advice! Can you elaborate on #3? – TheSugoiBoi Dec 29 '16 at 16:49
  • @AlekseyL. Thanks! I moved the await tx.CommitAsync() outside the while loop and added a counter so it'll only commit the transaction if the counter is more than 0 and after it's completely done with all the TryRemoveAsync(). I also added an await Task.Delay(60000) after await deleteExpired("MyDictionaryName", cancellationToken). That seemed to solve the issue! – TheSugoiBoi Dec 29 '16 at 19:09

1 Answers1

3

If the Reliable Dictionary contains more than one item ready to be expired, the above code keeps using the transaction after it is terminated (committed in the above case). This causes an assert in the enumeration since it is being used after the transaction it was bound to has terminated.

I will update the recommendation in Reliable Collections to

  • Do not use an enumeration after the transaction used to create is terminated (committed/aborted) or disposed.
  • Thanks! I moved the await tx.CommitAsync() outside the while loop and added a counter so it'll only commit the transaction if the counter is more than 0 and after it's completely done with all the TryRemoveAsync(). I also added an await Task.Delay(60000) after await deleteExpired("MyDictionaryName", cancellationToken). That seemed to solve the issue! – TheSugoiBoi Dec 29 '16 at 19:09