C# 使用Katana OpenID Connect中间件解决OnSendingHeaders死锁

C# 使用Katana OpenID Connect中间件解决OnSendingHeaders死锁,c#,async-await,task-parallel-library,owin,system.web,C#,Async Await,Task Parallel Library,Owin,System.web,我正在尝试使用Katana项目提供的OpenID Connect身份验证中间件 在以下情况下,实现中存在导致死锁的错误: 在请求具有线程关联的主机(例如IIS)中运行 尚未检索OpenID Connect元数据文档或缓存副本已过期 应用程序调用身份验证方法 应用程序中发生的操作导致对响应流的写入 发生死锁的原因是身份验证中间件处理来自主机的回调的方式,主机发出发送头的信号。问题的根源在于这种方法: private static void OnSendingHeaderCallback(objec

我正在尝试使用Katana项目提供的OpenID Connect身份验证中间件

在以下情况下,实现中存在导致死锁的错误:

  • 在请求具有线程关联的主机(例如IIS)中运行
  • 尚未检索OpenID Connect元数据文档或缓存副本已过期
  • 应用程序调用身份验证方法
  • 应用程序中发生的操作导致对响应流的写入
  • 发生死锁的原因是身份验证中间件处理来自主机的回调的方式,主机发出发送头的信号。问题的根源在于这种方法:

    private static void OnSendingHeaderCallback(object state)
    {
        AuthenticationHandler handler = (AuthenticationHandler)state;
        handler.ApplyResponseAsync().Wait();
    }
    

    Task.Wait()
    的调用只有在返回的
    Task
    已经完成时才是安全的,而对于OpenID Connect中间件,则没有这样做

    中间件使用的实例来管理其配置的缓存副本。这是一个非同步实现,使用
    信号量lim
    作为异步锁和HTTP文档检索器来获取配置。我怀疑这是死锁
    Wait()
    调用的触发器

    我怀疑这就是原因所在:

    public async Task<T> GetConfigurationAsync(CancellationToken cancel)
    {
        DateTimeOffset now = DateTimeOffset.UtcNow;
        if (_currentConfiguration != null && _syncAfter > now)
        {
            return _currentConfiguration;
        }
    
        await _refreshLock.WaitAsync(cancel);
        try
        {
            Exception retrieveEx = null;
            if (_syncAfter <= now)
            {
                try
                {
                    // Don't use the individual CT here, this is a shared operation that shouldn't be affected by an individual's cancellation.
                    // The transport should have it's own timeouts, etc..
    
                    _currentConfiguration = await _configRetriever.GetConfigurationAsync(_metadataAddress, _docRetriever, CancellationToken.None);
                    Contract.Assert(_currentConfiguration != null);
                    _lastRefresh = now;
                    _syncAfter = DateTimeUtil.Add(now.UtcDateTime, _automaticRefreshInterval);
                }
                catch (Exception ex)
                {
                    retrieveEx = ex;
                    _syncAfter = DateTimeUtil.Add(now.UtcDateTime, _automaticRefreshInterval < _refreshInterval ? _automaticRefreshInterval : _refreshInterval);
                }
            }
    
            if (_currentConfiguration == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10803, _metadataAddress ?? "null"), retrieveEx);
            }
    
            // Stale metadata is better than no metadata
            return _currentConfiguration;
        }
        finally
        {
            _refreshLock.Release();
        }
    }
    
    公共异步任务GetConfigurationAsync(CancellationToken cancel) { DateTimeOffset now=DateTimeOffset.UtcNow; 如果(_currentConfiguration!=null&&u syncAfter>now) { 返回当前配置; } wait\u refreshLock.WaitAsync(取消); 尝试 { 异常retrieveEx=null; 如果(_syncAfter@Tragedian 我们对此问题采取了这些修复措施。 您是否可以更新并查看问题是否仍然存在(我们原以为用184解决了问题,但正如您所看到的,我们用185解决了问题)。另一位客户成功地使用了最新的nuget


    我无法对公认的答案发表评论,但即使有了这个具体的数字,这个问题似乎对我来说依然存在:/

    我发现我需要修改ConfigurationManager#GetConfigurationAsync行:

    await _refreshLock.WaitAsync(cancel);
    

    或者,我在这两个调用上都放置一个ConfigureAwait(false),并将“GetConfigurationAsync”包装到另一个方法中,该方法使用“.Result”调用阻塞,并在新的已完成任务中返回它

    如果我这样做,那么对于超过1个用户,注销时的死锁将不再发生(之前的修复解决了单个用户注销的问题)


    然而,这显然使“GetConfigurationAsync”方法完全同步://

    我不希望这会改变任何事情,但请尝试
    GetAwaiter().GetResult()
    而不是
    Wait()
    @PauloMorgado不幸的是,
    OnSendingHeaders
    代码被深埋在
    AuthenticationHandler
    类中,无法更改。或者,尝试等待
    任务.IsComplete
    为true。用于等待而不切换上下文。是否可以更改
    OnSendingHeaderCallback
    ?否,它在
    中定义>Microsoft.Owin.Security.Infrastructure.AuthenticationHandler
    。我只有一个简单的测试机会。它通过了我的初始测试方案,但我需要做更多的工作来验证我不能再创建死锁。当我有机会正确验证修复程序时,我将接受此答案。您的测试方案是什么?您是如何测试的正确吗?对于您建议的代码更改,我仍然遇到了死锁。我所做的是-更改为
    \u refreshLock.Wait(cancel);
    Wait\u configurationretriever.GetConfigurationAsync(…).configuratewait(false);
    。但是,这阻止了我出现死锁-不确定代码是否正确。
    _refreshLock.Wait(cancel);
    
    _currentConfiguration = await _configRetriever.GetConfigurationAsync(_metadataAddress, _docRetriever, CancellationToken.None)
    
    _currentConfiguration =  _configRetriever.GetConfigurationAsync(_metadataAddress, _docRetriever, CancellationToken.None).Result;