We ran into an issue in our hosted environment where multiple users calling /Account/ResetPassword
caused SQL connections to stay idle in the connection pool instead of being released.
During user onboarding, we require all users of a tenant to reset their password. After a recent onboarding, we saw a spike in usage of the Identity and Authentication server. Both share the Identity database connection string (from the ABP microservice template).
Over a period of time, the session count on the Identity database kept increasing without ever going down. This build-up eventually exhausted the connection pool and broke our app until we restarted it.
What we saw:
Every call to /Account/ResetPassword
runs a query that gets translated to SQL like this:
Even under low usage, sessions used by this query remain idle for extended periods instead of being released back to the pool.
Impact:
- Multiple calls to
/Account/ResetPassword
cause sessions to accumulate. - Eventually, the Authentication server cannot obtain a connection because the pool is full.
Repro steps: I was able to reproduce this locally on SQL Server by:
- Setting a fixed Max Pool Size in the Identity database connection string (to simulate our hosted DB limits).
- Running a simple load test with k6 or another tool against GET /Account/ResetPassword.
- Observing the database sessions steadily increase without decreasing.
- Eventually hitting the same error we saw in our hosted environment:
Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool.
This may have occurred because all pooled connections were in use and max pool size was reached.
Is there a way to prevent this issue without having to raise our database limits?
thanks
14 Answer(s)
-
0
The issue of SQL connections remaining idle and not being released in the connection pool during high usage of
/Account/ResetPassword
is typically related to improper disposal of DbContext or uncompleted Unit of Work (UoW) scopes in ABP-based applications.In ABP, database connections are managed by the Unit of Work system. If a UoW is not completed or disposed properly (for example, due to an unawaited async call, an exception, or a long-running operation that never finishes), the underlying DbContext and its SQL connection may remain open, causing connection pool exhaustion.
To prevent this issue:
- Ensure all application service and controller methods that interact with the database are
async
and properly awaited. - Never block on async code (e.g., avoid
.Result
or.Wait()
on async methods). - Always complete or dispose the Unit of Work. In ABP, this is handled automatically for application services and controllers, but custom UoW usage (e.g.,
IUnitOfWorkManager.Begin()
) must be completed withawait uow.CompleteAsync()
. - Check for any custom middleware, filters, or background jobs that might be holding onto DbContext or UoW instances longer than necessary.
- Review your code for any exceptions that might prevent UoW completion, especially in the password reset flow.
- Ensure all application service and controller methods that interact with the database are
For more details, see:
- ABP Entity Framework Core Connection Management
- ABP Unit of Work Documentation
- Example of a related password reset flow and tenant handling: ABP Support: Reset password - Invalid token
If you have custom code in your password reset logic (such as custom UoW or DbContext usage), ensure it follows the above best practices. If the problem persists, consider enabling detailed logging for UoW and DbContext lifetimes to identify where connections are not being released.
Sources:
- https://abp.io/docs/latest/Entity-Framework-Core#connection-management
- https://abp.io/docs/latest/Unit-Of-Work
- https://abp.io/support/questions/1326
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
-
0
That query seems to be initiated within the ABP codebase so we don't have an easy control over it
-
0
hi
GET /Account/ResetPassword.
Can you share the
curl
info of this request?
I checked the code, and it just queries the user and updates the password. No other operations.
It should not have any database problems.
Can you enable EF Core Debug logs and share the
logs.txt
file?var loggerConfiguration = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Debug) .Enrich.FromLogContext() .WriteTo.Async(c => c.File("Logs/logs.txt"))
liming.ma@volosoft.com
See https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
Thanks.
-
0
Log traces sent to your mail address,
Thanks
-
0
Thanks. I will check your logs.
-
0
hi
Can you also share the source code of
FortisIaResetPasswordModel(FortisIA.AuthServer.Pages.Account.FortisIaResetPasswordModel)
?Thanks.
-
0
Hello,
The code has been sent to your email.
Thanks
-
0
Thanks. I will check it.
-
0
hi
Can you try setting
includeDetails
tofalse
duringResetPasswordAsync
and then try again?var user = await IdentityUserRepository.GetAsync(input.UserId, includeDetails: false);
using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Emailing; using Volo.Abp.Account.PhoneNumber; using Volo.Abp.BlobStoring; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Imaging; using Volo.Abp.SettingManagement; namespace MyCompanyName.MyProjectName; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(AccountAppService), typeof(IAccountAppService))] public class MyAccountAppService : AccountAppService { protected IIdentityUserRepository IdentityUserRepository { get; set; } public MyAccountAppService( IdentityUserManager userManager, IAccountEmailer accountEmailer, IAccountPhoneService phoneService, IIdentityRoleRepository roleRepository, IdentitySecurityLogManager identitySecurityLogManager, IBlobContainer<AccountProfilePictureContainer> accountProfilePictureContainer, ISettingManager settingManager, IOptions<IdentityOptions> identityOptions, IIdentitySecurityLogRepository securityLogRepository, IImageCompressor imageCompressor, IOptions<AbpProfilePictureOptions> profilePictureOptions, IApplicationInfoAccessor applicationInfoAccessor, IdentityUserTwoFactorChecker identityUserTwoFactorChecker, IDistributedCache<EmailConfirmationCodeCacheItem> emailConfirmationCodeCache, IdentityErrorDescriber identityErrorDescriber, IOptions<AbpRegisterEmailConfirmationCodeOptions> registerEmailConfirmationCodeOptions, IIdentityUserRepository identityUserRepository) : base(userManager, accountEmailer, phoneService, roleRepository, identitySecurityLogManager, accountProfilePictureContainer, settingManager, identityOptions, securityLogRepository, imageCompressor, profilePictureOptions, applicationInfoAccessor, identityUserTwoFactorChecker, emailConfirmationCodeCache, identityErrorDescriber, registerEmailConfirmationCodeOptions) { IdentityUserRepository = identityUserRepository; } public override async Task ResetPasswordAsync(ResetPasswordDto input) { await IdentityOptions.SetAsync(); var user = await IdentityUserRepository.GetAsync(input.UserId, includeDetails: false); (await UserManager.ResetPasswordAsync(user, input.ResetToken, input.Password)).CheckErrors(); await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.ChangePassword }); } }
Thanks
-
0
[maliming] said: hi
Can you try setting
includeDetails
tofalse
duringResetPasswordAsync
and then try again?var user = await IdentityUserRepository.GetAsync(input.UserId, includeDetails: false);
using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Emailing; using Volo.Abp.Account.PhoneNumber; using Volo.Abp.BlobStoring; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Imaging; using Volo.Abp.SettingManagement; namespace MyCompanyName.MyProjectName; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(AccountAppService), typeof(IAccountAppService))] public class MyAccountAppService : AccountAppService { protected IIdentityUserRepository IdentityUserRepository { get; set; } public MyAccountAppService( IdentityUserManager userManager, IAccountEmailer accountEmailer, IAccountPhoneService phoneService, IIdentityRoleRepository roleRepository, IdentitySecurityLogManager identitySecurityLogManager, IBlobContainer<AccountProfilePictureContainer> accountProfilePictureContainer, ISettingManager settingManager, IOptions<IdentityOptions> identityOptions, IIdentitySecurityLogRepository securityLogRepository, IImageCompressor imageCompressor, IOptions<AbpProfilePictureOptions> profilePictureOptions, IApplicationInfoAccessor applicationInfoAccessor, IdentityUserTwoFactorChecker identityUserTwoFactorChecker, IDistributedCache<EmailConfirmationCodeCacheItem> emailConfirmationCodeCache, IdentityErrorDescriber identityErrorDescriber, IOptions<AbpRegisterEmailConfirmationCodeOptions> registerEmailConfirmationCodeOptions, IIdentityUserRepository identityUserRepository) : base(userManager, accountEmailer, phoneService, roleRepository, identitySecurityLogManager, accountProfilePictureContainer, settingManager, identityOptions, securityLogRepository, imageCompressor, profilePictureOptions, applicationInfoAccessor, identityUserTwoFactorChecker, emailConfirmationCodeCache, identityErrorDescriber, registerEmailConfirmationCodeOptions) { IdentityUserRepository = identityUserRepository; } public override async Task ResetPasswordAsync(ResetPasswordDto input) { await IdentityOptions.SetAsync(); var user = await IdentityUserRepository.GetAsync(input.UserId, includeDetails: false); (await UserManager.ResetPasswordAsync(user, input.ResetToken, input.Password)).CheckErrors(); await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.ChangePassword }); } }
Thanks
Hello,
The ResetPasswordAsync method is called when POSTing the password. In my case, the issue I have can be reproduced only by loading the
/Account/ResetPassword
page which will only call theAccountAppService.VerifyPasswordResetTokenAsync
. TheAccountAppService.ResetPasswordAsync
won't be called in that flow -
0
ok, I will share a new version code snippet
Thanks.
-
0
hi
Can you try this?
using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Emailing; using Volo.Abp.Account.PhoneNumber; using Volo.Abp.BlobStoring; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Imaging; using Volo.Abp.SettingManagement; namespace MyCompanyName.MyProjectName; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(AccountAppService), typeof(IAccountAppService))] public class MyAccountAppService : AccountAppService { protected IIdentityUserRepository IdentityUserRepository { get; set; } public MyAccountAppService( IdentityUserManager userManager, IAccountEmailer accountEmailer, IAccountPhoneService phoneService, IIdentityRoleRepository roleRepository, IdentitySecurityLogManager identitySecurityLogManager, IBlobContainer<AccountProfilePictureContainer> accountProfilePictureContainer, ISettingManager settingManager, IOptions<IdentityOptions> identityOptions, IIdentitySecurityLogRepository securityLogRepository, IImageCompressor imageCompressor, IOptions<AbpProfilePictureOptions> profilePictureOptions, IApplicationInfoAccessor applicationInfoAccessor, IdentityUserTwoFactorChecker identityUserTwoFactorChecker, IDistributedCache<EmailConfirmationCodeCacheItem> emailConfirmationCodeCache, IdentityErrorDescriber identityErrorDescriber, IOptions<AbpRegisterEmailConfirmationCodeOptions> registerEmailConfirmationCodeOptions, IIdentityUserRepository identityUserRepository) : base(userManager, accountEmailer, phoneService, roleRepository, identitySecurityLogManager, accountProfilePictureContainer, settingManager, identityOptions, securityLogRepository, imageCompressor, profilePictureOptions, applicationInfoAccessor, identityUserTwoFactorChecker, emailConfirmationCodeCache, identityErrorDescriber, registerEmailConfirmationCodeOptions) { IdentityUserRepository = identityUserRepository; } public override async Task<bool> VerifyPasswordResetTokenAsync(VerifyPasswordResetTokenInput input) { var user = await IdentityUserRepository.GetAsync(input.UserId, includeDetails: false); return await UserManager.VerifyUserTokenAsync( user, UserManager.Options.Tokens.PasswordResetTokenProvider, UserManager<IdentityUser>.ResetPasswordTokenPurpose, input.ResetToken); } public override async Task ResetPasswordAsync(ResetPasswordDto input) { await IdentityOptions.SetAsync(); var user = await IdentityUserRepository.GetAsync(input.UserId, includeDetails: false); (await UserManager.ResetPasswordAsync(user, input.ResetToken, input.Password)).CheckErrors(); await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = IdentitySecurityLogActionConsts.ChangePassword }); } }
-
0
Thanks for the snippet.
The snippet does seem to help reduce the number of queries being made, but those queries still create sessions that remain idle in the connection pool until they’re removed 4–8 minutes later, as described here: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-connection-pooling#remove-connections
I reran my load test 3–4 times both with and without
includeDetails: false
, and the results were almost always the same. WithoutincludeDetails: false
, I consistently see about twice as many sessions being opened and then idled in the pool compared to when I useincludeDetails: false
.I also ran the load tests on other endpoints not directly related to Identity, and I observed the same behavior.
Do you have any idea if there might be connections that aren’t being properly disposed somewhere?
Thanks
-
0
Hi
I can confirm the problem is related to your database. It is not code problem.
Can you test your app with local db?
Thanks.