I tried debugging through the code and it seems to repro mainly when multiple clients are trying to modify the same key in a transaction. Retrying the transaction usually gets rid of the error, but is there any reason why the exception is thrown in the first place?

The code I'm trying to execute is pretty straightforward:

var existingValue = db.HashGetAsync(hashKey, field);
var t = db.CreateTransaction();
t.AddCondition(Condition.HashEqual(hashKey, field, existingValue));
t.HashSetAsync(hashKey, field, newValue, flags: CommandFlags.FireAndForget);
bool succeeded = await t.ExecuteAsync(); // StackExchange.Redis.RedisConnectionException thrown intermittently
1

There are 1 best solutions below

0
On

This exception occurs when you are trying to update the same key from 2 different threads simultaneously. If you use one ConnectionMultiplexer per application (as recomended) it will occur only when key is accessed from different applications or hosts.

When you transactionally update value you should retry if update fails (transaction.ExecuteAsync() returns false or "Unexpected response to EXEC: MultiBulk: 0 items" exception is thrown).

Here is a method that transactionally updates string value:

    public async Task<string> UpdateValueAsync(string key, Func<string, string> updateAction)
    {
        for (int i = 0; i < UpdateRetryCount; i++)
        {
            var oldValue = await database.StringGetAsync(key);

            if (oldValue.IsNull)
                throw new InvalidOperationException(string.Format("Key \"{0}\" not found.", key));

            var newValue = updateAction(oldValue);

            var transaction = database.CreateTransaction();
            transaction.AddCondition(Condition.StringEqual(key, oldValue));
            transaction.StringSetAsync(key, newValue);

            try
            {
                if (await transaction.ExecuteAsync())
                {
                    return newValue;
                }
            }
            catch (RedisConnectionException exception)
            {
                if (exception.Message != "Unexpected response to EXEC: MultiBulk: 0 items")
                {
                    throw;
                }
            }
        }

        throw new InvalidOperationException(string.Format("Failed to update value in key {0}.", key));
    }