diff --git a/src/EntityFramework.Storage/IdentityServer4.EntityFramework.Storage.sln b/src/EntityFramework.Storage/IdentityServer4.EntityFramework.Storage.sln index 61e62eb183..7413f87056 100644 --- a/src/EntityFramework.Storage/IdentityServer4.EntityFramework.Storage.sln +++ b/src/EntityFramework.Storage/IdentityServer4.EntityFramework.Storage.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30204.135 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AF5DAC33-08AC-45EE-9772-4FF39FB09E57}" EndProject @@ -17,6 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlServer", "migrations\SqlServer\SqlServer.csproj", "{A93A59EC-D75D-44E1-9720-F75D9EF95BC3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "host", "host", "{07C56E10-A807-4372-ACD9-ADED2D099BC8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleHost", "host\ConsoleHost\ConsoleHost.csproj", "{2AA5AC6B-531B-426E-AD38-5B03F1949CF5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -75,6 +79,18 @@ Global {A93A59EC-D75D-44E1-9720-F75D9EF95BC3}.Release|x64.Build.0 = Release|Any CPU {A93A59EC-D75D-44E1-9720-F75D9EF95BC3}.Release|x86.ActiveCfg = Release|Any CPU {A93A59EC-D75D-44E1-9720-F75D9EF95BC3}.Release|x86.Build.0 = Release|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Debug|x64.ActiveCfg = Debug|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Debug|x64.Build.0 = Debug|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Debug|x86.Build.0 = Debug|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Release|Any CPU.Build.0 = Release|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Release|x64.ActiveCfg = Release|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Release|x64.Build.0 = Release|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Release|x86.ActiveCfg = Release|Any CPU + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -84,6 +100,7 @@ Global {8239FC82-46A3-4F21-8D05-1F0BE0B1B1FC} = {712ED94A-F982-4667-A9CE-E8F21900BBED} {E90F7470-C7F8-464B-9C28-87C474085812} = {712ED94A-F982-4667-A9CE-E8F21900BBED} {A93A59EC-D75D-44E1-9720-F75D9EF95BC3} = {E3EF31E0-6658-4899-8BDA-FF84355E2FDD} + {2AA5AC6B-531B-426E-AD38-5B03F1949CF5} = {07C56E10-A807-4372-ACD9-ADED2D099BC8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4DB894E3-1BF2-4410-911A-14D32FD79A96} diff --git a/src/EntityFramework.Storage/host/ConsoleHost/ConsoleHost.csproj b/src/EntityFramework.Storage/host/ConsoleHost/ConsoleHost.csproj new file mode 100644 index 0000000000..9906d53301 --- /dev/null +++ b/src/EntityFramework.Storage/host/ConsoleHost/ConsoleHost.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + diff --git a/src/EntityFramework.Storage/host/ConsoleHost/Program.cs b/src/EntityFramework.Storage/host/ConsoleHost/Program.cs new file mode 100644 index 0000000000..1a57e4d5d2 --- /dev/null +++ b/src/EntityFramework.Storage/host/ConsoleHost/Program.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; +using IdentityServer4.EntityFramework.Storage; +using Microsoft.EntityFrameworkCore; +using IdentityServer4.EntityFramework; + +namespace ConsoleHost +{ + class Program + { + static void Main(string[] args) + { + var connectionString = "server=(localdb)\\mssqllocaldb;database=IdentityServer4.EntityFramework-4.0.0;trusted_connection=yes;"; + + var services = new ServiceCollection(); + services.AddLogging(b => b.AddConsole().SetMinimumLevel(LogLevel.Trace)); + services.AddOperationalDbContext(options => + { + options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString); + + // this enables automatic token cleanup. this is optional. + options.EnableTokenCleanup = false; + options.TokenCleanupInterval = 5; // interval in seconds, short for testing + }); + + var sp = services.BuildServiceProvider(); + using (var scope = sp.CreateScope()) + { + var svc = scope.ServiceProvider.GetRequiredService(); + svc.RemoveExpiredGrantsAsync().GetAwaiter().GetResult(); + } + } + } +} diff --git a/src/EntityFramework.Storage/src/TokenCleanup/TokenCleanupService.cs b/src/EntityFramework.Storage/src/TokenCleanup/TokenCleanupService.cs index fd43c6121b..bba3840c00 100644 --- a/src/EntityFramework.Storage/src/TokenCleanup/TokenCleanupService.cs +++ b/src/EntityFramework.Storage/src/TokenCleanup/TokenCleanupService.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using IdentityServer4.EntityFramework.Entities; using IdentityServer4.EntityFramework.Interfaces; using IdentityServer4.EntityFramework.Options; using Microsoft.EntityFrameworkCore; @@ -85,25 +86,17 @@ protected virtual async Task RemoveGrantsAsync() if (found > 0) { _persistedGrantDbContext.PersistedGrants.RemoveRange(expiredGrants); - try - { - await _persistedGrantDbContext.SaveChangesAsync(); + await SaveChangesAsync(); - if (_operationalStoreNotification != null) - { - await _operationalStoreNotification.PersistedGrantsRemovedAsync(expiredGrants); - } - } - catch (DbUpdateConcurrencyException ex) + if (_operationalStoreNotification != null) { - // we get this if/when someone else already deleted the records - // we want to essentially ignore this, and keep working - _logger.LogDebug("Concurrency exception removing expired grants: {exception}", ex.Message); + await _operationalStoreNotification.PersistedGrantsRemovedAsync(expiredGrants); } } } } + /// /// Removes the stale device codes. /// @@ -126,23 +119,44 @@ protected virtual async Task RemoveDeviceCodesAsync() if (found > 0) { _persistedGrantDbContext.DeviceFlowCodes.RemoveRange(expiredCodes); - try - { - await _persistedGrantDbContext.SaveChangesAsync(); + await SaveChangesAsync(); - if (_operationalStoreNotification != null) - { - await _operationalStoreNotification.DeviceCodesRemovedAsync(expiredCodes); - } + if (_operationalStoreNotification != null) + { + await _operationalStoreNotification.DeviceCodesRemovedAsync(expiredCodes); } - catch (DbUpdateConcurrencyException ex) + } + } + } + + private async Task SaveChangesAsync() + { + var count = 3; + + while (count > 0) + { + try + { + await _persistedGrantDbContext.SaveChangesAsync(); + return; + } + catch (DbUpdateConcurrencyException ex) + { + count--; + + // we get this if/when someone else already deleted the records + // we want to essentially ignore this, and keep working + _logger.LogDebug("Concurrency exception removing expired grants: {exception}", ex.Message); + + foreach (var entry in ex.Entries) { - // we get this if/when someone else already deleted the records - // we want to essentially ignore this, and keep working - _logger.LogDebug("Concurrency exception removing expired grants: {exception}", ex.Message); + // mark this entry as not attached anymore so we don't try to re-delete + entry.State = EntityState.Detached; } } } + + _logger.LogDebug("Too many concurrency exceptions. Exiting."); } } } \ No newline at end of file