Use only 1 write connection/DB (#11986)

This commit is contained in:
Bond-009 2024-06-06 16:04:51 +02:00 committed by GitHub
parent 0d984b5162
commit cc4563a477
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 148 additions and 19 deletions

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -13,6 +14,8 @@ namespace Emby.Server.Implementations.Data
public abstract class BaseSqliteRepository : IDisposable public abstract class BaseSqliteRepository : IDisposable
{ {
private bool _disposed = false; private bool _disposed = false;
private SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
private SqliteConnection _writeConnection;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class. /// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
@ -98,9 +101,55 @@ namespace Emby.Server.Implementations.Data
} }
} }
protected SqliteConnection GetConnection(bool readOnly = false) protected ManagedConnection GetConnection(bool readOnly = false)
{ {
var connection = new SqliteConnection($"Filename={DbFilePath}" + (readOnly ? ";Mode=ReadOnly" : string.Empty)); if (!readOnly)
{
_writeLock.Wait();
if (_writeConnection is not null)
{
return new ManagedConnection(_writeConnection, _writeLock);
}
var writeConnection = new SqliteConnection($"Filename={DbFilePath};Pooling=False");
writeConnection.Open();
if (CacheSize.HasValue)
{
writeConnection.Execute("PRAGMA cache_size=" + CacheSize.Value);
}
if (!string.IsNullOrWhiteSpace(LockingMode))
{
writeConnection.Execute("PRAGMA locking_mode=" + LockingMode);
}
if (!string.IsNullOrWhiteSpace(JournalMode))
{
writeConnection.Execute("PRAGMA journal_mode=" + JournalMode);
}
if (JournalSizeLimit.HasValue)
{
writeConnection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value);
}
if (Synchronous.HasValue)
{
writeConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
}
if (PageSize.HasValue)
{
writeConnection.Execute("PRAGMA page_size=" + PageSize.Value);
}
writeConnection.Execute("PRAGMA temp_store=" + (int)TempStore);
return new ManagedConnection(_writeConnection = writeConnection, _writeLock);
}
var connection = new SqliteConnection($"Filename={DbFilePath};Mode=ReadOnly");
connection.Open(); connection.Open();
if (CacheSize.HasValue) if (CacheSize.HasValue)
@ -135,17 +184,17 @@ namespace Emby.Server.Implementations.Data
connection.Execute("PRAGMA temp_store=" + (int)TempStore); connection.Execute("PRAGMA temp_store=" + (int)TempStore);
return connection; return new ManagedConnection(connection, null);
} }
public SqliteCommand PrepareStatement(SqliteConnection connection, string sql) public SqliteCommand PrepareStatement(ManagedConnection connection, string sql)
{ {
var command = connection.CreateCommand(); var command = connection.CreateCommand();
command.CommandText = sql; command.CommandText = sql;
return command; return command;
} }
protected bool TableExists(SqliteConnection connection, string name) protected bool TableExists(ManagedConnection connection, string name)
{ {
using var statement = PrepareStatement(connection, "select DISTINCT tbl_name from sqlite_master"); using var statement = PrepareStatement(connection, "select DISTINCT tbl_name from sqlite_master");
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
@ -159,7 +208,7 @@ namespace Emby.Server.Implementations.Data
return false; return false;
} }
protected List<string> GetColumnNames(SqliteConnection connection, string table) protected List<string> GetColumnNames(ManagedConnection connection, string table)
{ {
var columnNames = new List<string>(); var columnNames = new List<string>();
@ -174,7 +223,7 @@ namespace Emby.Server.Implementations.Data
return columnNames; return columnNames;
} }
protected void AddColumn(SqliteConnection connection, string table, string columnName, string type, List<string> existingColumnNames) protected void AddColumn(ManagedConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
{ {
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase)) if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
{ {
@ -207,6 +256,24 @@ namespace Emby.Server.Implementations.Data
return; return;
} }
if (dispose)
{
_writeLock.Wait();
try
{
_writeConnection.Dispose();
}
finally
{
_writeLock.Release();
}
_writeLock.Dispose();
}
_writeConnection = null;
_writeLock = null;
_disposed = true; _disposed = true;
} }
} }

View File

@ -0,0 +1,62 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Data.Sqlite;
namespace Emby.Server.Implementations.Data;
public sealed class ManagedConnection : IDisposable
{
private readonly SemaphoreSlim? _writeLock;
private SqliteConnection _db;
private bool _disposed = false;
public ManagedConnection(SqliteConnection db, SemaphoreSlim? writeLock)
{
_db = db;
_writeLock = writeLock;
}
public SqliteTransaction BeginTransaction()
=> _db.BeginTransaction();
public SqliteCommand CreateCommand()
=> _db.CreateCommand();
public void Execute(string commandText)
=> _db.Execute(commandText);
public SqliteCommand PrepareStatement(string sql)
=> _db.PrepareStatement(sql);
public IEnumerable<SqliteDataReader> Query(string commandText)
=> _db.Query(commandText);
public void Dispose()
{
if (_disposed)
{
return;
}
if (_writeLock is null)
{
// Read connections are managed with an internal pool
_db.Dispose();
}
else
{
// Write lock is managed by BaseSqliteRepository
// Don't dispose here
_writeLock.Release();
}
_db = null!;
_disposed = true;
}
}

View File

@ -601,7 +601,7 @@ namespace Emby.Server.Implementations.Data
transaction.Commit(); transaction.Commit();
} }
private void SaveItemsInTransaction(SqliteConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples) private void SaveItemsInTransaction(ManagedConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
{ {
using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText)) using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText))
using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId")) using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId"))
@ -1980,7 +1980,7 @@ namespace Emby.Server.Implementations.Data
transaction.Commit(); transaction.Commit();
} }
private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, SqliteConnection db) private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, ManagedConnection db)
{ {
var startIndex = 0; var startIndex = 0;
var limit = 100; var limit = 100;
@ -4476,7 +4476,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
transaction.Commit(); transaction.Commit();
} }
private void ExecuteWithSingleParam(SqliteConnection db, string query, Guid value) private void ExecuteWithSingleParam(ManagedConnection db, string query, Guid value)
{ {
using (var statement = PrepareStatement(db, query)) using (var statement = PrepareStatement(db, query))
{ {
@ -4632,7 +4632,7 @@ AND Type = @InternalPersonType)");
return whereClauses; return whereClauses;
} }
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement) private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, ManagedConnection db, SqliteCommand deleteAncestorsStatement)
{ {
if (itemId.IsEmpty()) if (itemId.IsEmpty())
{ {
@ -5148,7 +5148,7 @@ AND Type = @InternalPersonType)");
return list; return list;
} }
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db) private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, ManagedConnection db)
{ {
if (itemId.IsEmpty()) if (itemId.IsEmpty())
{ {
@ -5167,7 +5167,7 @@ AND Type = @InternalPersonType)");
InsertItemValues(itemId, values, db); InsertItemValues(itemId, values, db);
} }
private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, SqliteConnection db) private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, ManagedConnection db)
{ {
const int Limit = 100; const int Limit = 100;
var startIndex = 0; var startIndex = 0;
@ -5239,7 +5239,7 @@ AND Type = @InternalPersonType)");
transaction.Commit(); transaction.Commit();
} }
private void InsertPeople(Guid id, List<PersonInfo> people, SqliteConnection db) private void InsertPeople(Guid id, List<PersonInfo> people, ManagedConnection db)
{ {
const int Limit = 100; const int Limit = 100;
var startIndex = 0; var startIndex = 0;
@ -5388,7 +5388,7 @@ AND Type = @InternalPersonType)");
transaction.Commit(); transaction.Commit();
} }
private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, SqliteConnection db) private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, ManagedConnection db)
{ {
const int Limit = 10; const int Limit = 10;
var startIndex = 0; var startIndex = 0;
@ -5772,7 +5772,7 @@ AND Type = @InternalPersonType)");
private void InsertMediaAttachments( private void InsertMediaAttachments(
Guid id, Guid id,
IReadOnlyList<MediaAttachment> attachments, IReadOnlyList<MediaAttachment> attachments,
SqliteConnection db, ManagedConnection db,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
const int InsertAtOnce = 10; const int InsertAtOnce = 10;

View File

@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private void ImportUserIds(SqliteConnection db, IEnumerable<User> users) private void ImportUserIds(ManagedConnection db, IEnumerable<User> users)
{ {
var userIdsWithUserData = GetAllUserIdsWithUserData(db); var userIdsWithUserData = GetAllUserIdsWithUserData(db);
@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private List<Guid> GetAllUserIdsWithUserData(SqliteConnection db) private List<Guid> GetAllUserIdsWithUserData(ManagedConnection db)
{ {
var list = new List<Guid>(); var list = new List<Guid>();
@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private static void SaveUserData(SqliteConnection db, long internalUserId, string key, UserItemData userData) private static void SaveUserData(ManagedConnection db, long internalUserId, string key, UserItemData userData)
{ {
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)")) using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
{ {