Skip to content

Commit

Permalink
fixed TSqlDBConnectionProperties.SharedTransaction thread-safety
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnaud Bouchez committed Jul 25, 2022
1 parent cb4be40 commit 56cee1d
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 53 deletions.
124 changes: 72 additions & 52 deletions src/db/mormot.db.sql.pas
Expand Up @@ -1191,6 +1191,13 @@ ESqlDBException = class(ESynException)
const aTableName: RawUtf8; const FieldNames: array of RawUtf8; Unique: boolean;
IndexName: RawUtf8; const SQL: RawUtf8): boolean of object;

/// internal in-memory structure used for transactions
TSqlDBConnectionTransaction = record
SessionID: cardinal;
RefCount: integer;
Connection: TSqlDBConnection;
end;

/// specify the class of TSqlDBConnectionProperties
// - sometimes used to create connection properties instances, from a set
// of available classes (see e.g. SynDBExplorer or sample 16)
Expand Down Expand Up @@ -1233,11 +1240,8 @@ TSqlDBConnectionProperties = class
fStatementCacheReplicates: integer;
fSqlCreateField: TSqlDBFieldTypeDefinition;
fSqlCreateFieldMax: cardinal;
fSharedTransactions: array of record
SessionID: cardinal;
RefCount: integer;
Connection: TSqlDBConnection;
end;
fSharedTransactionsSafe: TLightLock;
fSharedTransactions: array of TSqlDBConnectionTransaction;
fExecuteWhenConnected: TRawUtf8DynArray;
fForeignKeys: TSynNameValue;
fOnTableCreate: TOnTableCreate;
Expand Down Expand Up @@ -3572,68 +3576,84 @@ function TSqlDBConnectionProperties.NewThreadSafeStatementPrepared(

function TSqlDBConnectionProperties.SharedTransaction(SessionID: cardinal;
action: TSqlDBSharedTransactionAction): TSqlDBConnection;

procedure SetResultToSameConnection(index: PtrInt);
begin
result := ThreadSafeConnection;
if result <> fSharedTransactions[index].Connection then
raise ESqlDBException.CreateUtf8(
'%.SharedTransaction(sessionID=%) with mixed thread connections: % and %',
[self, SessionID, result, fSharedTransactions[index].Connection]);
end;

var
i, n: PtrInt;
found: boolean;
t: ^TSqlDBConnectionTransaction;
begin
n := Length(fSharedTransactions);
try
for i := 0 to n - 1 do
if fSharedTransactions[i].SessionID = SessionID then
begin
SetResultToSameConnection(i);
case action of
transBegin: // nested StartTransaction
LockedInc32(@fSharedTransactions[i].RefCount);
else
begin // (nested) commit/rollback
if InterlockedDecrement(fSharedTransactions[i].RefCount) = 0 then
result := ThreadSafeConnection;
// thread-safe found transactions support
fSharedTransactionsSafe.Lock;
try
n := Length(fSharedTransactions);
t := pointer(fSharedTransactions);
found := false;
for i := 0 to n - 1 do
if t^.SessionID = SessionID then
begin
if result <> t^.Connection then
raise ESqlDBException.CreateUtf8(
'%.SharedTransaction(sessionID=%) with mixed connections: % and %',
[self, SessionID, result, t^.Connection]);
if action = transBegin then
begin
// found StartTransaction
inc(t^.RefCount);
exit;
end
else
begin
// (found) commit/rollback
dec(t^.RefCount);
if t^.RefCount = 0 then
begin
dec(n);
MoveFast(fSharedTransactions[i + 1], fSharedTransactions[i],
(n - i) * SizeOf(fSharedTransactions[0]));
MoveFast(fSharedTransactions[i + 1], t^, (n - i) * SizeOf(t^));
SetLength(fSharedTransactions, n);
case action of
transCommitWithException,
transCommitWithoutException:
result.Commit;
transRollback:
result.Rollback;
end;
found := true;
end;
end;
end;
exit;
end;
break;
end
else
inc(t);
if not found then
if action = transBegin then
begin
t := pointer(fSharedTransactions);
for i := 1 to n do
if t^.Connection = result then
raise ESqlDBException.CreateUtf8(
'Dup %.SharedTransaction(sessionID=%,transBegin) sessionID=%',
[self, SessionID, t^.SessionID])
else
inc(t);
SetLength(fSharedTransactions, n + 1);
t := @fSharedTransactions[n];
t^.SessionID := SessionID;
t^.RefCount := 1;
t^.Connection := result;
end
else
raise ESqlDBException.CreateUtf8('Unexpected %.SharedTransaction(%,%)',
[self, SessionID, ord(action)]);
finally
fSharedTransactionsSafe.Unlock;
end;
// perform the actual transaction SQL operation outside the lock
case action of
transBegin:
begin
result := ThreadSafeConnection;
for i := 0 to n - 1 do
if fSharedTransactions[i].Connection = result then
raise ESqlDBException.CreateUtf8(
'%.SharedTransaction(sessionID=%) already started for sessionID=%',
[self, SessionID, fSharedTransactions[i].SessionID]);
if not result.Connected then
result.Connect;
result.StartTransaction;
SetLength(fSharedTransactions, n + 1);
fSharedTransactions[n].SessionID := SessionID;
fSharedTransactions[n].RefCount := 1;
fSharedTransactions[n].Connection := result;
end
else
raise ESqlDBException.CreateUtf8('Unexpected %.SharedTransaction(%,%)',
[self, SessionID, ord(action)]);
end;
transCommitWithException,
transCommitWithoutException:
result.Commit;
transRollback:
result.Rollback;
end;
except
on Exception do
Expand Down
2 changes: 1 addition & 1 deletion src/mormot.commit.inc
@@ -1 +1 @@
'2.0.3754'
'2.0.3755'

0 comments on commit 56cee1d

Please sign in to comment.