1

I'm using Polly version 7.1.1

I have a simple Polly retry policy that will retry an operation once if it fails with an NpgsqlException:

var policy = Policy
    .Handle<NpgsqlException>()
    .Retry(
        retryCount: 1,
        onRetry: (exception, retryCount, context) =>
        {
            _logger.Log(LogLevel.Warning, $"Retry {retryCount} of {context.OperationKey}, due to {exception.Message}", exception);
        })
    .WithPolicyKey("PostgresConnectionPolicy");

I have a method that attempts to connect to a PostgreSQL database and run a query using said policy:

using (var conn = new NpgsqlConnection("myConnectionString"))
{
    conn.Open()

    using(var command = GetCommand())
    {
        policy.Execute(
            action: context => command.ExecuteNonQuery(),
            contextData: new Context("Command.ExecuteNonQuery"));
    }
}

The method fails, gets retried, and the logger prints the following:

Retry 1 of , due to 42P01: relation "myDatabase" does not exist

I would expect the following to be logged:

Retry 1 of Command.ExecuteNonQuery, due to 42P01: relation "myDatabase" does not exist

Why is OperationKey empty when the policy logs the retry?

EDIT: Adding a simplified console application as an example.

This prints the following to the console:

Retry 1 of , due to Testing Polly
Error.

Example:

using Polly;
using System;

namespace TestPolly
{
    class Program
    {
        static void Main(string[] args)
        {
            var policy = Policy
                .Handle<Exception>()
                .Retry(
                    retryCount: 1,
                    onRetry: (exception, retryCount, context) =>
                    {
                        Console.WriteLine($"Retry {retryCount} of {context.OperationKey}, due to {exception.Message}");
                    })
                .WithPolicyKey("PostgresConnectionPolicy");

            try
            {
                policy.Execute(
                    action: context => TestPolly(),
                    contextData: new Context("TestPolly"));
            }
            catch (Exception e)
            {
                Console.WriteLine("Error.");
            }

            Console.ReadKey();
        }

        private static void TestPolly()
        {
            throw new Exception("Testing Polly");
        }
    }
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Marcus
  • 6,697
  • 11
  • 46
  • 89

2 Answers2

1

Got around this issue by using a custom context instead.

Policy:

var policy = Policy
            .Handle<NpgsqlException>()
            .Retry(
                retryCount: 1,
                onRetry: (exception, retryCount, context) =>
                {
                    _logger.Log(LogLevel.Warning, $"Retry {retryCount} of {context["Operation"]}, due to {exception.Message}", exception);
                })
            .WithPolicyKey("PostgresConnectionPolicy");

Execute:

policy.Execute(
    action: context => command.ExecuteNonQuery(),
    contextData: new Context() { { "Operation", "Command.ExecuteNonQuery()" } });
Marcus
  • 6,697
  • 11
  • 46
  • 89
1

The Context object

  • defines some predefined fields (like OperationKey, PolicyKey, CorrelationId)
  • and it implements the IDictionary<string, object> interface to let the consumer provide any key-value pairs.

If you look at this line of the source code or the documentation you can see that there is a constructor overload which accepts the OperationKey.

So you define your Execute call like this:

policy.Execute(
    action: context => command.ExecuteNonQuery(),
    contextData: new Context("Command.ExecuteNonQuery()"));

In my opinion it is a bit better approach since you are not relying on string-based key insertion and retrieval.


UPDATE: I've found the problem.

You have been using a wrong overload of Execute.

policy.Execute(
    action: context => command.ExecuteNonQuery(),
    context: new Context("Command.ExecuteNonQuery()"));

Use context instead of contextData and the OperationKey will present.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • This is exactly what I did, as you can see in my question, but OperationKey is empty in the policy retry callback. – Marcus Oct 01 '20 at 13:38
  • I've added a copy/paste example for you – Marcus Oct 01 '20 at 13:44
  • @Marcus You are right. I've tried it with 7.x, 6.x and 5.x versions and all the time it was null. I haven't found any open or closed issue about this behaviour, but maybe you should raise one. – Peter Csala Oct 01 '20 at 14:41
  • 1
    I will. Thanks though! – Marcus Oct 01 '20 at 14:47
  • 1
    @Marcus I've found the problem. Your sample is using a wrong overload. Please use `context:` instead of `contextData:` and the `OperationKey` will present. I've updated my answer accordingly. – Peter Csala Oct 02 '20 at 08:44