From bff66fb786c8f373fd8f03ca050b392e1ad81a2b Mon Sep 17 00:00:00 2001 From: jamesmoore Date: Sun, 8 Mar 2026 07:35:35 +0000 Subject: [PATCH 1/6] add locks around transaction closing --- SDMeta/Cache/SqliteDataSource.cs | 50 +++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/SDMeta/Cache/SqliteDataSource.cs b/SDMeta/Cache/SqliteDataSource.cs index cee6beb..16a877d 100644 --- a/SDMeta/Cache/SqliteDataSource.cs +++ b/SDMeta/Cache/SqliteDataSource.cs @@ -1,4 +1,4 @@ -using Dapper; +using Dapper; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging; using System; @@ -12,6 +12,7 @@ public partial class SqliteDataSource : IImageFileDataSource const string TableName = "PngFilesv2"; private string FTSTableName = $"FTS5{TableName}"; private SqliteTransaction? transaction; + private readonly object transactionLock = new(); private readonly string[] columns = [ @@ -75,13 +76,18 @@ private T ExecuteOnConnection(Func func) { if (this.transaction?.Connection != null) { - return func(transaction.Connection); - } - else - { - using var connection = GetConnection(); - return func(connection); + lock (transactionLock) + { + var currentTransaction = this.transaction; + if (currentTransaction?.Connection != null) + { + return func(currentTransaction.Connection); + } + } } + + using var connection = GetConnection(); + return func(connection); } private string GetInsertSql() @@ -257,18 +263,35 @@ private DataRow FromModel(ImageFile info) public void BeginTransaction() { - this.transaction ??= GetConnection().BeginTransaction(); + lock (transactionLock) + { + this.transaction ??= GetConnection().BeginTransaction(); + } } public void CommitTransaction() { - if (this.transaction != null) + SqliteTransaction? transactionToCommit; + + lock (transactionLock) { - var connection = this.transaction.Connection; - this.transaction.Commit(); - this.transaction.Dispose(); + transactionToCommit = this.transaction; this.transaction = null; - connection?.Close(); + } + + if (transactionToCommit == null) + { + return; + } + + try + { + transactionToCommit.Commit(); + } + finally + { + var connection = transactionToCommit.Connection; + transactionToCommit.Dispose(); connection?.Dispose(); } } @@ -333,3 +356,4 @@ public static string ToCommaSeparated(this IEnumerable list) internal record struct ColumnDefinition(string Column, string Parameter, string DataType, bool IsPrimaryKey); } + From 10a31011901472ea955d5d354557f91270511681 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:22:48 +0000 Subject: [PATCH 2/6] Initial plan From 0995b53bac256aeec6c9d7cfc3d3ef7ae2357b40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:26:59 +0000 Subject: [PATCH 3/6] Fix transaction synchronization: pass captured transaction to ExecuteOnConnection callback Co-authored-by: jamesmoore <6506748+jamesmoore@users.noreply.github.com> --- SDMeta/Cache/SqliteDataSource.cs | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/SDMeta/Cache/SqliteDataSource.cs b/SDMeta/Cache/SqliteDataSource.cs index 16a877d..b456f3c 100644 --- a/SDMeta/Cache/SqliteDataSource.cs +++ b/SDMeta/Cache/SqliteDataSource.cs @@ -72,7 +72,7 @@ private SqliteConnection GetConnection() return connection; } - private T ExecuteOnConnection(Func func) + private T ExecuteOnConnection(Func func) { if (this.transaction?.Connection != null) { @@ -81,13 +81,13 @@ private T ExecuteOnConnection(Func func) var currentTransaction = this.transaction; if (currentTransaction?.Connection != null) { - return func(currentTransaction.Connection); + return func(currentTransaction.Connection, currentTransaction); } } } using var connection = GetConnection(); - return func(connection); + return func(connection, null); } private string GetInsertSql() @@ -108,11 +108,11 @@ public void Initialize() var tabledef = GetTableDefinition(); // Setup table if absent https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/types - ExecuteOnConnection(connection => connection.Execute(@$"CREATE TABLE IF NOT EXISTS {TableName} ( + ExecuteOnConnection((connection, _) => connection.Execute(@$"CREATE TABLE IF NOT EXISTS {TableName} ( {tabledef.Select(p => $"{p.Column} {p.DataType}{(p.IsPrimaryKey ? " PRIMARY KEY" : "")}").ToCommaSeparated()} );")); - ExecuteOnConnection(connection => connection.Execute(@$"CREATE VIRTUAL TABLE IF NOT EXISTS {FTSTableName} USING fts5({ftscolumns.ToCommaSeparated()});")); + ExecuteOnConnection((connection, _) => connection.Execute(@$"CREATE VIRTUAL TABLE IF NOT EXISTS {FTSTableName} USING fts5({ftscolumns.ToCommaSeparated()});")); logger.LogInformation("Initalization completed"); } @@ -141,7 +141,7 @@ public IEnumerable Query(QueryParams queryParams) modelHash = queryParams.ModelFilter?.ModelHash, }; - var reader = ExecuteOnConnection(connection => + var reader = ExecuteOnConnection((connection, _) => connection.Query(sql, param) ); return reader; @@ -218,7 +218,7 @@ private static string BuildOrderByClause(QuerySortBy querySort) public ImageFile? ReadImageFile(string realFileName) { - var reader = ExecuteOnConnection(connection => connection.QueryFirstOrDefault( + var reader = ExecuteOnConnection((connection, _) => connection.QueryFirstOrDefault( $@"SELECT * FROM {TableName} WHERE FileName = @FileName @@ -236,10 +236,10 @@ private static string BuildOrderByClause(QuerySortBy querySort) public void WriteImageFile(ImageFile info) { - ExecuteOnConnection(connection => connection.Execute( + ExecuteOnConnection((connection, tx) => connection.Execute( insertSql.Value, FromModel(info), - this.transaction + tx )); } @@ -298,7 +298,7 @@ public void CommitTransaction() public IEnumerable GetModelSummaryList() { - var reader = ExecuteOnConnection(connection => connection.Query( + var reader = ExecuteOnConnection((connection, _) => connection.Query( $@"SELECT Model, ModelHash, Count(*) as Count FROM {TableName} GROUP BY Model, ModelHash @@ -310,7 +310,7 @@ ORDER BY 3 DESC" public IEnumerable GetAllFilenames() { - var reader = ExecuteOnConnection(connection => connection.Query( + var reader = ExecuteOnConnection((connection, _) => connection.Query( $@"SELECT Filename FROM {TableName} WHERE [Exists] = 1" @@ -321,20 +321,20 @@ public IEnumerable GetAllFilenames() public void Truncate() { - ExecuteOnConnection(connection => connection.Execute($"DELETE FROM {TableName}")); - ExecuteOnConnection(connection => connection.Execute($"DELETE FROM {FTSTableName}")); + ExecuteOnConnection((connection, _) => connection.Execute($"DELETE FROM {TableName}")); + ExecuteOnConnection((connection, _) => connection.Execute($"DELETE FROM {FTSTableName}")); } public void PostUpdateProcessing() { - ExecuteOnConnection(connection => + ExecuteOnConnection((connection, tx) => connection.Execute( $@"INSERT INTO {FTSTableName} (FileName, Prompt, PromptFormat, Version) SELECT FileName, Prompt, PromptFormat, Version FROM {TableName} WHERE FileName NOT IN (SELECT FileName from {FTSTableName})", - this.transaction)); + tx)); - ExecuteOnConnection(connection => + ExecuteOnConnection((connection, tx) => connection.Execute( $@"UPDATE {FTSTableName} SET Prompt = p.Prompt, @@ -342,7 +342,7 @@ WHERE FileName NOT IN (SELECT FileName from {FTSTableName})", Version = p.Version FROM {TableName} p WHERE {FTSTableName}.FileName = p.FileName and {FTSTableName}.Version != p.Version", - this.transaction)); + tx)); } } From 4a4be81746ac73f3cf7f394423ab74124b84a803 Mon Sep 17 00:00:00 2001 From: jamesmoore Date: Wed, 11 Mar 2026 06:53:46 +0000 Subject: [PATCH 4/6] use Lock --- SDMeta/Cache/SqliteDataSource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SDMeta/Cache/SqliteDataSource.cs b/SDMeta/Cache/SqliteDataSource.cs index b456f3c..13ae550 100644 --- a/SDMeta/Cache/SqliteDataSource.cs +++ b/SDMeta/Cache/SqliteDataSource.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace SDMeta.Cache { @@ -12,7 +13,7 @@ public partial class SqliteDataSource : IImageFileDataSource const string TableName = "PngFilesv2"; private string FTSTableName = $"FTS5{TableName}"; private SqliteTransaction? transaction; - private readonly object transactionLock = new(); + private readonly Lock transactionLock = new(); private readonly string[] columns = [ From fffb9365f0e014f8c5e64465c67b70898ec11073 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 07:08:50 +0000 Subject: [PATCH 5/6] Initial plan From c7ac43eda2fc2ed2243cbdd9f7c161965a653e93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 07:12:13 +0000 Subject: [PATCH 6/6] Fix unsynchronized transaction read in ExecuteOnConnection Co-authored-by: jamesmoore <6506748+jamesmoore@users.noreply.github.com> --- SDMeta/Cache/SqliteDataSource.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/SDMeta/Cache/SqliteDataSource.cs b/SDMeta/Cache/SqliteDataSource.cs index 13ae550..0c52edf 100644 --- a/SDMeta/Cache/SqliteDataSource.cs +++ b/SDMeta/Cache/SqliteDataSource.cs @@ -75,15 +75,12 @@ private SqliteConnection GetConnection() private T ExecuteOnConnection(Func func) { - if (this.transaction?.Connection != null) + lock (transactionLock) { - lock (transactionLock) + var currentTransaction = this.transaction; + if (currentTransaction?.Connection != null) { - var currentTransaction = this.transaction; - if (currentTransaction?.Connection != null) - { - return func(currentTransaction.Connection, currentTransaction); - } + return func(currentTransaction.Connection, currentTransaction); } }