-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathSimpleFutex.pas
2333 lines (1950 loc) · 86.9 KB
/
SimpleFutex.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
{-------------------------------------------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-------------------------------------------------------------------------------}
{===============================================================================
Simple futex
Main aim of this library is to provide wrappers for futexes (synchronization
primitives in Linux) and some very simple complete synchronization objects
based on them - currently simple mutex, simple semaphore, simple robust
mutex and simple recursive mutex (which in itself is an extension of simple
robust mutex) are implemented.
NOTE - since proper implementation of futexes is not particularly easy,
there are probably errors. If you find any, please let me know.
NOTE - because simple recursive mutex is only an extension of simple
robust mutex, most information provided for SRM are also valid
for SCM.
WARNING - simple robust mutex might not be provided by this unit if
dependecy library InterlockedOps does not provide functions
accepting 64bit arguments (see there for details).
Public constant SF_SRM_AVAILABLE can be used to probe whether
SRM is or isn't provided (as it is a true constant, it can be
used in conditional compilation).
Version 1.3 (2024-09-21)
Last change 2024-09-21
©2021-2024 František Milt
Contacts:
František Milt: [email protected]
Support:
If you find this code useful, please consider supporting its author(s) by
making a small donation using the following link(s):
https://www.paypal.me/FMilt
Changelog:
For detailed changelog and history please refer to this git repository:
github.com/TheLazyTomcat/Lib.SimpleFutex
Dependencies:
AuxClasses - github.com/TheLazyTomcat/Lib.AuxClasses
* AuxExceptions - github.com/TheLazyTomcat/Lib.AuxExceptions
AuxTypes - github.com/TheLazyTomcat/Lib.AuxTypes
InterlockedOps - github.com/TheLazyTomcat/Lib.InterlockedOps
Library AuxExceptions is required only when rebasing local exception classes
(see symbol SimpleFutex_UseAuxExceptions for details).
Library AuxExceptions might also be required as an indirect dependency.
Indirect dependencies:
SimpleCPUID - github.com/TheLazyTomcat/Lib.SimpleCPUID
StrRect - github.com/TheLazyTomcat/Lib.StrRect
UInt64Utils - github.com/TheLazyTomcat/Lib.UInt64Utils
WinFileInfo - github.com/TheLazyTomcat/Lib.WinFileInfo
===============================================================================}
unit SimpleFutex;
{
SimpleFutex_UseAuxExceptions
If you want library-specific exceptions to be based on more advanced classes
provided by AuxExceptions library instead of basic Exception class, and don't
want to or cannot change code in this unit, you can define global symbol
SimpleFutex_UseAuxExceptions to achieve this.
}
{$IF Defined(SimpleFutex_UseAuxExceptions)}
{$DEFINE UseAuxExceptions}
{$IFEND}
//------------------------------------------------------------------------------
{$IF Defined(LINUX) and Defined(FPC)}
{$DEFINE Linux}
{$ELSE}
{$MESSAGE FATAL 'Unsupported operating system.'}
{$IFEND}
{$IFDEF FPC}
{$MODE ObjFPC}
{$DEFINE FPC_DisableWarns}
{$MACRO ON}
{$ENDIF}
{$H+}
{$IFOPT Q+}
{$DEFINE OverflowChecks}
{$ENDIF}
//------------------------------------------------------------------------------
{
RobustMutexThreadTimeCheck
Changes how the simple robust mutex (SRM) checks whether thread that locked
the SRM still lives - see description of SRM for more details and explanation
of effects this symbol has.
Has meaning only if simple robust mutexes are provided.
WARNING - libraries compiled with and without this symbol defined are
mutually completely incompatible (the SRMs, that is).
Not defined by default.
To enable/define this symbol in a project without changing this library,
define project-wide symbol SimpleFutex_RobustMutexThreadTimeCheck_ON.
}
{$UNDEF RobustMutexThreadTimeCheck}
{$IFDEF SimpleFutex_RobustMutexThreadTimeCheck_ON}
{$DEFINE RobustMutexThreadTimeCheck}
{$ENDIF}
{
RobustMutexBufferThreadID
When this symbol is defined (default), then thread ID and process ID (which
are used internally by the mutex) are obtained only once - they are buffered
(stored in a thread-local variable) and, when needed again, copied from the
buffer. When not defined, these IDs are obtained using system calls everytime
they are needed.
Generally, the use of buffering is preferred, but sometimes it might be
necessary to disable it, this symbol is here for that purpose.
Has meaning only if simple robust mutexes are provided.
Defined by default.
To disable/undefine this symbol in a project without changing this library,
define project-wide symbol SimpleFutex_RobustMutexBufferThreadID_OFF.
}
{$DEFINE RobustMutexBufferThreadID}
{$IFDEF SimpleFutex_RobustMutexBufferThreadID_OFF}
{$UNDEF RobustMutexBufferThreadID}
{$ENDIF}
interface
uses
SysUtils, UnixType,
AuxTypes, AuxClasses, InterlockedOps
{$IFDEF UseAuxExceptions}, AuxExceptions{$ENDIF};
{$IF ILO_64BIT_VARS} // constant from InterlockedOps
{$DEFINE SF_SimpleRobustMutex}
{$ELSE}
{$UNDEF SF_SimpleRobustMutex}
{$IFEND}
{$IFDEF RobustMutexThreadTimeCheck}
{$DEFINE SF_SRM_TimeCheck}
{$ELSE}
{$UNDEF SF_SRM_TimeCheck}
{$ENDIF}
{===============================================================================
Informative public constants
===============================================================================}
const
SF_SRM_AVAILABLE = {$IFDEF SF_SimpleRobustMutex}True{$ELSE}False{$ENDIF};
{===============================================================================
Library-specific exceptions
===============================================================================}
type
ESFException = class({$IFDEF UseAuxExceptions}EAEGeneralException{$ELSE}Exception{$ENDIF});
ESFTimeError = class(ESFException);
ESFFutexError = class(ESFException);
ESFInvalidValue = class(ESFException);
ESFSignalError = class(ESFException);
ESFParsingError = class(ESFException);
ESFInvalidState = class(ESFException);
{===============================================================================
--------------------------------------------------------------------------------
Futex wrappers
--------------------------------------------------------------------------------
===============================================================================}
{===============================================================================
Futex wrappers - constants and types
===============================================================================}
const
INFINITE = UInt32(-1); // infinite timeout
FUTEX_BITSET_MATCH_ANY = UInt32($FFFFFFFF); // the name says it all :P
type
TFutexWord = cInt;
PFutexWord = ^TFutexWord;
// used only for casting to unsigned counter in simple semaphore
TFutexUWord = cUInt;
PFutexUWord = ^TFutexUWord;
TFutex = TFutexWord;
PFutex = ^TFutex;
//------------------------------------------------------------------------------
{
Values returned from waiting on futex.
fwrWoken - the waiting was ended by FUTEX_WAKE
fwrValue - value of futex did not match parameter Value at the time
of the call
fwrTimeout - waiting timed out
fwrInterrupted - waiting was interrupted (eg. by a signal)
}
type
TFutexWaitResult = (fwrWoken,fwrValue,fwrTimeout,fwrInterrupted);
//------------------------------------------------------------------------------
type
TFutexOperation = (fopSet,fopAdd,fopOR,forANDN,fopXOR);
TFutexComparison = (focEqual,focNotEqual,focLess,focLessOrEqual,focGreater,
focGreaterOrEqual);
{===============================================================================
Futex wrappers - declaration
===============================================================================}
{
FutexWait
Waits on futex until the thread is woken by FUTEX_WAKE, signal, spurious
wakeup or the timeout period elapses.
If the parameter Value does not match content of the futex at the time of
call, the function returns immediately, returning fwrValue.
Setting Private to true indicates that the futex is used only withing
current process and allows system to do some optimizations.
When Realtime is true, the timing will use CLOCK_REALTIME clock instead of
CLOCK_MONOTONIC. Supported only from Linux 4.5 up.
What caused the function to return is indicated by returned value.
WARNING - even when the function returns fwrWoken, it does not
necessarily mean the waiter was explicitly woken,
always consider it being a spurious wakeup.
}
Function FutexWait(var Futex: TFutexWord; Value: TFutexWord; Timeout: UInt32 = INFINITE; Private: Boolean = False; Realtime: Boolean = False): TFutexWaitResult;
{
FutexWaitNoInt
Behaves the same as FutexWait, but it will never return fwrInterrupted.
If the waiting is ended by a cause that falls into that category, the
function recalculates timeout in relation to already elapsed time and
re-enters waiting.
WARNING - do not use if the waiting can be requeued to a different futex.
If the waiting is requeued and then is interrupted, this call
will re-enter waiting on the original futex, not on the one
to which it was requeued.
}
Function FutexWaitNoInt(var Futex: TFutexWord; Value: TFutexWord; Timeout: UInt32 = INFINITE; Private: Boolean = False; Realtime: Boolean = False): TFutexWaitResult;
{
FutexWake
Wakes at least one and at most Count threads waiting on the given futex.
If passed Count is negative, it will try to wake MAXINT (2147483647)
threads.
Private indicates whether the futex is used only withing the current
process.
Returs number of woken waiters.
}
Function FutexWake(var Futex: TFutexWord; Count: Integer; Private: Boolean = False): Integer;
{
FutexFD
Creates a file descriptor that is associated with the futex.
Value is used for asynchronous notification via signals (value stored here,
when non-zero, denotes the signal number), for details refer to futex
documentation.
Private indicates whether the futex is used only withing the current
process.
Returs created file descriptor.
WARNING - support for this call was removed in linux 2.6.26.
}
Function FutexFD(var Futex: TFutexWord; Value: Integer; Private: Boolean = False): Integer;
{
FutexRequeue
Requeues waiting from one futex (Futex) to another (Futex2).
Please refer to futex documentation for detailed description of requeue
operation.
WakeCount is maximum number of woken waiters (any negative value is
translated to MAXINT).
RequeueCount is maximum number of requeued waiters (any negative value is
translated to MAXINT).
Private indicates whether the futex is used only withing the current
process.
Returns number of woken waiters.
}
Function FutexRequeue(var Futex: TFutexWord; var Futex2: TFutexWord; WakeCount,RequeueCount: Integer; Private: Boolean = False): Integer;
{
FutexCmpRequeue
Works the same as FutexRequeue, but it first checks whether Futex contains
value passed in parameter Value. If not, then the function will immediately
exit, returning a negative value.
For detailed description of compare-requeue operation, refer to futex
documentation.
WakeCount gives maximum number of woken waiters (any negative value is
translated to MAXINT).
RequeueCount is maximum number of requeued waiters (any negative value is
translated to MAXINT).
Private indicates whether the futex is used only withing the current
process.
When FutexCmpRequeue returns any negative number, it indicates that value
of Futex variable did not match Value parameter at the time of call.
Otherwise it returns a sum of woken and requeued waiters.
}
Function FutexCmpRequeue(var Futex: TFutexWord; Value: TFutexWord; var Futex2: TFutexWord; WakeCount,RequeueCount: Integer; Private: Boolean = False): Integer;
{
FutexWakeOp
Executes the following sequence atomically and totally ordered in respect to
other futex operations on any of the two supplied futex words:
uint32_t oldval = *(uint32_t *) uaddr2;
*(uint32_t *) uaddr2 = oldval op oparg;
futex(uaddr, FUTEX_WAKE, val, 0, 0, 0);
if (oldval cmp cmparg)
futex(uaddr2, FUTEX_WAKE, val2, 0, 0, 0);
...or, in local nomenclature:
_OldVal := Futex2;
Futex2 := _OldVal Op OpArg;
FutexWake(Futex,Count);
If _OldVal Cmp CmpArg then
FutexWake(Futex2,Count2);
For more details, refer to documentation of futexes (FUTEX_WAKE_OP).
If passed Count or Count2 is negative, it will be translated to MAXINT
(2147483647).
Private indicates whether the futexes are used only withing the current
process.
Returns a sum of woken waiters from both futexes.
}
Function FutexWakeOp(var Futex: TFutexWord; Count: Integer; var Futex2: TFutexWord; Count2: Integer;
Op: TFutexOperation; OpArg: Integer; Cmp: TFutexComparison; CmpArg: Integer;
OpArgShift: Boolean = False; Private: Boolean = False): Integer;
{
FutexWaitBitSet
Works the same as FutexWait, but the waiter has a bitmask stored in its
in-kernel state.
NOTE - the BitMask must not be zero.
}
Function FutexWaitBitSet(var Futex: TFutexWord; Value: TFutexWord; BitMask: UInt32; Timeout: UInt32 = INFINITE; Private: Boolean = False; Realtime: Boolean = False): TFutexWaitResult;
{
FutexWaitBitSet
Works the same as FutexWaitBitSet, but it will never return fwrInterrupted
(see FutexWaitNoInt).
NOTE - the BitMask must not be zero.
WARNING - do not use if the waiting can be requeued to a different futex.
}
Function FutexWaitBitSetNoInt(var Futex: TFutexWord; Value: TFutexWord; BitMask: UInt32; Timeout: UInt32 = INFINITE; Private: Boolean = False; Realtime: Boolean = False): TFutexWaitResult;
{
FutexWakeBitSet
Works the same as FutexWake, but it wakes only waiters that were added to
waiting with a bitmask that has at least one set bit common with BitMask
parameter passed in here (ie. bit-wise AND of BitMask and mask passed to
FutexWaitBitSet(NoInt) must produce non-zero result).
If you set BitMask to FUTEX_BITSET_MATCH_ANY (all bits set), then the call is
equivalent to FutexWake.
NOTE - the BitMask must not be zero.
}
Function FutexWakeBitSet(var Futex: TFutexWord; Count: Integer; BitMask: UInt32 = FUTEX_BITSET_MATCH_ANY; Private: Boolean = False): Integer;
{
Priority-inheritance futexes
For descriptions of PI operations, refer to official documentation of futexes.
NOTE - if FutexLockPI(2) returns fwrValue, you should try the lock again
(it indicates that the owner of lock is about to exit but has not
yet cleared the internal state).
}
Function FutexLockPI(var Futex: TFutexWord; Timeout: UInt32 = INFINITE; Private: Boolean = False): TFutexWaitResult;
Function FutexLockPI2(var Futex: TFutexWord; Timeout: UInt32 = INFINITE; Private: Boolean = False; Realtime: Boolean = False): TFutexWaitResult;
Function FutexTryLockPI(var Futex: TFutexWord; Private: Boolean = False): Boolean;
Function FutexUnlockPI(var Futex: TFutexWord; Private: Boolean = False): Boolean;
Function FutexCmpRequeuePI(var Futex: TFutexWord; Value: TFutexWord; var Futex2: TFutexWord; RequeueCount: Integer; Private: Boolean = False): Integer;
Function FutexWaitRequeuePI(var Futex: TFutexWord; Value: TFutexWord; var Futex2: TFutexWord; Timeout: UInt32 = INFINITE; Private: Boolean = False; Realtime: Boolean = False): TFutexWaitResult;
{===============================================================================
--------------------------------------------------------------------------------
Simple mutex
--------------------------------------------------------------------------------
===============================================================================}
{
Simple mutex (SM) behaves like a basic mutex or critical section - only one
thread can lock it and no other thread can lock it again until it is unlocked.
If the SM is locked, the SimpleMutexLock function will block until the SM is
unlocked by other thread.
A call to SimpleMutexLock must be paired with SimpleMutexUnlock.
Calling SimpleMutexUnlock on an unlocked SM is allowed.
WARNING - Simple mutex is not recursive. Calling SimpleMutexLock on a
locked mutex in the same thread will block indefinitely, creating
a deadlock.
WARNING - Simple mutex is not robust. If a thread fails to unlock the SM,
it will stay locked indefinitely (but note that it can be
unlocked by any thread - SM is not a classical mutex with thread
ownership).
NOTE - Simple mutex does not need to be explicitly initialized if it is
set to all zero by other means (eg. memory initialization).
}
{===============================================================================
Simple Mutex - declaration
===============================================================================}
procedure SimpleMutexInit(out Futex: TFutexWord);
procedure SimpleMutexLock(var Futex: TFutexWord);
procedure SimpleMutexUnlock(var Futex: TFutexWord);
{===============================================================================
--------------------------------------------------------------------------------
Simple semaphore
--------------------------------------------------------------------------------
===============================================================================}
{
Only very basic implementation of semaphore (counter synchronizer) is
provided.
If count is greater than zero, it is signaled (unlocked). If zero, it is
non-signaled (locked).
SimpleSemaphoreWait decrements (with unsigned saturation) the count. If the
count was zero before the call, it will enter waiting and blocks until the
semaphore counter becomes positive again trough a call to SimpleSemaphorePost.
SimpleSemaphorePost increments (with unsigned saturation) the count and tries
to wake as many waiters as is the current count.
Simple semaphore does not need to be initialized explicitly - it is enough
to set it to any integer or zero, which can be done eg. through memory
initialization.
}
{===============================================================================
Simple semaphore - declaration
===============================================================================}
procedure SimpleSemaphoreInit(out Futex: TFutexWord; InitialCount: UInt32);
procedure SimpleSemaphoreWait(var Futex: TFutexWord);
procedure SimpleSemaphorePost(var Futex: TFutexWord);
{$IFDEF SF_SimpleRobustMutex}
{===============================================================================
--------------------------------------------------------------------------------
Simple robust mutex
--------------------------------------------------------------------------------
===============================================================================}
{
Simple robust mutex (SRM) behaves like simple mutex declared above - only one
thread can lock it and no other thread can lock it again until it is unlocked.
But unlike simple mutex, this object is quasi-robust (full robustness would
be provided by the OS, here it is not - see further). That means it can be
locked again if thread that locked it previously exits without unlocking it.
If the SRM is locked, the SimpleRobustMutexLock function will block until the
SRM is unlocked by other thread or the thread that locked it previously exits
(by any means - it can end, crash, exit, be terminated, whatever causes it to
cease to exist). If the SimpleRobustMutexLock returns, the mutex is guaranteed
to be locked for use by the calling thread.
A call to SimpleRobustMutexLock must be paired with SimpleRobustMutexUnlock.
Calling SimpleRobustMutexUnlock on an already unlocked SRM is allowed and
does not pose any problem.
SimpleRobustMutexInit initializes the mutex to an unlocked state.
WARNING - SRM is not recursive. Calling SimpleRobustMutexLock on a locked
mutex in the same thread will block indefinitely, creating a
deadlock.
NOTE - Simple robust mutex does not neeed to be explicitly initialized if
it is set to all zero by other means (eg. memory initialization),
but, in such a case, calling memory fencing instruction is highly
recommended (you can use eg. function ReadWriteBarrier from library
InterlockedOps - function SimpleRobustMutexInit calls this function
automatically).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
And now few words on the robustness...
As mentioned previously, this object is not fully robust - full robustness
would necessitate a cooperation with operating system, and unfortunatelly
there is no way I am aware of to achieve this.
Note that I know about robust futex lists provided by Linux (functions
get_robust_list and set_robust_list and things around it), but the way they
are implemented precludes them from being used by multiple libraries. And
since glibc already uses them for posix mutexes, we cannot use them here.
So, to emulate the robustness, current implementation is following:
Initialy, the internal state of the SRM is set to all zero, which indicates
that the mutex is unlocked.
When any thread calls a locking function (that is, any overload of function
SimpleRobustMutexLock), and the SRM is unlocked by that point, the calling
thread stores some information about itself into the state, marking it as
locked, and returns immediately.
If SRM is already locked when another thread tries to lock it, then this
thread enters a waiting - note that unlike in simple mutex, this waiting is
semi-active.
The waiting thread is entering passive waiting on futex, but is periodically
awakened to check whether thread that previously locked the mutex still
exists within the system. Length of this periode can be changed by setting
CheckInterval parameter (in milliseconds). Method used to discern whether
the thread still lives depends on symbol RobustMutexThreadTimeCheck and
value of parameter CheckMethod (see further).
This semi-active waiting is necessary because when a lock-holding thread
dies, other threads that could be in infinite passive waiting would NOT be
awakened, creating a deadlock.
When waiter finds that lock-holding thread does not exist, it relocks the
mutex for itself and normally returns.
Methods used to check existence of lock-holding thread are following (note
that libraries compiled with and without the RobustMutexThreadTimeCheck
symbol defined are mutually incompatible):
> Symbol RobustMutexThreadTimeCheck defined
Parameter CheckMethod is ignored.
The SRM state contains locking thread's ID and its time of creation
(lower 32bits - see notes in implementation for details), as stored
in file "/proc/[tid]/stat".
First, it is checked whether the file "/proc/[tid]/stat" actually
exists. If it doesn't, it is assumed the thread does not live anymore.
When it does exist, it is opened and the creation time is parsed from
it. This obtained time is then checked against a value stored in the
SRM state. If they match, then the lock-holding thread still lives and
waiter enters passive period before checking again. If they do not
match, then it is assumed the file belongs to other thread than the one
that locked the mutex (it just have the same TID) and the original
locker is dead.
If the stat file cannot be opened (raises an exception), but it still
exists, then this situation is counted towards a consecutive failure
count.
Parameter MaxConsecFailCount limits how many times this can happen in a
row before the exception is re-raised and the locking function crashes
with it. Whenever the file is successfully accessed, this count is
reset to zero.
This is here to protect against situation where the stat file is only
temporarily inaccessible.
This check is slightly more reliable than the following ones, but it
does depend on stat file being accessible, and also is slightly slower
than other methods.
> Symbol RobustMutexThreadTimeCheck not defined
WARNING - all methods in this group assume that IDs are not frequently
or even readily reused for new threads, and that situation
where thread of specific ID is running within a process of
specific ID is extremely rare and unlikely to happen
multiple-times on short timescales.
Here, the SRM state stores locking thread's ID and ID of process this
thread is running within.
Parameter CheckMethod is used to select one of the following methods:
> tcmDefault, tcmSignal
Existence of a thread is checked by trying to send a signal to that
thread using system call kill(tid,0) (this in fact does not send
anything, it just checks recipient existence).
If this call succeeds, then it is assumed the thread of given ID
(tid) exists. If the call fails with error ESRCH, it is assumed the
thread does not exist. In case of failure with other error, an
exception of class ESFSignalError is raised.
But checking that the thread exists is not enough to be sure it is
the thread that made the lock. Therefore, the abovementioned
algorith is first used to check whether the locking thread (thread
ID) exists, then whether its parent process exists (process ID)
and, if both do exist, whether the given process is really a parent
process of that thread.
Only if all this holds true, then the locking thread is asumed to
be still alive, othervise it is presumed dead.
> tcmProc
This method is simple - a check is made whether directory
"/proc/[pid]/task/[tid]" (tid = ID if locking thread, pid = ID of
the locking thread's parent process) exists or not. When it does,
the thread exists, when it doesn't, the locking thread is presumed
dead.
This method of course rely on accessibility of mentioned directory.
}
{===============================================================================
Simple robust mutex - declaration
===============================================================================}
type
TSimpleRobustMutexState = record
case Integer of
0: (FullWidth: UInt64);
1: (ThreadID: pid_t;
ProcessID: pid_t);
2: (FutexWord: TFutexWord;
ThreadCTime: UInt32); // creation time of the locking thread
end;
PSimpleRobustMutexState = ^TSimpleRobustMutexState;
{$IF SizeOf(TSimpleRobustMutexState) <> 8} // just a slight paranoia
{$MESSAGE FATAL 'Invalid size of simple robust mutex record.'}
{$IFEND}
//------------------------------------------------------------------------------
type
// tcmDefault is equivalent to tcmSignal
TThreadCheckMethod = (tcmDefault,tcmSignal,tcmProc);
{
TSimpleRobustMutexInfo
This structure is used to pass settings and other information in and out of
SimpleRobustMutexLock call.
WARNING - this structure can be changed in the future, and therefore using
it and its overload of SimpleRobustMutexLock is not recommended.
CheckInterval (in) - Number of milliseconds to wait between checks
whether the thread that previously locked the
mutex we are trying to acquire still lives.
CheckMethod (in,out) - Method that should be used to check whether the
locking thread still lives.
When symbol RobustMutexThreadTimeCheck is defined,
then it will be always changed to tcmDefault upon
return from the locking call, irrespective of its
previous value. If this symbol is not defined,
then it will not change during the call.
CheckCount (out) - Indicates how many times the call checked whether
the locking thread still lives. It will be zero
if the SRM was unlocked and immediately acquired.
FailCount (out) - Contains total number of failures to obtain
locking thread creation time.
Used only when RobustMutexThreadTimeCheck is
defined, otherwise it will always be zero upon
return.
MaxConsecFailCount (in) - Maximum number of consecutive failures to read
lock-holding thread creation time before an
exception is raised.
Used only when RobustMutexThreadTimeCheck is
defined, otherwise it is ignored.
ConsecFailCount (out) - Number of consecutive failures to obtain thread
creation time in the last failure sequence.
This field is used only internally. It is reset
everytime a successful read is done, therefore it
will usually contain zero.
Used only when RobustMutexThreadTimeCheck is
defined.
If some details in field descriptions are not clear, then refer higher to
description of SRM itself, part where robustness is talked about.
}
TSimpleRobustMutexInfo = record
CheckInterval: UInt32;
CheckMethod: TThreadCheckMethod;
CheckCount: Integer;
FailCount: Integer;
MaxConsecFailCount: Integer;
ConsecFailCount: Integer;
end;
//------------------------------------------------------------------------------
procedure SimpleRobustMutexInit(out RobustMutex: TSimpleRobustMutexState);
{
The first overload of SimpleRobustMutexLock (the one with Info parameter)
is intended for debugging. You can use it if you wish, but it is generally
not recommended as the type TSimpleRobustMutexInfo can change in the future.
}
procedure SimpleRobustMutexLock(var RobustMutex: TSimpleRobustMutexState; var Info: TSimpleRobustMutexInfo); overload;
procedure SimpleRobustMutexLock(var RobustMutex: TSimpleRobustMutexState; CheckInterval: UInt32 = 100; CheckMethod: TThreadCheckMethod = tcmDefault); overload;
procedure SimpleRobustMutexLock(var RobustMutex: TSimpleRobustMutexState; CheckMethod: TThreadCheckMethod); overload;
procedure SimpleRobustMutexUnlock(var RobustMutex: TSimpleRobustMutexState);
{===============================================================================
--------------------------------------------------------------------------------
Simple recursive mutex
--------------------------------------------------------------------------------
===============================================================================}
{
Simple recursive mutex (SCM) is implemented only as an extension of simple
robust mutex (SRM), therefore most information provided for SRM are also
valid here.
WARNING - SCM and SRM have different internal state, so do not attempt to
use state of one in calls to the other.
SCM differs from SRM in the following details:
- As name suggests, it is recursive, meaning it can be locked multiple
times by the same thread.
WARNING - each locking call must be paired by a call to unlock. Mutex
stays locked until the same number of unlocks as locks is
called.
- SCM observes thread ownership, so unlocking operation can only be
performed by a thread that is holding the lock.
WARNING - calling unlock from other thread will raise an exception of
class ESFInvalidState.
NOTE - calling unlock on already unlocked mutex does nothing (exception
is not raised).
}
{===============================================================================
Simple recursive mutex - declaration
===============================================================================}
type
TSimpleRecursiveMutexState = record
Mutex: TSimpleRobustMutexState;
Counter: Integer;
end;
PSimpleRecursiveMutexState = ^TSimpleRecursiveMutexState;
//------------------------------------------------------------------------------
procedure SimpleRecursiveMutexInit(out RecursiveMutex: TSimpleRecursiveMutexState);
procedure SimpleRecursiveMutexLock(var RecursiveMutex: TSimpleRecursiveMutexState; var Info: TSimpleRobustMutexInfo); overload;
procedure SimpleRecursiveMutexLock(var RecursiveMutex: TSimpleRecursiveMutexState; CheckInterval: UInt32 = 100; CheckMethod: TThreadCheckMethod = tcmDefault); overload;
procedure SimpleRecursiveMutexLock(var RecursiveMutex: TSimpleRecursiveMutexState; CheckMethod: TThreadCheckMethod); overload;
procedure SimpleRecursiveMutexUnlock(var RecursiveMutex: TSimpleRecursiveMutexState);
{$ENDIF}
{===============================================================================
--------------------------------------------------------------------------------
TSimpleSynchronizer
--------------------------------------------------------------------------------
===============================================================================}
{
Common ancestor class for wrappers around implemented simple synchronization
primitives.
Instance can either be created as standalone or as shared.
Standalone instance is created by constructor that does not expect an
external state variable. The state is completely internal and is managed
automatically. To properly use it, create one instance and use this one
object in all synchronizing threads.
Shared instace expects pre-existing state variable (be it futex word for
simple mutex and simple semaphore, or TSimpleRobustMutexState variable
for simple robust mutex) to be passed to the constructor. This state is
then used for locking. To use this mode, allocate a state variable and
create new instance from this one state for each synchronizing thread.
Note that you are responsible for state management (initialization,
finalization) - you can use methods Init and Final to do so if you do not
want to use procedural interface (note that these methods will always
initialize and finalize the state whether it is in a shared instance or
standalone instance).
}
type
{$IFDEF SF_SimpleRobustMutex}
TSimpleSynchronizerState = array[0..Pred(SizeOf(TSimpleRecursiveMutexState))] of Byte;
{$ELSE}
TSimpleSynchronizerState = array[0..Pred(SizeOf(TFutexWord))] of Byte;
{$ENDIF}
PSimpleSynchronizerState = ^TSimpleSynchronizerState;
{===============================================================================
TSimpleSynchronizer - class declaration
===============================================================================}
type
TSimpleSynchronizer = class(TCustomObject)
protected
fLocalState: TSimpleSynchronizerState;
fStatePtr: PSimpleSynchronizerState;
fOwnsState: Boolean;
procedure Initialize(StatePtr: PSimpleSynchronizerState); virtual;
procedure Finalize; virtual;
public
constructor Create(StatePtr: PSimpleSynchronizerState); overload; virtual;
constructor Create(var Futex: TFutexWord); overload; virtual;
constructor Create; overload; virtual;
destructor Destroy; override;
procedure Init; virtual;
procedure Final; virtual;
end;
{===============================================================================
--------------------------------------------------------------------------------
TSimpleMutex
--------------------------------------------------------------------------------
===============================================================================}
{===============================================================================
TSimpleMutex - class declaration
===============================================================================}
type
TSimpleMutex = class(TSimpleSynchronizer)
protected
procedure Initialize(StatePtr: PSimpleSynchronizerState); override;
public
procedure Init; override;
procedure Enter; virtual;
procedure Leave; virtual;
end;
{===============================================================================
--------------------------------------------------------------------------------
TSimpleSemaphore
--------------------------------------------------------------------------------
===============================================================================}
{===============================================================================
TSimpleSemaphore - class declaration
===============================================================================}
type
TSimpleSemaphore = class(TSimpleSynchronizer)
protected
fInitialCount: UInt32;
procedure Initialize(StatePtr: PSimpleSynchronizerState); override;
public
constructor CreateAndInitCount(InitialCount: UInt32); overload; virtual;
procedure Init; override; // initialzes to a value of property InitialCount
procedure Acquire; virtual;
procedure Release; virtual;
{
Note that if this object is created using constructors without InitialCount
argument (ie. initial count is not explicitly given during construction),
then property InitialCount is set to a value present in the semaphore's
state during the construction, which might not be a value you expect.
}
property InitialCount: UInt32 read fInitialCount write fInitialCount;
end;
{$IFDEF SF_SimpleRobustMutex}
{===============================================================================
--------------------------------------------------------------------------------
TSimpleRobustMutex
--------------------------------------------------------------------------------
===============================================================================}
{===============================================================================
TSimpleRobustMutex - class declaration
===============================================================================}
type
TSimpleRobustMutex = class(TSimpleSynchronizer)
protected
procedure Initialize(StatePtr: PSimpleSynchronizerState); override;
public
constructor Create(var SimpleRobustMutexState: TSimpleRobustMutexState); overload; virtual;
procedure Init; override;
procedure Enter; virtual;
procedure Leave; virtual;
end;
{===============================================================================
--------------------------------------------------------------------------------
TSimpleRecursiveMutex
--------------------------------------------------------------------------------
===============================================================================}
{===============================================================================
TSimpleRecursiveMutex - class declaration
===============================================================================}
type
TSimpleRecursiveMutex = class(TSimpleSynchronizer)
protected
procedure Initialize(StatePtr: PSimpleSynchronizerState); override;
public
constructor Create(var SimpleRecursiveMutexState: TSimpleRecursiveMutexState); overload; virtual;
procedure Init; override;
procedure Enter; virtual;
procedure Leave; virtual;
end;
{$ENDIF}
implementation
uses
BaseUnix, Linux, Errors{$IFDEF SF_SimpleRobustMutex}, Syscall, Math{$ENDIF},
Classes;
{$IFDEF FPC_DisableWarns}
{$DEFINE FPCDWM}
{$DEFINE W4055:={$WARN 4055 OFF}} // Conversion between ordinals and pointers is not portable
{$ENDIF}
{===============================================================================
Futex system constants
===============================================================================}
const
FUTEX_WAIT = 0;
FUTEX_WAKE = 1;
FUTEX_FD = 2; // removed in Linux 2.6.26
FUTEX_REQUEUE = 3;
FUTEX_CMP_REQUEUE = 4;
FUTEX_WAKE_OP = 5;
FUTEX_LOCK_PI = 6;
FUTEX_UNLOCK_PI = 7;
FUTEX_TRYLOCK_PI = 8;
FUTEX_WAIT_BITSET = 9;
FUTEX_WAKE_BITSET = 10;
FUTEX_WAIT_REQUEUE_PI = 11;
FUTEX_CMP_REQUEUE_PI = 12;
FUTEX_LOCK_PI2 = 13; // since Linux 5.14
FUTEX_PRIVATE_FLAG = 128;
FUTEX_CLOCK_REALTIME = 256;
FUTEX_OP_SET = 0; // uaddr2 = oparg
FUTEX_OP_ADD = 1; // uaddr2 += oparg
FUTEX_OP_OR = 2; // uaddr2 |= oparg
FUTEX_OP_ANDN = 3; // uaddr2 &= ~oparg
FUTEX_OP_XOR = 4; // uaddr2 ^= oparg
FUTEX_OP_ARG_SHIFT = 8; // use (1 << oparg) as operand
FUTEX_OP_CMP_EQ = 0; // if (oldval == cmparg) wake
FUTEX_OP_CMP_NE = 1; // if (oldval != cmparg) wake
FUTEX_OP_CMP_LT = 2; // if (oldval < cmparg) wake
FUTEX_OP_CMP_LE = 3; // if (oldval <= cmparg) wake
FUTEX_OP_CMP_GT = 4; // if (oldval > cmparg) wake
FUTEX_OP_CMP_GE = 5; // if (oldval >= cmparg) wake