From 8959621da7d9e0696e530448fc028c0089c2609a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 12 Jul 2020 14:45:52 -0400 Subject: [PATCH 1/3] Fix EF Core memory leak --- .../Users/UserManager.cs | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 47d514b1a4..c8d7fa769a 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -23,6 +23,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations.Users @@ -86,7 +87,18 @@ namespace Jellyfin.Server.Implementations.Users public event EventHandler>? OnUserLockedOut; /// - public IEnumerable Users => _dbProvider.CreateContext().Users; + public IEnumerable Users + { + get + { + using var dbContext = _dbProvider.CreateContext(); + return dbContext.Users.Include(user => user.Permissions) + .Include(user => user.Preferences) + .Include(user => user.AccessSchedules) + .Include(user => user.ProfileImage) + .ToList(); + } + } /// public IEnumerable UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id); @@ -99,7 +111,12 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Guid can't be empty", nameof(id)); } - return _dbProvider.CreateContext().Users.Find(id); + using var dbContext = _dbProvider.CreateContext(); + return dbContext.Users.Include(user => user.Permissions) + .Include(user => user.Preferences) + .Include(user => user.AccessSchedules) + .Include(user => user.ProfileImage) + .FirstOrDefault(user => user.Id == id); } /// @@ -110,7 +127,13 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Invalid username", nameof(name)); } - return _dbProvider.CreateContext().Users.ToList() + using var dbContext = _dbProvider.CreateContext(); + + return dbContext.Users.Include(user => user.Permissions) + .Include(user => user.Preferences) + .Include(user => user.AccessSchedules) + .Include(user => user.ProfileImage) + .ToList() .FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)); } @@ -127,7 +150,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Invalid username", nameof(newName)); } - if (user.Username.Equals(newName, StringComparison.Ordinal)) + if (user.Username.Equals(newName, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("The new and old names must be different."); } @@ -149,7 +172,7 @@ namespace Jellyfin.Server.Implementations.Users /// public void UpdateUser(User user) { - var dbContext = _dbProvider.CreateContext(); + using var dbContext = _dbProvider.CreateContext(); dbContext.Users.Update(user); dbContext.SaveChanges(); } @@ -157,7 +180,7 @@ namespace Jellyfin.Server.Implementations.Users /// public async Task UpdateUserAsync(User user) { - var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); dbContext.Users.Update(user); await dbContext.SaveChangesAsync().ConfigureAwait(false); @@ -171,7 +194,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); } - var dbContext = _dbProvider.CreateContext(); + using var dbContext = _dbProvider.CreateContext(); // TODO: Remove after user item data is migrated. var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0; @@ -194,8 +217,12 @@ namespace Jellyfin.Server.Implementations.Users /// public void DeleteUser(Guid userId) { - var dbContext = _dbProvider.CreateContext(); - var user = dbContext.Users.Find(userId); + using var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Include(u => u.Permissions) + .Include(u => u.Preferences) + .Include(u => u.AccessSchedules) + .Include(u => u.ProfileImage) + .FirstOrDefault(u => u.Id == userId); if (user == null) { throw new ResourceNotFoundException(nameof(userId)); @@ -380,7 +407,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentNullException(nameof(username)); } - var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); bool success; IAuthenticationProvider? authenticationProvider; @@ -408,8 +435,7 @@ namespace Jellyfin.Server.Implementations.Users // Search the database for the user again // the authentication provider might have created it - user = Users - .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) { @@ -546,7 +572,7 @@ namespace Jellyfin.Server.Implementations.Users public void Initialize() { // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. - var dbContext = _dbProvider.CreateContext(); + using var dbContext = _dbProvider.CreateContext(); if (dbContext.Users.Any()) { @@ -698,7 +724,7 @@ namespace Jellyfin.Server.Implementations.Users /// public void ClearProfileImage(User user) { - var dbContext = _dbProvider.CreateContext(); + using var dbContext = _dbProvider.CreateContext(); dbContext.Remove(user.ProfileImage); dbContext.SaveChanges(); } From befd0c7a00d0129d85db95d9ddf8e9d96df79d0a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 13 Jul 2020 12:49:20 -0400 Subject: [PATCH 2/3] Remove EF Core Proxies --- Jellyfin.Data/Jellyfin.Data.csproj | 1 - Jellyfin.Server/CoreAppHost.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 282ea511cf..58d1ba2f36 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -21,7 +21,6 @@ - diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index fe07411a68..207eaa98d1 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -66,8 +66,7 @@ namespace Jellyfin.Server // TODO: Set up scoping and use AddDbContextPool serviceCollection.AddDbContext( options => options - .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}") - .UseLazyLoadingProxies(), + .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), ServiceLifetime.Transient); serviceCollection.AddSingleton(); From b468ae2aea3b42a3073a81c2566dd19a313dc3fa Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 13 Jul 2020 14:09:20 -0400 Subject: [PATCH 3/3] Use AsEnumerable for UserManager.Users --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 4ed0b7501a..6283a1bca5 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -96,7 +96,7 @@ namespace Jellyfin.Server.Implementations.Users .Include(user => user.Preferences) .Include(user => user.AccessSchedules) .Include(user => user.ProfileImage) - .ToList(); + .AsEnumerable(); } }