英文:
Why socket handles are leaked
问题
以下是翻译好的部分:
我有一个部署到Azure App Service的.NET Core Web应用程序。API只有一个端点,它的功能是调用另一个API(在MyCLass
内部)并返回响应。
我在我的Startup中有以下代码:
services.AddScoped<IOAuthService, OAuthService>();
services.AddHttpClient<IMyClass, MyClass>(client =>
{
var authConfig = config.Get<OAuthConfig>();
var oAuthService = services.BuildServiceProvider().GetRequiredService<IOAuthService>();
var token = oAuthService.GetTokenAsync(authConfig).GetAwaiter().GetResult();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
以下是OAuthService.GetTokenAsync
方法:
private IConfidentialClientApplication app;
public async Task<string> GetTokenAsync(OAuthConfig config)
{
app ??= ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId(config.TenantId)
.WithClientSecret(config.ClientSecret)
.WithLegacyCacheCompatibility(false) // 不需要与ADAL.NET共享; 增加性能
.Build();
var scopes = new string[] { $"{config.Resource}/.default" };
var authResult = await app.AcquireTokenForClient(scopes)
.ExecuteAsync()
.ConfigureAwait(false);
return authResult.AccessToken;
}
一个HttpClient
被注入到MyClass
中,在那里它调用另一个API并返回响应。上面的代码导致了套接字句柄泄漏和SNAT端口耗尽的问题。
然而,以下更改将解决这个问题:1. 不要在DI中获取令牌,而是在每次发出请求时在MyCLass
中动态获取令牌(不需要对OAuthService
进行任何更改)。 2. 将上述DI代码更改为以下内容:
services.AddSingleton<IOAuthService, OAuthService>();
services.AddHttpClient<IHdcApiDataConnector, HdcApiDataConnector>();
我的问题是,为什么第二段代码可以解决套接字句柄泄漏的问题?
英文:
I have a .Net Core web app deployed to Azure App Service. The API has only one endpoint, and all it does is call another API (inside MyCLass
) and return the response.
I have the following in my Startup:
services.AddScoped<IOAuthService, OAuthService>();
services.AddHttpClient<IMyClass, MyClass>(client =>
{
var authConfig = config.Get<OAuthConfig>();
var oAuthService = services.BuildServiceProvider().GetRequiredService<IOAuthService>();
var token = oAuthService.GetTokenAsync(authConfig).GetAwaiter().GetResult();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
And here is the OAuthService.GetTokenAsync
method:
private IConfidentialClientApplication app;
public async Task<string> GetTokenAsync(OAuthConfig config)
{
app ??= ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId(config.TenantId)
.WithClientSecret(config.ClientSecret)
.WithLegacyCacheCompatibility(false) // No need to share with ADAL.NET; increases performance
.Build();
var scopes = new string[] { $"{config.Resource}/.default" };
var authResult = await app.AcquireTokenForClient(scopes)
.ExecuteAsync()
.ConfigureAwait(false);
return authResult.AccessToken;
}
A HttpClient
is injected into MyClass
where it calls another API and returns the response. The above code results in socket handle leaks and SNAT port exhaustion.
However, the following changes would solve the problem: 1. Instead of getting the token once in DI, get the token in MyCLass
on the fly every time a request is made (without any changes to OAuthService
). 2. Changing the above DI code to the following:
services.AddSingleton<IOAuthService, OAuthService>();
services.AddHttpClient<IHdcApiDataConnector, HdcApiDataConnector>();
My question is why exactly the second code solves the socket handle leak problem?
答案1
得分: 1
答案我的问题。
经过一些调查,我找出了发生了什么:
- 使用
??==
是为了确保只创建一次ConfidentialClientApplicationBuilder
。然而,将IOAuthService
注册为作用域意味着每个传入的请求都会创建一个新实例,因此在每个实例中创建一个ConfidentialClientApplicationBuilder
。在第二种方法中将其设置为单例将解决此问题。 services.AddHttpClient<IMyClass, MyClass>
将IMyClass
注册为瞬态。这意味着每个传入请求都会创建一个新的MyClass
实例。此外,对于每个这样的实例,代码services.BuildServiceProvider().GetRequiredService<IOAuthService>()
创建另一个IOAuthService
实例,从而导致上面提到的问题。
除了 DI 问题,我的代码还存在一些其他问题:
- 正如 @JoelCoehoorn 在评论中提到的,第一种方法中还有一个严重的缺陷:令牌会过期,需要刷新。
app ??= ConfidentialClientApplicationBuilder.Create
可能应该移到构造函数或甚至放在 DI 中,将这种逻辑(确保ConfidentialClientApplicationBuilder
是单例的)放在类方法内部非常令人困惑且容易出错。
此外,请注意,如果 ConfidentialClientApplicationBuilder
是单例的,那么令牌将在内部缓存,您不必自己实现令牌的 刷新(参见此链接)。
英文:
Answering my question.
Ok after some investigation, I found out what was happening:
- Using
??==
was an attempt to make sureConfidentialClientApplicationBuilder
is created only once. However, registeringIOAuthService
as scoped means a new instance is created for every incoming request and hence oneConfidentialClientApplicationBuilder
is created in each instance. Making it singleton in the second approach would solve this issue. services.AddHttpClient<IMyClass, MyClass>
registersIMyClass
as transient. This means a new instance ofMyClass
is created for each incoming request. Further, for every such instance, the codeservices.BuildServiceProvider().GetRequiredService<IOAuthService>()
creates another instance ofIOAuthService
causing the problem mentioned above.
Aside from the DI issue, there are some other issues with my code above:
- As @JoelCoehoorn mentioned in the comments, there is another big flaw in the first approach: tokens expire and should be refreshed.
app ??= ConfidentialClientApplicationBuilder.Create
should probably be moved to either the constructor or even in DI, it's very confusing and error-prone to have that kind of logic (making sure theConfidentialClientApplicationBuilder
is singleton) inside the class method.
Further, note that if the ConfidentialClientApplicationBuilder
is singleton, then the tokens are cached under the hood and you don't have to implement refreshing tokens yourself (see this).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论