Skip to content

Commit dedc234

Browse files
committed
cleanup
- moved 'lock count' to the Locked<T> record - removed LockCount from TLightweightMREWEx because it was just an approximation and shouldn't be relied upon - more internal documentation
1 parent 4876ae1 commit dedc234

File tree

2 files changed

+92
-49
lines changed

2 files changed

+92
-49
lines changed

OtlOptions.inc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,14 @@
120120
{$DEFINE OTL_MobileSupport}
121121
{$IFEND}
122122

123-
{$IF CompilerVersion >= 30} //Seattle
123+
{$IF CompilerVersion >= 30} //10 Seattle
124124
{$DEFINE OTL_CanInlineOperators}
125125
{$IFEND}
126126

127+
{$IF CompilerVersion >= 35} //11 Alexandria
128+
{$DEFINE OTL_HasLightweightMREW}
129+
{$IFEND}
130+
127131
{$IFNDEF MSWINDOWS}
128132
{$IFNDEF OTL_MobileSupport}
129133
{$MESSAGE ERROR 'Only Windows compilation is supported on this Delphi version (at least XE8 is required for non-Windows platforms.'}

OtlSync.pas

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
/// - Added methods Enter: T and Leave to Locked<T>.
5151
/// - Added methods BeginRead, TryBeginRead, EndRead, BeginWrite,
5252
/// TryBeginWrite, EndWrite to Locked<T> (Delphi 11+ only).
53+
/// - Locked<T>.Access/Release now implement locking with a
54+
/// SRW lock (when available) in 'write' access mode. This is funcionally
55+
/// identical to the old implementation (critical section).
5356
/// 1.27d: 2023-11-28
5457
/// - Fixed hints & warnings.
5558
/// - Locked<T>.Initialize without the 'factory' parameter could return
@@ -384,10 +387,9 @@ TOmniResourceCount = class abstract(TInterfacedObject, IOmniResourceCount, IOm
384387
{$IFDEF OTL_HasLightweightMREW}
385388
TLightweightMREWEx = record
386389
private
387-
FRWLock : TLightweightMREW;
388-
FReadLockCount : TOmniAlignedInt32;
389-
FWriteLockCount : TOmniAlignedInt32;
390-
FLockOwner : TThreadID;
390+
FRWLock : TLightweightMREW;
391+
FWriteLockCount: TOmniAlignedInt32;
392+
FLockOwner : TThreadID;
391393
private
392394
function GetLockOwner: TThreadID; inline;
393395
procedure SetLockOwner(value: TThreadID); inline;
@@ -404,7 +406,6 @@ TLightweightMREWEx = record
404406
function TryBeginWrite(timeout: cardinal): boolean; overload;
405407
{$IFEND LINUX or ANDROID}
406408
procedure EndWrite;
407-
function IsLocked: boolean; inline;
408409
end; { TLightweightMREWEx }
409410

410411
ILightweightMREWEx = interface
@@ -418,7 +419,6 @@ TLightweightMREWEx = record
418419
function TryBeginWrite(timeout: cardinal): boolean; overload;
419420
{$IFEND LINUX or ANDROID}
420421
procedure EndWrite;
421-
function IsLocked: boolean;
422422
end; { ILightweightMREWEx }
423423

424424
TLightweightMREWExImpl = class(TInterfacedObject, ILightweightMREWEx)
@@ -435,7 +435,6 @@ TLightweightMREWExImpl = class(TInterfacedObject, ILightweightMREWEx)
435435
function TryBeginWrite(timeout: cardinal): boolean; overload;
436436
{$IFEND LINUX or ANDROID}
437437
procedure EndWrite;
438-
function IsLocked: boolean;
439438
end; { TLightweightMREWEx }
440439
{$ENDIF OTL_HasLightweightMREW}
441440

@@ -457,11 +456,16 @@ Atomic<I; T:constructor> = class
457456
Locked<T> = record
458457
strict private // keep those aligned!
459458
FLock : {$IFDEF OTL_HasLightweightMREW}ILightweightMREWEx{$ELSE}TOmniCS{$ENDIF};
459+
{$IFDEF DEBUG}
460+
FLockCount: IOmniCounter;
461+
{$ENDIF DEBUG}
460462
FValue : T;
461463
strict private
462464
FInitialized: boolean;
463465
FLifecycle : IInterface;
464466
FOwnsObject : boolean;
467+
procedure AssertLocked; inline;
468+
procedure AssertNotLocked; inline;
465469
procedure Clear; inline;
466470
function GetValue: T; inline;
467471
procedure SetValue(const value: T); inline;
@@ -1655,40 +1659,35 @@ class function Atomic<I,T>.Initialize(var storage: I): I;
16551659

16561660
function TLightweightMREWEx.GetLockOwner: TThreadID;
16571661
begin
1658-
{$IFDEF CPUX64}
1659-
Result := TThreadID(TInterlocked.Read(Int64(FLockOwner)));
1662+
{$IFDEF MSWINDOWS}
1663+
Result := InterlockedCompareExchange(integer(FLockOwner), 0, 0);
16601664
{$ELSE}
1661-
Result := TThreadID(TInterlocked.Read(Integer(FLockOwner)));
1662-
{$ENDIF}
1663-
MemoryBarrier;
1665+
Result := TInterlocked.Read(FLockOwner);
1666+
{$ENDIF ~MSWINDOWS}
16641667
end; { TLightweightMREWEx.GetLockOwner }
16651668

16661669
procedure TLightweightMREWEx.SetLockOwner(value: TThreadID);
16671670
begin
1668-
MemoryBarrier;
1669-
{$IFDEF CPUX64}
1670-
TInterlocked.Exchange(Int64(FLockOwner), Int64(value));
1671-
{$ELSE}
1672-
TInterlocked.Exchange(Integer(FLockOwner), Integer(value));
1673-
{$ENDIF}
1671+
Assert(CAS32(FLockOwner, value, FLockOwner), 'Failed to set new lock owner - check for race conditions!');
16741672
end; { TLightweightMREWEx.SetLockOwner }
16751673

16761674
class operator TLightweightMREWEx.Initialize(out dest: TLightweightMREWEx);
16771675
begin
16781676
Dest.SetLockOwner(0);
1679-
Dest.FReadLockCount.Value := 0;
16801677
Dest.FWriteLockCount.Value := 0;
16811678
end; { TLightweightMREWEx.Initialize }
16821679

16831680
procedure TLightweightMREWEx.BeginRead;
16841681
begin
16851682
FRWLock.BeginRead;
1686-
FReadLockCount.Increment;
16871683
end; { TLightweightMREWEx.BeginRead }
16881684

16891685
procedure TLightweightMREWEx.BeginWrite;
16901686
begin
16911687
if GetLockOwner = TThread.Current.ThreadID then
1688+
// We are already an owner so no need for locking.
1689+
// If another thread executes BeginWrite at this moment, it would enter
1690+
// the 'else' part below and block in the call to FRWLock.BeginWrite.
16921691
FWriteLockCount.Increment
16931692
else begin
16941693
FRWLock.BeginWrite;
@@ -1699,7 +1698,6 @@ procedure TLightweightMREWEx.BeginWrite;
16991698

17001699
procedure TLightweightMREWEx.EndRead;
17011700
begin
1702-
FReadLockCount.Decrement;
17031701
FRWLock.EndRead;
17041702
end; { TLightweightMREWEx.EndRead }
17051703

@@ -1714,16 +1712,9 @@ procedure TLightweightMREWEx.EndWrite;
17141712
end;
17151713
end; { TLightweightMREWEx.EndWrite }
17161714

1717-
function TLightweightMREWEx.IsLocked: boolean;
1718-
begin
1719-
Result := (FReadLockCount.Value > 0) or (FWriteLockCount.Value > 0);
1720-
end; { TLightweightMREWEx.IsLocked }
1721-
17221715
function TLightweightMREWEx.TryBeginRead: boolean;
17231716
begin
17241717
Result := FRWLock.TryBeginRead;
1725-
if Result then
1726-
FReadLockCount.Increment;
17271718
end; { TLightweightMREWEx.TryBeginRead }
17281719

17291720
{$IF defined(LINUX) or defined(ANDROID)}
@@ -1772,17 +1763,19 @@ function TLightweightMREWEx.TryBeginWrite(timeout: cardinal): boolean;
17721763

17731764
constructor Locked<T>.Create(const value: T; ownsObject: boolean);
17741765
begin
1766+
{$IFDEF OTL_HasLightweightMREW}
1767+
FLock := TLightweightMREWExImpl.Create;
1768+
{$ELSE ~OTL_HasLightweightMREW}
1769+
FLock.Initialize;
1770+
{$ENDIF ~OTL_HasLightweightMREW}
1771+
FLockCount := CreateCounter;
1772+
17751773
Clear;
17761774
FValue := value;
17771775
if ownsObject and (PTypeInfo(TypeInfo(T))^.Kind = tkClass) then
17781776
FLifecycle := CreateAutoDestroyObject(TObject(PPointer(@value)^));
17791777

17801778
FInitialized := true;
1781-
{$IFDEF OTL_HasLightweightMREW}
1782-
FLock := TLightweightMREWExImpl.Create;
1783-
{$ELSE ~OTL_HasLightweightMREW}
1784-
FLock.Initialize;
1785-
{$ENDIF ~OTL_HasLightweightMREW}
17861779
end; { Locked<T>.Create }
17871780

17881781
class operator Locked<T>.Implicit(const value: Locked<T>): T;
@@ -1802,24 +1795,56 @@ procedure Locked<T>.Acquire;
18021795
{$ELSE ~OTL_HasLightweightMREW}
18031796
FLock.Acquire;
18041797
{$ENDIF ~OTL_HasLightweightMREW}
1798+
{$IFDEF DEBUG}
1799+
FLockCount.Increment;
1800+
{$ENDIF DEBUG}
18051801
end; { Locked<T>.Acquire }
18061802

1803+
procedure Locked<T>.AssertLocked;
1804+
begin
1805+
// This is just a debugging helper. It catches most bad cases of accessing
1806+
// Locked<T>.Value while Locked<T> is not locked. It may fail (not detect a problem)
1807+
// in multithreading code where one thread may lock the Locked<T> and
1808+
// then another thread tries to access Locked<T>.Value.
1809+
{$IFDEF DEBUG}
1810+
Assert(FLockCount.Value > 0, 'Locked<T> is not locked!');
1811+
{$ENDIF DEBUG}
1812+
end; { Locked<T>.AssertLocked }
1813+
1814+
procedure Locked<T>.AssertNotLocked;
1815+
begin
1816+
// This is just a debugging helper. It catches probles in a single-threaded
1817+
// code. It may fail (not detect a problem) in a multithreaded code when
1818+
// one thread executes this test, another thread then locks the the value
1819+
// and first thread resumes execution.
1820+
{$IFDEF DEBUG}
1821+
Assert(FLockCount.Value = 0, 'Locked<T> is locked!');
1822+
{$ENDIF DEBUG}
1823+
end; { Locked<T>.AssertNotLocked }
1824+
18071825
{$IFDEF OTL_HasLightweightMREW}
18081826
function Locked<T>.BeginRead: T;
18091827
begin
18101828
FLock.BeginRead;
1829+
{$IFDEF DEBUG}
1830+
FLockCount.Increment;
1831+
{$ENDIF DEBUG}
18111832
Result := FValue;
18121833
end; { Locked<T>.BeginRead }
18131834

18141835
function Locked<T>.BeginWrite: T;
18151836
begin
18161837
FLock.BeginWrite;
1838+
{$IFDEF DEBUG}
1839+
FLockCount.Increment;
1840+
{$ENDIF DEBUG}
18171841
Result := FValue;
18181842
end; { Locked<T>.BeginWrite }
18191843
{$ENDIF OTL_HasLightweightMREW}
18201844

18211845
procedure Locked<T>.Clear;
18221846
begin
1847+
AssertNotLocked;
18231848
FLifecycle := nil;
18241849
FInitialized := false;
18251850
FValue := Default(T);
@@ -1830,11 +1855,17 @@ procedure Locked<T>.Clear;
18301855
procedure Locked<T>.EndRead;
18311856
begin
18321857
FLock.EndRead;
1858+
{$IFDEF DEBUG}
1859+
FLockCount.Decrement;
1860+
{$ENDIF DEBUG}
18331861
end; { Locked<T>.EndRead }
18341862

18351863
procedure Locked<T>.EndWrite;
18361864
begin
18371865
FLock.EndWrite;
1866+
{$IFDEF DEBUG}
1867+
FLockCount.Decrement;
1868+
{$ENDIF DEBUG}
18381869
end; { Locked<T>.EndWrite }
18391870
{$ENDIF OTL_HasLightweightMREW}
18401871

@@ -1862,21 +1893,13 @@ procedure Locked<T>.Free;
18621893

18631894
function Locked<T>.GetValue: T;
18641895
begin
1865-
{$IFDEF OTL_HasLightweightMREW}
1866-
Assert(FLock.IsLocked, 'Locked<T>.GetValue: Not locked');
1867-
{$ELSE ~OTL_HasLightweightMREW}
1868-
Assert(FLock.LockCount > 0, 'Locked<T>.GetValue: Not locked');
1869-
{$ENDIF ~OTL_HasLightweightMREW}
1896+
AssertLocked;
18701897
Result := FValue;
18711898
end; { Locked<T>.GetValue }
18721899

18731900
procedure Locked<T>.SetValue(const value: T);
18741901
begin
1875-
{$IFDEF OTL_HasLightweightMREW}
1876-
Assert(FLock.IsLocked, 'Locked<T>.SetValue: Not locked');
1877-
{$ELSE ~OTL_HasLightweightMREW}
1878-
Assert(FLock.LockCount > 0, 'Locked<T>.SetValue: Not locked');
1879-
{$ENDIF ~OTL_HasLightweightMREW}
1902+
AssertLocked;
18801903
FValue := value;
18811904
end; { Locked<T>.SetValue }
18821905

@@ -1888,6 +1911,8 @@ function Locked<T>.Initialize(factory: TFactory): T;
18881911
{$ELSE ~OTL_HasLightweightMREW}
18891912
FLock.Initialize;
18901913
{$ENDIF ~OTL_HasLightweightMREW}
1914+
FLockCount := CreateCounter;
1915+
18911916
Acquire;
18921917
try
18931918
if not FInitialized then begin
@@ -1944,6 +1969,9 @@ procedure Locked<T>.Leave;
19441969
{$ELSE ~OTL_HasLightweightMREW}
19451970
FLock.Release;
19461971
{$ENDIF ~OTL_HasLightweightMREW}
1972+
{$IFDEF DEBUG}
1973+
FLockCount.Decrement;
1974+
{$ENDIF DEBUG}
19471975
end; { Locked<T>.Leave }
19481976

19491977
procedure Locked<T>.Locked(proc: TProc);
@@ -1971,22 +1999,38 @@ procedure Locked<T>.Release;
19711999
function Locked<T>.TryBeginRead: boolean;
19722000
begin
19732001
Result := FLock.TryBeginRead;
2002+
{$IFDEF DEBUG}
2003+
if Result then
2004+
FLockCount.Increment;
2005+
{$ENDIF DEBUG}
19742006
end; { Locked<T>.TryBeginRead}
19752007

19762008
function Locked<T>.TryBeginWrite: boolean;
19772009
begin
19782010
Result := FLock.TryBeginWrite;
2011+
{$IFDEF DEBUG}
2012+
if Result then
2013+
FLockCount.Increment;
2014+
{$ENDIF DEBUG}
19792015
end; { Locked<T>.TryBeginWrite }
19802016

19812017
{$IF defined(LINUX) or defined(ANDROID)}
19822018
function Locked<T>.TryBeginRead(timeout: cardinal): boolean;
19832019
begin
19842020
Result := FLock.TryBeginRead(timeout);
2021+
{$IFDEF DEBUG}
2022+
if Result then
2023+
FLockCount.Increment;
2024+
{$ENDIF DEBUG}
19852025
end; { Locked<T>.TryBeginRead }
19862026

19872027
function Locked<T>.TryBeginWrite(timeout: cardinal): boolean; overload; inline;
19882028
begin
19892029
Result := FLock.TryBeginWrite(timeout);
2030+
{$IFDEF DEBUG}
2031+
if Result then
2032+
FLockCount.Increment;
2033+
{$ENDIF DEBUG}
19902034
end; { Locked<T>.TryBeginWrite }
19912035
{$IFEND LINUX or ANDROID}
19922036
{$ENDIF OTL_HasLightweightMREW}
@@ -3006,11 +3050,6 @@ procedure TLightweightMREWExImpl.EndWrite;
30063050
FLock.EndWrite;
30073051
end; { TLightweightMREWExImpl.EndWrite }
30083052

3009-
function TLightweightMREWExImpl.IsLocked: boolean;
3010-
begin
3011-
Result := FLock.IsLocked;
3012-
end; { TLightweightMREWExImpl.IsLocked }
3013-
30143053
function TLightweightMREWExImpl.TryBeginRead: boolean;
30153054
begin
30163055
Result := FLock.TryBeginRead;

0 commit comments

Comments
 (0)