C# 使用Katana OpenID Connect中间件解决OnSendingHeaders死锁
我正在尝试使用Katana项目提供的OpenID Connect身份验证中间件 在以下情况下,实现中存在导致死锁的错误: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
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;