StackExchange.Redis死锁 [英] StackExchange.Redis Deadlocking

查看:674
本文介绍了StackExchange.Redis死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我用 StackExchange.Redis (SE.R今后)在我的南希的应用程序。有一个单一的全球 ConnectionMultiplexer 这就会自动围绕由南希的 TinyIoC 通过构造函数参数传递,任何时候我尝试使用 GetDatabase *异步方法(同步方法只启动失败的异步方法之一已经尝试后的一个)我的应用程序死锁。

I'm using StackExchange.Redis (SE.R henceforth) in my Nancy application. There is a single global ConnectionMultiplexer that gets automatically passed around by Nancy's TinyIoC via constructor parameters, and any time I try and use GetDatabase and one of the *Async methods (sync methods only start failing after one of the async methods have been attempted) my application deadlocks.

看着我的平行叠层看来我有四个主题:

Looking at my parallel stacks it appears that I have four threads:


  1. 调用线程结果我的任务之一,它使用SE.R. (有很多的东西,南希在堆栈上,然后我的图书馆,利用SE.R一个电话,为结果顶部堆叠的是<$ C $通话C> Monitor.Wait )。

  2. 这催生了另外两个线程的线程。我认为这是由SE.R.管理以管理过渡, ThreadHelper.ThreadStart ,并在堆栈的顶部是本机启动> ThreadHelper.ThreadStart_Context 。

  3. 被卡住像这样的小堆栈:

    • Monitor.Wait

    • Monitor.Wait

    • SocketManager.WriteAllQueues

    • SocketManager.cctor.AnonymousMethod__16

  1. The thread that called Result on one of my Tasks that uses SE.R. (Has lots of Nancy stuff on the stack, then a call to my library that utilizes SE.R, and a call to Result. Top of the stack is Monitor.Wait).
  2. A thread that spawned two other threads. I assume this is managed by SE.R. Starts with Native to Managed Transition, ThreadHelper.ThreadStart, and at the top of the stack is ThreadHelper.ThreadStart_Context.
  3. A small stack that is stuck like this:
    • Monitor.Wait
    • Monitor.Wait
    • SocketManager.WriteAllQueues
    • SocketManager.cctor.AnonymousMethod__16

  • 托管到本机过渡

  • SocketManager.ReadImpl

  • SocketManager.Read

  • SocketManager.cctor.AnonymousMethod__19

  • Managed to Native Transition
  • SocketManager.ReadImpl
  • SocketManager.Read
  • SocketManager.cctor.AnonymousMethod__19

我几乎可以肯定这是某种形式的僵局。我甚至认为它可能是与<一个href=\"http://stackoverflow.com/questions/25567566/timeout-exception-after-async-commands-and-task-whenany-awaits-in-stackexchange?rq=1\">this问题。但我不知道该怎么办才好。

I'm almost sure this is a deadlock of some sort. I even think it might have something to do with this question. But I have no idea what to do about it.

ConnectionMultiplexer 设置在南希 IRegistrations 通过以下code:

The ConnectionMultiplexer is set up in a Nancy IRegistrations with the following code:

var configOpts =  new ConfigurationOptions {
  EndPoints = {
    RedisHost,
  },
  Password = RedisPass,
  AllowAdmin = false,
  ClientName = ApplicationName,
  ConnectTimeout = 10000,
  SyncTimeout = 5000,
};
var mux = ConnectionMultiplexer.Connect(configOpts);
yield return new InstanceRegistration(typeof (ConnectionMultiplexer), mux);

MUX 是获取所有code共享实例,要求它在其构造参数列表。

mux is the instance that gets shared by all code requesting it in their constructor parameter list.

我有一类称为 SchemaCache 。一小块它(包含code抛出错误的问题)如下:

I have a class called SchemaCache. A small piece of it (that includes the code that throws the error in question) follows:

public SchemaCache(ConnectionMultiplexer connectionMultiplexer) {
    ConnectionMultiplexer = connectionMultiplexer;
}

private ConnectionMultiplexer ConnectionMultiplexer { get; set; }

private async Task<string[]> Cached(string key, bool forceFetch, Func<string[]> fetch) {
    var db = ConnectionMultiplexer.GetDatabase();

    return forceFetch || !await db.KeyExistsAsync(key)
        ? await CacheSetSet(db, key, await Task.Run(fetch))
        : await CacheGetSet(db, key);
}

private static async Task<string[]> CacheSetSet(IDatabaseAsync db, string key, string[] values) {
    await db.KeyDeleteAsync(key);
    await db.SetAddAsync(key, EmptyCacheSentinel);

    var keysSaved = values
        .Append(EmptyCacheSentinel)
        .Select(val => db.SetAddAsync(key, val))
        .ToArray()
        .Append(db.KeyExpireAsync(key, TimeSpan.FromDays(1)));
    await Task.WhenAll(keysSaved);

    return values;
}

private static async Task<string[]> CacheGetSet(IDatabaseAsync db, string key) {
    var results = await db.SetMembersAsync(key);
    return results.Select(rv => (string) rv).Without(EmptyCacheSentinel).ToArray();
}

// There are a bunch of these public methods:
public async Task<IEnumerable<string>> UseCache1(bool forceFetch = false) {
    return await Cached("the_key_i_want", forceFetch, () => {
        using (var cnn = MakeConnectionToDatabase("server", "databaseName")) {
            // Uses Dapper:
            return cnn.Query<string>("--expensive sql query").ToArray();
        }
    });
}

我也有一个类,使得这一用途的方法需要一些从缓存中的信息:

I also have a class that makes uses of this in a method requiring some of the information from the cache:

public OtherClass(SchemaCache cache) {
    Cache = cache;
}

private SchemaCache Cache { get; set; }

public Result GetResult(Parameter parameter) {
    return Cache.UseCache1().Result
        .Where(r => Cache.UseCache2(r).Result.Contains(parameter))
        .Select(r => CheckResult(r))
        .FirstOrDefault(x => x != null);
}

所有细上述作品LinqPad那里的问题都只有一个实例。相反,它失败了 TimeoutException异常(后来的关于无可用的连接除外)。唯一的区别是,我通过依赖注入获得高速缓存的实例,我pretty确保南希使用任务并行的要求。

All of the above works fine in LinqPad where there's only one instance of everything in question. Instead it fails with a TimeoutException (and later an exception about no available connection). The only difference is that I get an instance of the cache via dependency injection, and I'm pretty sure Nancy uses Tasks to parallelize the requests.

推荐答案

在这里,我们去:

return Cache.UseCache1().Result

热潮;僵局。但无关StackExchange.Redis。

boom; deadlock. But nothing to do with StackExchange.Redis.

至少,从最同步上下文提供者。这是因为你的等待 s的所有隐含要求同步上下文激活 - 这可能意味着在UI线程(的WinForms,WPF),或者在当前指定的工线(WCF,ASP.NET,MVC等)。这里的问题是,此线程将永远不会可用来处理这些项,因为。结果是同步和阻塞调用。因此,你没有完成回调将得到处理,因为那都不可能处理它们唯一的线程在等待他们使自己可用之前完成。

At least, from most sync-context providers. This is because your awaits are all implicitly requesting sync-context activation - which can mean "on the UI thread" (winforms, WPF), or "on the currently designated worker thread" (WCF, ASP.NET, MVC, etc). The problem here is that this thread will never be available to process those items, because .Result is a synchronous and blocking call. Thus none of your completion callbacks will get processed, because the only thread that can possibly process them is waiting for them to finish before making itself available.

请注意:StackExchange.Redis的使用同步上下文;它明确地同步上下文断开,以避免被死锁的原因(这是正常的,建议库)。关键的一点是,你的code的

Note: StackExchange.Redis does not use the sync-context; it explicitly disconnects from sync-context to avoid being the cause of deadlocks (this is normal and recommended for libraries). The key point is that your code doesn't.

选项:


  • 请不要混用异步。结果 / .Wait()

  • 有你的等待电话(或至少是下面 .UseCache1())显式调用<$的C $ C> .ConfigureAwait(假)! - 但是请注意,这意味着落成将不会出现在调用上下文

  • don't mix async and .Result / .Wait(), or
  • have all of your await calls (or at least those underneath .UseCache1()) explicitly call .ConfigureAwait(false) - note, however, that this means that the completions will not occur in the calling context!

第一种选择是简单的;如果可以隔离呼叫的树不依赖于同步上下文那么第二种方法是可行的。

The first option is the simplest; if you can isolate a tree of calls that do not depend on sync-context then the second approach is viable.

这是一个非常普遍的问题; <一href=\"http://stackoverflow.com/questions/13621647/using-async-even-if-it-should-complete-as-part-of-a-mvc-route-deadlocks-the\">I做pretty同样的事情。

This is a very common problem; I did pretty much the same thing.

这篇关于StackExchange.Redis死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆