forked from gabr42/OmniThreadLibrary
-
Notifications
You must be signed in to change notification settings - Fork 0
/
OtlSync.pas
2706 lines (2438 loc) · 86.9 KB
/
OtlSync.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
///<summary>Synchronisation primitives. Part of the OmniThreadLibrary project.</summary>
///<remarks>Move* family of functions require Pentium 4 processor (or newer).</remarks>
///<author>Primoz Gabrijelcic</author>
///<license>
///This software is distributed under the BSD license.
///
///Copyright (c) 2020, Primoz Gabrijelcic
///All rights reserved.
///
///Redistribution and use in source and binary forms, with or without modification,
///are permitted provided that the following conditions are met:
///- Redistributions of source code must retain the above copyright notice, this
/// list of conditions and the following disclaimer.
///- Redistributions in binary form must reproduce the above copyright notice,
/// this list of conditions and the following disclaimer in the documentation
/// and/or other materials provided with the distribution.
///- The name of the Primoz Gabrijelcic may not be used to endorse or promote
/// products derived from this software without specific prior written permission.
///
///THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
///ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
///WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
///DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
///ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
///(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
///LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
///ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
///(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
///SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///</license>
///<remarks><para>
/// Home : http://www.omnithreadlibrary.com
/// Support : https://en.delphipraxis.net/forum/32-omnithreadlibrary/
/// Author : Primoz Gabrijelcic
/// E-Mail : [email protected]
/// Blog : http://thedelphigeek.com
/// Contributors : GJ, Lee_Nover, dottor_jeckill, Sean B. Durkin, VyPu
/// Creation date : 2009-03-30
/// Last modification : 2023-11-28
/// Version : 1.27d
///</para><para>
/// History:
/// 1.27d: 2023-11-28
/// - Fixed hints & warnings.
/// - Locked<T>.Initialize without the 'factory' parameter could return
/// uninitialized value.
/// 1.27c: 2020-09-16
/// - Fixed TOmniMREW.TryEnterWriteLock which incorrectly managed the shared lock
/// state when a timeout occurred. [issue #149]
/// 1.27b: 2019-03-19
/// - TOmniMREW.TryEnterReadLock and .TryEnterWriteLock were returning True on timeout.
/// 1.27a: 2018-11-02
/// - Fixed race condition between TOmniResourceCount.[Try]Allocate and TOmniResourceCount.Release.
/// 1.27: 2018-04-06
/// - Added timeout parameter to TOmniMREW.TryEnterReadLock and TOmniMREW.TryExitReadLock.
/// 1.26: 2017-11-09
/// - [VyPu] Fixed: TOmniCriticalSection.Release decremented ocsLockCount after releasing the critical section.
/// 1.25: 2017-09-28
/// - [VyPu] Locked<T>.Value is now both readable and writable property.
/// 1.24: 2017-06-14
/// - TOmniCS.Initialize uses global lock to synchronize initialization instead of
/// a CAS operation. This fixes all reasons for the infamous error
/// "TOmniCS.Initialize: XXX is not properly aligned!".
/// 1.23: 2016-10-24
/// - Implemented two-parameter version of Atomic initializer which intializes
/// an interface type from a class type.
/// 1.22c: 2015-09-10
/// - Fixed unsafe 64-bit pointer-to-integer casts.
/// 1.22b: 2015-09-07
/// - TWaitFor.MsgWaitAny now uses RegisterWaitForSingleObject approach when
/// waiting on 64 handles. Previously, MsgWaitForMultipleObjectsEx was called,
/// which can only handle up to 63 handles.
/// 1.22a: 2015-09-04
/// - Fixed a bug in TWaitFor: When the code was waiting on less than 64 handles
/// and timeout occurred, the Signalled[] property was not always empty.
/// - Fixed: TWaitFor was not working correctly with more than 64 handles if
/// it was created with the parameter-less constructor.
/// 1.22: 2015-07-27
/// - Implemented TOmniSingleThreadUseChecker.AttachToThread which forcibly
/// attaches thread checker to the current thread even if it was used
/// from another thread before.
/// 1.21: 2015-07-10
/// - Implemented TOmniSingleThreadUseChecker, a record which checks that the
/// owner is only used from one thread. See OtlComm/TOmniCommunicationEndpoint
/// for an example.
/// 1.20: 2015-04-17
/// - TOmniCS.GetLockCount won't crash if Initialize was not called yet.
/// 1.19: 2014-11-04
/// - TWaitForAll renamed to TWaitFor.
/// - TWaitFor.Wait renamed to TWaitFor.WaitAll.
/// - Implemented TWaitFor.MsgWaitAny and .WaitAny.
/// - Implemented WaitForAnyObject.
/// 1.18: 2014-11-03
/// - Implemented WaitForAllObjects and TWaitForAll class.
/// 1.17: 2014-01-11
/// - Implemented TOmniMREW.TryEnterReadLock and TryEnterWriteLock.
/// 1.16: 2014-01-09
/// - Locked<T>.Free can be called if Locked<T> owns its Value.
/// 1.15: 2013-03-05
/// - TOmniLockManager<K> is reentrant.
/// 1.14: 2013-02-27
/// - Implemented TOmniLockManager<K> and IOmniLockManager<K>.
/// 1.13a: 2013-01-08
/// - Locked<T>.Free must execute in locked context.
/// 1.13: 2012-02-21
/// - Implemented Locked<T>.Locked.
/// 1.12: 2011-12-16
/// - [GJ] Converted low-level primitives to work in 64-bit platform and added few
/// platform-independent versions (CAS, MoveDPtr).
/// 1.11: 2011-12-14
/// - Implemented simplified versions of Atomic<T:class,constructor>.Initialize and
/// Locked<T:class,constructor>.Initialize that work on D2010 and newer.
/// 1.10a: 2011-12-09
/// - TOmniCS reuses LockCount from owned TOmniCriticalSection.
/// 1.10: 2011-12-02
/// - Locked<class> by default takes ownership of the object and frees it when
/// Locked<> goes out of scope. You can change this by calling
/// Locked<T>.Create(obj, false). To free the object manually, call Locked<T>.Free.
/// - Atomic<class>.Initialize was broken.
/// - Implemented Atomic<class>.Initialize(object) and Locked<class>.Initialize.
/// - Implemented Mfence.
/// - Locked<T>.Initialize creates memory barrier after storing newly created
/// resource into shared variable.
/// 1.09: 2011-12-01
/// - IOmniCriticalSection implements TFixedCriticalSection (as suggested by Eric
/// Grange in http://delphitools.info/2011/11/30/fixing-tcriticalsection/).
/// - Implemented IOmniCriticalSection.LockCount and TOmniCS.LockCount.
/// - Locked<T>.GetValue raises exception if critical section's LockCount is 0.
/// 1.08: 2011-11-29
/// - Implements Locked<T> class.
/// 1.07a: 2011-11-29
/// - Compiles with D2007.
/// 1.07: 2011-11-25
/// - Implemented Atomic<T> class for atomic interface initialization.
/// 1.06: 2011-03-01
/// - [dottor_jeckill] Bug fix: TOmniResourceCount.TryAllocate always returned False.
/// 1.05: 2010-07-01
/// - Includes OTLOptions.inc.
/// 1.04a: 2010-03-30
/// - Prevent race condition in a rather specialized usage of TOmniResourceCount.
/// 1.04: 2010-02-04
/// - Implemented CAS8 and CAS16.
/// 1.03: 2010-02-03
/// - IOmniCancellationToken extended with the Clear method.
/// 1.02: 2010-02-02
/// - Implemented IOmniCancellationToken.
/// 1.01a: 2010-01-07
/// - "Wait when no resources" state in TOmniResourceCount was not properly
/// implemented.
/// 1.01: 2009-12-30
/// - Implemented resource counter with empty state signalling - TOmniResourceCount.
/// 1.0: 2008-08-26
/// - TOmniCS and IOmniCriticalSection imported from the OtlCommon unit.
/// - [GJ] Added very simple (and very fast) multi-reader-exclusive-writer TOmniMREW.
/// - First official release.
///</para></remarks>
unit OtlSync;
{$I OtlOptions.inc}
interface
uses
SysUtils,
SyncObjs,
Classes,
{$IFDEF OTL_Generics}
Generics.Defaults,
Generics.Collections,
{$ENDIF OTL_Generics}
{$IFDEF OTL_ERTTI}
RTTI,
{$ENDIF OTL_ERTTI}
TypInfo,
{$IFDEF MSWINDOWS}
Windows,
DSiWin32,
GpStuff,
GpLists,
{$ENDIF}
{$IFDEF OTL_MobileSupport}
{$IFDEF POSIX}
Posix.Pthread,
{$ENDIF}
System.Diagnostics,
{$ENDIF OTL_MobileSupport}
OtlCommon;
type
TFixedCriticalSection = class(TCriticalSection)
strict protected
FDummy: array [0..95] of byte;
end; { TFixedCriticalSection }
IOmniCriticalSection = interface ['{AA92906B-B92E-4C54-922C-7B87C23DABA9}']
function GetLockCount: integer;
//
procedure Acquire;
procedure Release;
function GetSyncObj: TSynchroObject;
property LockCount: integer read GetLockCount;
end; { IOmniCriticalSection }
{$IFDEF OTL_MobileSupport}
IOmniSynchroObserver = interface ['{03330A74-3C3D-4D2F-9A21-89663DE7FD10}']
procedure EnterGate;
procedure LeaveGate;
/// <param name="SynchObj">SynchObj must support IOmniSynchroObject.</param>
procedure DereferenceSynchObj(const SynchObj: TObject; AllowInterface: boolean);
/// <param name="Subtractend">Signaller must support IOmniSynchroObject.</param>
procedure BeforeSignal(const Signaller: TObject; var Data: TObject);
/// <param name="Subtractend">Signaller must support IOmniSynchroObject.</param>
procedure AfterSignal(const Signaller: TObject; var Data: TObject);
end; { IOmniSynchroObserver }
IOmniSynchro = interface ['{2C4F0CF8-A722-45EC-BFCA-AA512E58B54D}']
function EnterSpinLock: IInterface;
procedure Signal;
/// <remarks>
/// If this event is attached to IOmniSynchroObserver,
// such as TWaitFor (acting as a condition variable)
/// a thread must not invoke WaitFor() directly on this event, but
/// rather through the containing TWaitFor, or as otherwise defined by
// the attached observer.
/// </remarks>
function WaitFor(Timeout: LongWord = INFINITE): TWaitResult; overload;
procedure ConsumeSignalFromObserver( const Observer: IOmniSynchroObserver);
/// <remarks>
/// IsSignaled() is only valid when all the Signal()/ Reset()
/// invocations are done whilst attached to an IOmniEventObserver.
/// Otherwise this returned value must not be relied upon.
/// </remarks>
function IsSignalled: boolean;
procedure AddObserver(const Observer: IOmniSynchroObserver);
procedure RemoveObserver(const Observer: IOmniSynchroObserver);
function Base: TSynchroObject;
{$IFDEF MSWINDOWS}
function Handle: THandle;
{$ENDIF}
end; { IOmniSynchro }
IOmniSynchroObject = interface ['{A8B95978-87BF-4031-94B2-8EDC351F47BE}']
function GetSynchro: IOmniSynchro;
//
property Synchro: IOmniSynchro read GetSynchro;
end; { IOmniSynchroObject }
/// <remarks>
/// IOmniEvent is a wrapper around a TEvent object.
/// It can co-operate with condition variables through the use of an
/// attached IOmniEventObserver. IOmniEvent objects can be enrolled
/// in TWaitFor objects on non-windows platforms.
/// </remarks>
IOmniEvent = interface(IOmniSynchro) ['{3403D24B-3CBE-4A83-9F4C-FA4719AA23C5}']
procedure SetEvent;
procedure Reset;
function BaseEvent: TEvent;
end; { IOmniEvent }
IOmniCountdownEvent = interface(IOmniSynchro) ['{40557184-B610-46E8-B186-D5B431D1B1A4}']
function BaseCountdown: TCountdownEvent;
procedure Reset;
end; { IOmniCountdownEvent }
{$ENDIF OTL_MobileSupport}
//At some point this type will be dropped and all the codebase will use
//IOmniEvent or something similar.
TOmniTransitionEvent = {$IFDEF MSWINDOWS}THandle{$ELSE}IOmniEvent{$ENDIF};
{$IFDEF MSWINDOWS}
IOmniHandleObject = interface ['{80B85D03-8E1F-4812-8782-38A04BA52076}']
function GetHandle: THandle;
//
property Handle: THandle read GetHandle;
end; { IOmniHandleObject }
{$ENDIF MSWINDOWS}
///<summary>Simple critical section wrapper. Critical section is automatically
/// initialised on first use.</summary>
TOmniCS = record
strict private
ocsSync: IOmniCriticalSection;
private
function GetLockCount: integer; inline;
function GetSyncObj: TSynchroObject; inline;
public
procedure Initialize;
procedure Acquire; inline;
procedure Release; inline;
property LockCount: integer read GetLockCount;
property SyncObj: TSynchroObject read GetSyncObj;
end; { TOmniCS }
///<summary>Very lightweight multiple-readers-exclusive-writer lock.</summary>
TOmniMREW = record
strict private
//Treated as an integer, IInterface is only used to provide automatic initialization to 0.
//Bit0 is 'writing in progress' flag.
omrewReference: IInterface;
public
procedure EnterReadLock; inline;
procedure EnterWriteLock; inline;
procedure ExitReadLock; inline;
procedure ExitWriteLock; inline;
function TryEnterReadLock(timeout_ms: integer = 0): boolean;
function TryEnterWriteLock(timeout_ms: integer = 0): boolean;
end; { TOmniMREW }
IOmniResourceCount = interface({$IFDEF MSWINDOWS}
IOmniHandleObject
{$ELSE}{$IFDEF OTL_MobileSupport}
IOmniSynchroObject
{$ENDIF}{$ENDIF})
['{F5281539-1DA4-45E9-8565-4BEA689A23AD}']
function Allocate: cardinal;
function Release: cardinal;
function TryAllocate(var resourceCount: cardinal; timeout_ms: cardinal = 0): boolean;
end; { IOmniResourceCount }
///<summary>Kind of an inverse semaphore. Gets signalled when count drops to 0.
/// Allocate decrements the count (and blocks if initial count is 0), Release
/// increments the count.
/// Threadsafe.
///</summary>
{$IFDEF MSWINDOWS}
TOmniResourceCount = class(TInterfacedObject, IOmniResourceCount, IOmniHandleObject)
strict private
orcAvailable : TDSiEventHandle;
orcHandle : TDSiEventHandle;
orcLock : TOmniCS;
orcNumResources: TOmniAlignedInt32;
protected
function GetHandle: THandle;
public
constructor Create(initialCount: cardinal);
destructor Destroy; override;
function Allocate: cardinal; inline;
function Release: cardinal;
function TryAllocate(var resourceCount: cardinal; timeout_ms: cardinal = 0): boolean;
property Handle: THandle read GetHandle;
end; { TOmniResourceCount }
{$ELSE}{$IFDEF OTL_MobileSupport}
TOmniResourceCount = class abstract(TInterfacedObject, IOmniResourceCount, IOmniSynchroObject)
strict protected
function GetSynchro: IOmniSynchro;
public
constructor Create(initialCount: cardinal);
destructor Destroy; override;
function Allocate: cardinal;
function Release: cardinal;
function TryAllocate(var resourceCount: cardinal; timeout_ms: cardinal = 0): boolean;
property Synchro: IOmniSynchro read GetSynchro;
end; { TOmniResourceCount }
{$ENDIF OTL_MobileSupport}{$ENDIF MSWINDOWS}
IOmniCancellationToken = interface ['{5946F4E8-45C0-4E44-96AB-DBE2BE66A701}']
{$IFDEF MSWINDOWS}
function GetHandle: THandle;
{$ELSE}
function GetEvent: IOmniEvent;
{$ENDIF MSWINDOWS}
//
procedure Clear;
function IsSignalled: boolean;
procedure Signal;
{$IFDEF MSWINDOWS}
property Handle: THandle read GetHandle;
{$ELSE}
property Event: IOmniEvent read GetEvent;
{$ENDIF MSWINDOWS}
end; { IOmniCancellationToken }
{$IFDEF OTL_Generics}
Atomic<T> = class
type TFactory = reference to function: T;
class function Initialize(var storage: T; factory: TFactory): T; overload;
{$IFDEF OTL_ERTTI}
class function Initialize(var storage: T): T; overload;
{$ENDIF OTL_ERTTI}
end; { Atomic<T> }
{$IFDEF OTL_ERTTI}
Atomic<I; T:constructor> = class
class function Initialize(var storage: I): I;
end; { Atomic<I,T> }
{$ENDIF OTL_ERTTI}
Locked<T> = record
strict private // keep those aligned!
FLock : TOmniCS;
FValue : T;
strict private
FInitialized: boolean;
FLifecycle : IInterface;
FOwnsObject : boolean;
procedure Clear; inline;
function GetValue: T; inline;
procedure SetValue(const value: T); inline;
public
type TFactory = reference to function: T;
type TProcT = reference to procedure(const value: T);
constructor Create(const value: T; ownsObject: boolean = true);
class operator Implicit(const value: Locked<T>): T; inline;
class operator Implicit(const value: T): Locked<T>; inline;
function Initialize(factory: TFactory): T; overload;
{$IFDEF OTL_ERTTI}
function Initialize: T; overload;
{$ENDIF OTL_ERTTI}
procedure Acquire; inline;
procedure Locked(proc: TProc); overload; inline;
procedure Locked(proc: TProcT); overload; inline;
procedure Release; inline;
procedure Free; inline;
property Value: T read GetValue write SetValue;
end; { Locked<T> }
IOmniLockManagerAutoUnlock = interface
procedure Unlock;
end; { IOmniLockManagerAutoUnlock }
IOmniLockManager<K> = interface
function Lock(const key: K; timeout_ms: cardinal): boolean;
function LockUnlock(const key: K; timeout_ms: cardinal): IOmniLockManagerAutoUnlock;
procedure Unlock(const key: K);
end; { IOmniLockManager<K> }
{$IFDEF MSWINDOWS} // mobile version does not implement doubly linked list (yet)
TOmniLockManager<K> = class(TInterfacedObject, IOmniLockManager<K>)
strict private type
TNotifyPair = class(TGpDoublyLinkedListObject)
Key : K;
Notify: TDSiEventHandle;
constructor Create(const aKey: K; aNotify: TDSiEventHandle);
end;
TLockValue = record
LockCount: integer;
ThreadID : cardinal;
constructor Create(aThreadID: cardinal; aLockCount: integer);
end;
strict private
FComparer : IEqualityComparer<K>;
FLock : TOmniCS;
FLockList : TDictionary<K,TLockValue>;
FNotifyList: TGpDoublyLinkedList;
strict private type
TAutoUnlock = class(TInterfacedObject, IOmniLockManagerAutoUnlock)
strict private
FUnlockProc: TProc;
public
constructor Create(unlockProc: TProc);
destructor Destroy; override;
procedure Unlock;
end;
public
class function CreateInterface(capacity: integer = 0): IOmniLockManager<K>; overload;
class function CreateInterface(comparer: IEqualityComparer<K>; capacity: integer = 0):
IOmniLockManager<K>; overload;
constructor Create(capacity: integer = 0); overload;
constructor Create(const comparer: IEqualityComparer<K>; capacity: integer = 0); overload;
destructor Destroy; override;
function Lock(const key: K; timeout_ms: cardinal): boolean;
function LockUnlock(const key: K; timeout_ms: cardinal): IOmniLockManagerAutoUnlock;
procedure Unlock(const key: K);
end; { TOmniLockManager<K> }
{$ENDIF MSWINDOWS}
{$ENDIF OTL_Generics}
{$IFDEF MSWINDOWS}
///<summary>Waits on any/all from any number of handles.</summary>
/// Don't use it to wait on mutexes!
/// http://joeduffyblog.com/2007/05/13/registerwaitforsingleobject-and-mutexes-dont-mix/
TWaitFor = class
private type
TWaitMode = (wmSmart, wmForceWFM, wmForceRWFS);
protected type //must be visible from the callback
TWaiter = class
strict private
FIdxHandle: integer;
FOwner : TWaitFor;
FSignalled: boolean;
public
constructor Create(owner: TWaitFor; idxHandle: integer);
procedure Awaited;
property Index: integer read FIdxHandle;
property Signalled: boolean read FSignalled write FSignalled;
end;
public type
TWaitForResult = (
waAwaited, // WAIT_OBJECT_0 .. WAIT_OBJECT_n
waTimeout, // WAIT_TIMEOUT
waFailed, // WAIT_FAILED
waIOCompletion, // WAIT_IO_COMPLETION
waMessage // message or wake event (WAIT_OBJECT_n+1)
);
THandleInfo = record
Index: integer;
end;
THandles = array of THandleInfo;
THandleArr = array of THandle;
strict private
FAwaitedLock : TOmniCS;
FHandles : array of THandle;
FIdxSignalled : integer;
FResourceCount : IOmniResourceCount;
FSignal : TDSiEventHandle;
FSignalledHandles: THandles;
FWaitHandles : TGpInt64ObjectList;
FWaitMode : TWaitMode; // for testing
strict protected
function GetWaitHandles: THandleArr;
function MapToHandle(winResult: cardinal; isMsgWait: boolean): cardinal;
function MapToResult(winResult: cardinal): TWaitForResult;
procedure RegisterWaitHandles(extraFlags: cardinal);
procedure UnregisterWaitHandles;
protected //must be visible from the callback
procedure Awaited_Asy(idxHandle: integer);
public
constructor Create; overload;
constructor Create(const handles: array of THandle); overload;
destructor Destroy; override;
function MsgWaitAny(timeout_ms, wakeMask, flags: cardinal): TWaitForResult;
procedure SetHandles(const handles: array of THandle);
function WaitAll(timeout_ms: cardinal): TWaitForResult;
function WaitAny(timeout_ms: cardinal; alertable: boolean = false): TWaitForResult;
property Signalled: THandles read FSignalledHandles;
property WaitHandles: THandleArr read GetWaitHandles;
end; { TWaitFor }
{$ELSE ~MSWINDOWS}
{$IFDEF OTL_MobileSupport}
///<summary>Waits on any/all from any number of synchroobjects such as Events and CountDownEvents.</summary>
TSynchroWaitFor = class
public type //TODO: not integrated yet (maybe will even be removed at the end but currently OtlTaskControl expects it)
TWaitForResult = (
waAwaited, // WAIT_OBJECT_0 .. WAIT_OBJECT_n
waTimeout, // WAIT_TIMEOUT
waFailed, // WAIT_FAILED
waIOCompletion, // WAIT_IO_COMPLETION
waMessage // message or wake event (WAIT_OBJECT_n+1)
);
THandleInfo = record //TODO: not integrated yet (maybe will even be removed at the end but currently OtlTaskControl expects it)
Index: integer;
end;
strict private type
TSynchroList = class(TList<IOmniSynchro>) end;
ISynchroClientEx = interface ['{A4D963B3-88CD-466A-9885-3C66E605E32E}']
procedure Deref;
end; { ISyncroClientEx }
TSynchroClient = class(TInterfacedObject, IOmniSynchroObserver, ISynchroClientEx)
strict private
FController: TSynchroWaitFor;
procedure EnterGate;
procedure LeaveGate;
procedure DereferenceSynchObj(const SynchObj: TObject; AllowInterface: boolean);
procedure BeforeSignal(const Signaller: TObject; var Data: TObject);
procedure AfterSignal(const Signaller: TObject; var Data: TObject);
procedure Deref;
public
constructor Create(AController: TSynchroWaitFor);
end; { TSynchroClient }
protected type
TCondition = class
protected
FCondVar : TConditionVariableCS;
FController: TSynchroWaitFor;
public
constructor Create(AController: TSynchroWaitFor);
destructor Destroy; override;
function Wait(timeout_ms: cardinal; var Signaller: IOmniSynchro): TWaitResult;
function Test(var Signaller: IOmniSynchro): boolean; virtual; abstract;
function WaitAll: boolean; virtual; abstract;
end;
strict private
FAllSignalled: TCondition;
FGate : IOmniCriticalSection;
FOneSignalled: TCondition;
FSynchObjects: TSynchroList;
FSynchClient : IOmniSynchroObserver;
protected
property Gate: IOmniCriticalSection read FGate;
property SynchObjects: TSynchroList read FSynchObjects;
public
constructor Create(const SynchObjects: array of IOmniSynchro; const AShareLock: IOmniCriticalSection = nil);
destructor Destroy; override;
function WaitAll(timeout_ms: cardinal): TWaitResult;
function WaitAny(timeout_ms: cardinal; var Signaller: IOmniSynchro): TWaitResult;
end; { TWaitForAll }
TWaitFor = TSynchroWaitFor;
{$ENDIF OTL_MobileSupport}
{$ENDIF ~MSWINDOWS}
TOmniSingleThreadUseChecker = record
private
FLock : TOmniCS;
FThreadID: cardinal;
public
procedure AttachToCurrentThread; inline;
procedure Check; inline;
procedure DebugCheck; inline;
end; { TOmniSingleThreadUseChecker }
// Compatibility layer for interlocked operations.
TInterlockedEx = class
public
class function Add(var Target: NativeInt; Increment: NativeInt): NativeInt; overload; static; inline;
class function CAS(const oldValue, newValue: NativeInt; var destination): boolean; overload; static; inline;
class function CAS(const oldValue, newValue: pointer; var destination): boolean; overload; static; inline;
class function CompareExchange(var Target: NativeInt; Value: NativeInt; Comparand: NativeInt): NativeInt; static; inline;
class function Increment(var Target: Integer): Integer; overload; static; inline;
class function Decrement(var Target: Integer): Integer; overload; static; inline;
end; { TInterlockedEx }
{$IFDEF OTL_NeedsWindowsAPIs}
TWaitOrTimerCallback = procedure (Context: Pointer; Success: Boolean) stdcall;
BOOL = LongBool;
ULONG = Cardinal;
const
WT_EXECUTEONLYONCE = ULONG($00000008);
WT_EXECUTEINPERSISTENTTHREAD = ULONG($00000080);
function RegisterWaitForSingleObject(out phNewWaitObject: THandle; hObject: THandle;
CallBack: TWaitOrTimerCallback; Context: Pointer; dwMilliseconds: ULONG;
dwFlags: ULONG): BOOL; stdcall;
external 'kernel32.dll' name 'RegisterWaitForSingleObject';
function RegisterWaitForSingleObjectEx(hObject: THandle;
CallBack: TWaitOrTimerCallback; Context: Pointer; dwMilliseconds: ULONG;
dwFlags: ULONG): THandle; stdcall;
external 'kernel32.dll' name 'RegisterWaitForSingleObjectEx';
function UnregisterWait(WaitHandle: THandle): BOOL; stdcall;
external 'kernel32.dll' name 'UnregisterWait';
function UnregisterWaitEx(WaitHandle: THandle; CompletionEvent: THandle): BOOL; stdcall;
external 'kernel32.dll' name 'UnregisterWaitEx';
{$ENDIF OTL_NeedsWindowsAPIs}
function CreateOmniCriticalSection: IOmniCriticalSection;
function CreateOmniCancellationToken: IOmniCancellationToken;
function CreateResourceCount(initialCount: integer): IOmniResourceCount;
{$IFDEF OTL_MobileSupport}
function CreateOmniCountdownEvent(Count: Integer; SpinCount: Integer; const AShareLock: IOmniCriticalSection = nil): IOmniCountdownEvent;
function CreateOmniEvent(AManualReset, InitialState: boolean; const AShareLock: IOmniCriticalSection = nil): IOmniEvent;
{$ENDIF OTL_MobileSupport}
{$IFDEF MSWINDOWS}
procedure NInterlockedExchangeAdd(var addend; value: NativeInt);
function CAS8(const oldValue, newValue: byte; var destination): boolean;
function CAS16(const oldValue, newValue: word; var destination): boolean;
function CAS32(const oldValue, newValue: cardinal; var destination): boolean; overload;
{$IFNDEF CPUX64}
function CAS32(const oldValue: pointer; newValue: pointer; var destination): boolean; overload;
{$ENDIF ~CPUX64}
function CAS64(const oldData, newData: int64; var destination): boolean; overload;
function CAS(const oldValue, newValue: NativeInt; var destination): boolean; overload;
function CAS(const oldValue, newValue: pointer; var destination): boolean; overload;
function CAS(const oldData: pointer; oldReference: NativeInt; newData: pointer;
newReference: NativeInt; var destination): boolean; overload;
{$IFNDEF CPUX64}
procedure Move64(var Source, Destination); overload;
procedure Move64(newData: pointer; newReference: cardinal; var Destination); overload;
{$ENDIF ~CPUX64}
procedure Move128(var Source, Destination);
procedure MoveDPtr(var Source, Destination); overload;
procedure MoveDPtr(newData: pointer; newReference: NativeInt; var Destination); overload;
///<summary>Waits on any number of handles.</summary>
///<returns>True on success, False on timeout.</returns>
function WaitForAllObjects(const handles: array of THandle; timeout_ms: cardinal): boolean;
{$ENDIF MSWINDOWS}
function GetThreadId: NativeInt;
function GetCPUTimeStamp: int64;
function SetEvent(event: TOmniTransitionEvent): boolean;
var
GOmniCancellationToken: IOmniCancellationToken;
CASAlignment: integer; //required alignment for the CAS function - 8 or 16, depending on the platform
implementation
type
TOmniCriticalSection = class(TInterfacedObject, IOmniCriticalSection)
strict private
ocsCritSect : TSynchroObject;
ocsLockCount: integer;
public
constructor Create;
destructor Destroy; override;
procedure Acquire; inline;
function GetLockCount: integer;
function GetSyncObj: TSynchroObject;
procedure Release; inline;
end; { TOmniCriticalSection }
TOmniCancellationToken = class(TInterfacedObject, IOmniCancellationToken)
{$IFDEF MSWINDOWS}
private
FEvent : TDSiEventHandle;
FIsSignalled: boolean;
protected
function GetHandle: THandle; inline;
{$ELSE}
private
FEvent: IOmniEvent;
protected
function GetEvent: IOmniEvent; inline;
{$ENDIF MSWINDOWS}
public
constructor Create;
procedure Clear; inline;
function IsSignalled: boolean; inline;
procedure Signal; inline;
{$IFDEF MSWINDOWS}
destructor Destroy; override;
property Handle: THandle read GetHandle;
{$ELSE}
property Event: IOmniEvent read GetEvent;
{$ENDIF MSWINDOWS}
end; { TOmniCancellationToken }
{$IFDEF OTL_MobileSupport}
TOmniSynchroObject = class abstract(TSynchroObject, IInterface, IOmniSynchro)
private
procedure PerformObservableAction(Action: TProc; DoLock: boolean);
function Base: TSynchroObject;
{$IFDEF MSWINDOWS}
function Handle: THandle;
{$ENDIF}
strict protected
FBase : TSynchroObject;
FOwnsBase : boolean;
FLock : TSpinLock;
FObservers : TList<IOmniSynchroObserver>;
FData : TArray<TObject>;
[Volatile] FRefCount: integer;
FShareLock : IOmniCriticalSection;
private
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
protected
property Lock: TSpinLock read FLock;
property ShareLock: IOmniCriticalSection read FShareLock;
public
procedure AfterConstruction; override;
class function NewInstance: TObject; override;
public
constructor Create(ABase: TSynchroObject; OwnsIt: boolean; const AShareLock: IOmniCriticalSection = nil);
destructor Destroy; override;
function EnterSpinLock: IInterface;
procedure Acquire; override;
procedure Release; override;
procedure Signal;
function WaitFor(Timeout: LongWord = INFINITE): TWaitResult; override;
procedure ConsumeSignalFromObserver(const Observer: IOmniSynchroObserver); virtual; abstract;
function IsSignalled: boolean; virtual; abstract;
procedure AddObserver(const Observer: IOmniSynchroObserver);
procedure RemoveObserver(const Observer: IOmniSynchroObserver);
end; { TOmniSynchroObject }
TSynchroSpin = class(TInterfacedObject)
private
FController: TOmniSynchroObject;
public
constructor Create(AController: TOmniSynchroObject);
destructor Destroy; override;
end; { TSynchroSpin }
TOmniCountdownEvent = class(TOmniSynchroObject, IOmniCountdownEvent)
strict protected
FCountdown: TCountdownEvent;
public
constructor Create(Count: Integer; SpinCount: Integer; const AShareLock: IOmniCriticalSection = nil);
procedure Reset;
procedure ConsumeSignalFromObserver(const Observer: IOmniSynchroObserver); override;
function IsSignalled: boolean; override;
function BaseCountdown: TCountdownEvent;
end; { TOmniCountdownEvent }
TOmniEvent = class(TOmniSynchroObject, IOmniEvent)
strict protected
FEvent: TEvent;
[Volatile] FState: boolean;
FManualReset: boolean;
public
constructor Create(AManualReset, InitialState: boolean; const AShareLock: IOmniCriticalSection = nil);
procedure Reset;
procedure SetEvent;
function BaseEvent: TEvent;
procedure ConsumeSignalFromObserver(const Observer: IOmniSynchroObserver); override;
function WaitFor(Timeout: LongWord = INFINITE): TWaitResult; override;
function IsSignalled: boolean; override;
end; { TOmniEvent }
{$IFNDEF MSWINDOWS}
TOneCondition = class(TSynchroWaitFor.TCondition)
public
function Test(var Signaller: IOmniSynchro): boolean; override;
function WaitAll: boolean; override;
end; { TOneCondition }
TAllCondition = class( TSynchroWaitFor.TCondition)
public
function Test(var Signaller: IOmniSynchro): boolean; override;
function WaitAll: boolean; override;
end; { TAllCondition }
TPreSignalData = class
public
OneSignalled: boolean;
AllSignalled: boolean;
constructor Create(AOneSignalled, AllSignalled: boolean);
end; { TPreSignaData }
{$ENDIF ~MSWINDOWS}
{$ENDIF OTL_MobileSupport}
var
GOmniCSInitializer: TOmniCriticalSection;
{ transitional }
function SetEvent(event: TOmniTransitionEvent): boolean;
begin
Result := true;
{$IFDEF MSWINDOWS}
if event <> 0 then
Result := Windows.SetEvent(event);
{$ELSE}
if assigned(event) then
event.SetEvent;
{$ENDIF ~MSWINDOWS}
end; { SetEvent }
{ exports }
function CreateOmniCriticalSection: IOmniCriticalSection;
begin
Result := TOmniCriticalSection.Create;
end; { CreateOmniCriticalSection }
function CreateOmniCancellationToken: IOmniCancellationToken;
begin
Result := TOmniCancellationToken.Create;
end; { CreateOmniCancellationToken }
function CreateResourceCount(initialCount: integer): IOmniResourceCount;
begin
Result := TOmniResourceCount.Create(initialCount);
end; { CreateResourceCount }
{$IFDEF OTL_MobileSupport}
function CreateOmniCountdownEvent(Count: Integer; SpinCount: Integer; const AShareLock: IOmniCriticalSection = nil): IOmniCountdownEvent;
begin
Result := TOmniCountdownEvent.Create(Count, SpinCount, AShareLock);
end; { CreateOmniCountdownEvent }
function CreateOmniEvent(AManualReset, InitialState: boolean; const AShareLock: IOmniCriticalSection = nil): IOmniEvent;
begin
Result := TOmniEvent.Create(AManualReset, InitialState, AShareLock);
end; { CreateOmniEvent }
{$ENDIF OTL_MobileSupport}
{$IFDEF MSWINDOWS}
function CAS8(const oldValue, newValue: byte; var destination): boolean;
asm
{$IFDEF CPUX64}
mov al, oldValue
{$ENDIF CPUX64}
lock cmpxchg [destination], dl
setz al
end; { CAS8 }
function CAS16(const oldValue, newValue: word; var destination): boolean;
asm
{$IFDEF CPUX64}
mov ax, oldValue
{$ENDIF CPUX64}
lock cmpxchg [destination], dx
setz al
end; { CAS16 }
function CAS32(const oldValue, newValue: cardinal; var destination): boolean; overload;
asm
{$IFDEF CPUX64}
mov eax, oldValue
{$ENDIF CPUX64}
lock cmpxchg [destination], edx
setz al
end; { CAS32 }
{$IFNDEF CPUX64}
function CAS32(const oldValue: pointer; newValue: pointer; var destination): boolean; overload;
asm
//{$IFDEF CPUX64}
mov eax, oldValue
//{$ENDIF CPUX64}
lock cmpxchg [destination], edx
setz al
end; { CAS32 }
{$ENDIF ~CPUX64}
function CAS64(const oldData, newData: int64; var destination): boolean; overload;
asm
{$IFNDEF CPUX64}
push edi
push ebx
mov edi, destination
mov ebx, low newData
mov ecx, high newData
mov eax, low oldData
mov edx, high oldData
lock cmpxchg8b [edi]
pop ebx
pop edi
{$ELSE CPUX64}
mov rax, oldData
lock cmpxchg [destination], newData
{$ENDIF ~CPUX64}
setz al
end; { CAS64 }
function CAS(const oldValue, newValue: NativeInt; var destination): boolean; overload;
asm
{$IFDEF CPUX64}
mov rax, oldValue
{$ENDIF CPUX64}
lock cmpxchg [destination], newValue
setz al
end; { CAS }
function CAS(const oldValue, newValue: pointer; var destination): boolean; overload;
asm
{$IFDEF CPUX64}
mov rax, oldValue
{$ENDIF CPUX64}
lock cmpxchg [destination], newValue
setz al
end; { CAS }
//eighter 8-byte or 16-byte CAS, depending on the platform; destination must be propely aligned (8- or 16-byte)
function CAS(const oldData: pointer; oldReference: NativeInt; newData: pointer;
newReference: NativeInt; var destination): boolean; overload;
asm
{$IFNDEF CPUX64}
push edi
push ebx
mov ebx, newData
mov ecx, newReference
mov edi, destination
lock cmpxchg8b qword ptr [edi]
pop ebx
pop edi
{$ELSE CPUX64}
.noframe
push rbx //rsp := rsp - 8 !
mov rax, oldData
mov rbx, newData
mov rcx, newReference
mov r8, [destination + 8] //+8 with respect to .noframe
lock cmpxchg16b [r8]
pop rbx
{$ENDIF CPUX64}
setz al
end; { CAS }
{$IFNDEF CPUX64}
procedure Move64(var Source, Destination); overload;
//Move 8 bytes atomicly from Source 8-byte aligned to Destination!
asm
movq xmm0, qword [Source]
movq qword [Destination], xmm0
end;
procedure Move64(newData: pointer; newReference: cardinal; var Destination); overload;
//Move 8 bytes atomically into 8-byte Destination!
asm
movd xmm0, eax
movd xmm1, edx
punpckldq xmm0, xmm1
movq qword [Destination], xmm0
end; { Move64 }
{$ENDIF ~CPUX64}
procedure Move128(var Source, Destination);
//Move 16 bytes atomically from Source to 16-byte aligned to Destination!
asm
{$IFNDEF CPUX64}
movdqa xmm0, dqword [Source]
movdqa dqword [Destination], xmm0
{$ELSE CPUX64}
//Move 16 bytes atomically into 16-byte Destination!
push rbx
mov r8, destination
mov rbx, [Source]