Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MultiReadSingleWriteObject.cs
1using System;
3using System.Diagnostics;
4using System.Threading;
5using System.Threading.Tasks;
7
9{
15 {
16 private static readonly Stopwatch watch = new Stopwatch();
17
19 {
20 watch.Start();
21 }
22
23 private readonly bool recordStackTraces;
24 private readonly object owner;
25 private readonly string creatorStackTrace;
26 private string lockStackTrace;
27 private LinkedList<WaitRec> noWriters = new LinkedList<WaitRec>();
28 private LinkedList<WaitRec> noReadersOrWriters = new LinkedList<WaitRec>();
29 private readonly object synchObj = new object();
30 private long token = 0;
31 private long start = 0;
32 private int nrReaders = 0;
33 private bool isWriting = false;
34 private bool disposed = false;
35
36 private class WaitRec
37 {
38 public TaskCompletionSource<bool> Pending;
39 public string StackTrace;
40
41 public WaitRec(bool RecordStackTraces)
42 {
43 this.Pending = new TaskCompletionSource<bool>();
44
46 this.StackTrace = Environment.StackTrace;
47 else
48 this.StackTrace = null;
49 }
50 }
51
58 : this()
59 {
60 this.owner = Owner;
61 }
62
68#if DEBUG
69 : this(true)
70#else
71 : this(false)
72#endif
73 {
74 }
75
81 : this(RecordStackTraces)
82 {
83 this.owner = Owner;
84 }
85
93 {
94 this.recordStackTraces = RecordStackTraces;
95
96 if (this.recordStackTraces)
97 this.creatorStackTrace = Environment.StackTrace;
98 else
99 this.creatorStackTrace = null;
100
101 this.lockStackTrace = null;
102 }
103
107 public object Owner => this.owner;
108
113 public bool RecordStackTraces => this.recordStackTraces;
114
118 public string CreatorStackTrace => this.creatorStackTrace;
119
123 public string LockStackTrace => this.lockStackTrace;
124
128 public int NrReaders
129 {
130 get
131 {
132 lock (this.synchObj)
133 {
134 if (this.disposed)
135 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
136
137 return this.nrReaders;
138 }
139 }
140 }
141
145 public bool IsReading
146 {
147 get
148 {
149 lock (this.synchObj)
150 {
151 if (this.disposed)
152 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
153
154 return this.nrReaders > 0;
155 }
156 }
157 }
158
162 public bool IsWriting
163 {
164 get
165 {
166 lock (this.synchObj)
167 {
168 if (this.disposed)
169 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
170
171 return this.isWriting;
172 }
173 }
174 }
175
180 {
181 get
182 {
183 lock (this.synchObj)
184 {
185 if (this.disposed)
186 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
187
188 return this.nrReaders > 0 || this.isWriting;
189 }
190 }
191 }
192
196 public int QueueSize
197 {
198 get
199 {
200 lock (this.synchObj)
201 {
202 if (this.disposed)
203 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
204
205 return this.noReadersOrWriters.Count;
206 }
207 }
208 }
209
213 public string[] QueuedStackTraces
214 {
215 get
216 {
217 ChunkedList<string> Result = null;
218
219 lock (this.synchObj)
220 {
221 if (this.disposed)
222 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
223
224 foreach (WaitRec Rec in this.noReadersOrWriters)
225 {
226 if (!string.IsNullOrEmpty(Rec.StackTrace))
227 {
228 if (Result is null)
229 Result = new ChunkedList<string>();
230
231 Result.Add(Rec.StackTrace);
232 }
233 }
234 }
235
236 return Result?.ToArray() ?? Array.Empty<string>();
237 }
238 }
239
243 public long TicksLocked
244 {
245 get
246 {
247 lock (this.synchObj)
248 {
249 if (this.disposed)
250 return 0;
251 else if (this.nrReaders > 0 || this.isWriting)
252 return watch.ElapsedTicks - this.start;
253 else
254 return 0;
255 }
256 }
257 }
258
262 public double MillisecondsLocked => this.TicksLocked * 1000.0 / Stopwatch.Frequency;
263
267 public bool Disposed => this.disposed;
268
273 public void AssertReading()
274 {
275 lock (this.synchObj)
276 {
277 if (this.disposed)
278 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
279
280 if (this.nrReaders <= 0)
281 throw new InvalidOperationException("Not in a reading state.");
282 }
283 }
284
289 public void AssertWriting()
290 {
291 lock (this.synchObj)
292 {
293 if (this.disposed)
294 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
295
296 if (!this.isWriting)
297 throw new InvalidOperationException("Not in a writing state.");
298 }
299 }
300
306 {
307 lock (this.synchObj)
308 {
309 if (this.disposed)
310 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
311
312 if (this.nrReaders <= 0 && !this.isWriting)
313 throw new InvalidOperationException("Not in a reading or writing state.");
314 }
315 }
316
323 public long Token
324 {
325 get
326 {
327 lock (this.synchObj)
328 {
329 if (this.disposed)
330 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
331
332 return this.token;
333 }
334 }
335 }
336
342 public virtual async Task<int> BeginRead()
343 {
344 if (this.disposed)
345 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
346
347 WaitRec Wait = null;
348 int Result = 0;
349 bool RecordStackTrace = false;
350
351 while (true)
352 {
353 lock (this.synchObj)
354 {
355 if (!this.isWriting)
356 {
357 if (this.nrReaders == 0)
358 {
359 this.start = watch.ElapsedTicks;
360 this.token++;
361
362 if (this.recordStackTraces)
363 RecordStackTrace = true;
364 }
365
366 Result = ++this.nrReaders;
367 }
368 else
369 {
370 Wait = new WaitRec(this.recordStackTraces);
371 this.noWriters.AddLast(Wait);
372 }
373 }
374
375 if (Wait is null)
376 {
377 if (RecordStackTrace)
378 this.lockStackTrace = Environment.StackTrace;
379
380 return Result;
381 }
382 else
383 {
384 await Wait.Pending.Task;
385 Wait = null;
386 }
387 }
388 }
389
395 public virtual Task<int> EndRead()
396 {
397 if (this.disposed)
398 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
399
400 LinkedList<WaitRec> List = null;
401
402 lock (this.synchObj)
403 {
404 if (this.nrReaders <= 0)
405 throw new InvalidOperationException("Not in a reading state.");
406
407 this.nrReaders--;
408 if (this.nrReaders == 0)
409 {
410 this.token++;
411 this.start = 0;
412
413 if (this.recordStackTraces)
414 this.lockStackTrace = null;
415 }
416 else
417 return Task.FromResult(this.nrReaders);
418
419 if (this.noReadersOrWriters.First is null)
420 return Task.FromResult(0);
421
422 List = this.noReadersOrWriters;
423 this.noReadersOrWriters = new LinkedList<WaitRec>();
424 }
425
426 foreach (WaitRec Rec in List)
427 Rec.Pending.TrySetResult(true);
428
429 return Task.FromResult(0);
430 }
431
438 public virtual async Task<bool> TryBeginRead(int Timeout)
439 {
440 if (this.disposed)
441 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
442
443 WaitRec Wait = null;
444 DateTime Start = DateTime.UtcNow;
445 bool RecordStackTrace = false;
446
447 while (true)
448 {
449 lock (this.synchObj)
450 {
451 if (!this.isWriting)
452 {
453 if (this.nrReaders == 0)
454 {
455 this.start = watch.ElapsedTicks;
456 this.token++;
457
458 if (this.recordStackTraces)
459 RecordStackTrace = true;
460 }
461
462 this.nrReaders++;
463 }
464 else if (Timeout <= 0)
465 return false;
466 else
467 {
468 Wait = new WaitRec(this.recordStackTraces);
469 this.noWriters.AddLast(Wait);
470 }
471 }
472
473 if (Wait is null)
474 {
475 if (RecordStackTrace)
476 this.lockStackTrace = Environment.StackTrace;
477
478 return true;
479 }
480 else
481 {
482 DateTime Now = DateTime.UtcNow;
483 bool Result;
484
485 using (Timer Timer = new Timer((P) =>
486 {
487 Wait?.Pending.TrySetResult(false);
488
489 }, null, Timeout, System.Threading.Timeout.Infinite))
490 {
491 Result = await Wait.Pending.Task;
492 }
493
494 if (!Result)
495 {
496 lock (this.synchObj)
497 {
498 this.noWriters.Remove(Wait);
499 }
500
501 return false;
502 }
503
504 Timeout -= (int)((Now - Start).TotalMilliseconds + 0.5);
505 Start = Now;
506 Wait = null;
507 }
508 }
509 }
510
518 public virtual async Task<bool> TryBeginRead(CancellationToken Cancel)
519 {
520 if (this.disposed)
521 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
522
523 if (Cancel.CanBeCanceled)
524 {
525 WaitRec Wait = null;
526 bool RecordStackTrace = false;
527
528 Cancel.Register(() =>
529 {
530 Wait?.Pending.TrySetResult(false);
531 });
532
533 while (true)
534 {
535 lock (this.synchObj)
536 {
537 if (!this.isWriting)
538 {
539 if (this.nrReaders == 0)
540 {
541 this.start = watch.ElapsedTicks;
542 this.token++;
543
544 if (this.recordStackTraces)
545 RecordStackTrace = true;
546 }
547
548 this.nrReaders++;
549 }
550 else if (Cancel.IsCancellationRequested)
551 return false;
552 else
553 {
554 Wait = new WaitRec(this.recordStackTraces);
555 this.noWriters.AddLast(Wait);
556 }
557 }
558
559 if (Wait is null)
560 {
561 if (RecordStackTrace)
562 this.lockStackTrace = Environment.StackTrace;
563
564 return true;
565 }
566 else
567 {
568 bool Result = await Wait.Pending.Task;
569
570 if (!Result)
571 {
572 lock (this.synchObj)
573 {
574 this.noWriters.Remove(Wait);
575 }
576
577 return false;
578 }
579
580 Wait = null;
581 }
582 }
583 }
584 else
585 {
586 await this.BeginRead();
587 return true;
588 }
589 }
590
595 public virtual async Task BeginWrite()
596 {
597 if (this.disposed)
598 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
599
600 WaitRec Prev = null;
601 WaitRec Wait = null;
602 bool RecordStackTrace = false;
603
604 while (true)
605 {
606 lock (this.synchObj)
607 {
608 if (!(Prev is null))
609 {
610 this.noWriters.Remove(Prev); // In case previously locked for reading
611 this.noReadersOrWriters.Remove(Prev);
612 }
613
614 if (this.nrReaders == 0 && !this.isWriting)
615 {
616 this.start = watch.ElapsedTicks;
617 this.token++;
618 this.isWriting = true;
619
620 if (this.recordStackTraces)
621 RecordStackTrace = true;
622 }
623 else
624 {
625 Wait = new WaitRec(this.recordStackTraces);
626 this.noReadersOrWriters.AddLast(Wait);
627 this.noWriters.AddLast(Wait);
628 }
629 }
630
631 if (Wait is null)
632 {
633 if (RecordStackTrace)
634 this.lockStackTrace = Environment.StackTrace;
635
636 return;
637 }
638 else
639 {
640 await Wait.Pending.Task;
641 Prev = Wait;
642 Wait = null;
643 }
644 }
645 }
646
651 public virtual Task EndWrite()
652 {
653 if (this.disposed)
654 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
655
656 LinkedList<WaitRec> List = null;
657 LinkedList<WaitRec> List2 = null;
658
659 lock (this.synchObj)
660 {
661 if (!this.isWriting)
662 throw new InvalidOperationException("Not in a writing state.");
663
664 this.token++;
665 this.isWriting = false;
666 this.start = 0;
667
668 if (this.recordStackTraces)
669 this.lockStackTrace = null;
670
671 if (!(this.noReadersOrWriters.First is null))
672 {
673 List = this.noReadersOrWriters;
674 this.noReadersOrWriters = new LinkedList<WaitRec>();
675 }
676
677 if (!(this.noWriters.First is null))
678 {
679 List2 = this.noWriters;
680 this.noWriters = new LinkedList<WaitRec>();
681 }
682 }
683
684 if (!(List is null))
685 {
686 foreach (WaitRec Rec in List)
687 Rec.Pending.TrySetResult(true);
688 }
689
690 if (!(List2 is null))
691 {
692 foreach (WaitRec Rec in List2)
693 Rec.Pending.TrySetResult(true);
694 }
695
696 return Task.CompletedTask;
697 }
698
705 public virtual async Task<bool> TryBeginWrite(int Timeout)
706 {
707 if (this.disposed)
708 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
709
710 WaitRec Prev = null;
711 WaitRec Wait = null;
712 DateTime Start = DateTime.UtcNow;
713 bool RecordStackTrace = false;
714
715 while (true)
716 {
717 lock (this.synchObj)
718 {
719 if (!(Prev is null))
720 {
721 this.noWriters.Remove(Prev); // In case previously locked for reading
722 this.noReadersOrWriters.Remove(Prev);
723 }
724
725 if (this.nrReaders == 0 && !this.isWriting)
726 {
727 this.start = watch.ElapsedTicks;
728 this.token++;
729 this.isWriting = true;
730
731 if (this.recordStackTraces)
732 RecordStackTrace = true;
733 }
734 else if (Timeout <= 0)
735 return false;
736 else
737 {
738 Wait = new WaitRec(this.recordStackTraces);
739 this.noWriters.AddLast(Wait);
740 this.noReadersOrWriters.AddLast(Wait);
741 }
742 }
743
744 if (Wait is null)
745 {
746 if (RecordStackTrace)
747 this.lockStackTrace = Environment.StackTrace;
748
749 return true;
750 }
751 else
752 {
753 DateTime Now = DateTime.UtcNow;
754 bool Result;
755
756 using (Timer Timer = new Timer((P) =>
757 {
758 Wait?.Pending.TrySetResult(false);
759
760 }, null, Timeout, System.Threading.Timeout.Infinite))
761 {
762 Result = await Wait.Pending.Task;
763 }
764
765 if (!Result)
766 {
767 lock (this.synchObj)
768 {
769 this.noWriters.Remove(Wait);
770 this.noReadersOrWriters.Remove(Wait);
771 }
772
773 return false;
774 }
775
776 Timeout -= (int)((Now - Start).TotalMilliseconds + 0.5);
777 Start = Now;
778 Prev = Wait;
779 Wait = null;
780 }
781 }
782 }
783
791 public virtual async Task<bool> TryBeginWrite(CancellationToken Cancel)
792 {
793 if (this.disposed)
794 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
795
796 if (Cancel.CanBeCanceled)
797 {
798 WaitRec Prev = null;
799 WaitRec Wait = null;
800 DateTime Start = DateTime.UtcNow;
801 bool RecordStackTrace = false;
802
803 Cancel.Register(() =>
804 {
805 Prev?.Pending.TrySetResult(false);
806 Wait?.Pending.TrySetResult(false);
807 });
808
809 while (true)
810 {
811 lock (this.synchObj)
812 {
813 if (!(Prev is null))
814 {
815 this.noWriters.Remove(Prev); // In case previously locked for reading
816 this.noReadersOrWriters.Remove(Prev);
817 }
818
819 if (this.nrReaders == 0 && !this.isWriting)
820 {
821 this.start = watch.ElapsedTicks;
822 this.token++;
823 this.isWriting = true;
824
825 if (this.recordStackTraces)
826 RecordStackTrace = true;
827 }
828 else if (Cancel.IsCancellationRequested)
829 return false;
830 else
831 {
832 Wait = new WaitRec(this.recordStackTraces);
833 this.noWriters.AddLast(Wait);
834 this.noReadersOrWriters.AddLast(Wait);
835 }
836 }
837
838 if (Wait is null)
839 {
840 if (RecordStackTrace)
841 this.lockStackTrace = Environment.StackTrace;
842
843 return true;
844 }
845 else
846 {
847 bool Result = await Wait.Pending.Task;
848
849 if (!Result)
850 {
851 lock (this.synchObj)
852 {
853 this.noWriters.Remove(Wait);
854 this.noReadersOrWriters.Remove(Wait);
855 }
856
857 return false;
858 }
859
860 Prev = Wait;
861 Wait = null;
862 }
863 }
864 }
865 else
866 {
867 await this.BeginWrite();
868 return true;
869 }
870 }
871
875 public virtual Task Unlock()
876 {
877 LinkedList<WaitRec> List = null;
878 LinkedList<WaitRec> List2 = null;
879
880 lock (this.synchObj)
881 {
882 this.nrReaders = 0;
883 this.isWriting = false;
884 this.token++;
885 this.start = 0;
886
887 if (this.recordStackTraces)
888 this.lockStackTrace = null;
889
890 if (!(this.noReadersOrWriters.First is null))
891 {
892 List = this.noReadersOrWriters;
893 this.noReadersOrWriters = new LinkedList<WaitRec>();
894 }
895
896 if (!(this.noWriters.First is null))
897 {
898 List2 = this.noWriters;
899 this.noWriters = new LinkedList<WaitRec>();
900 }
901 }
902
903 if (!(List is null))
904 {
905 foreach (WaitRec Rec in List)
906 Rec.Pending.TrySetResult(false);
907 }
908
909 if (!(List2 is null))
910 {
911 foreach (WaitRec Rec in List2)
912 Rec.Pending.TrySetResult(false);
913 }
914
915 return Task.CompletedTask;
916 }
917
921 public virtual void Dispose()
922 {
923 if (this.disposed)
924 throw new ObjectDisposedException(nameof(MultiReadSingleWriteObject));
925 else
926 {
927 this.disposed = true;
928 this.Unlock();
929 }
930 }
931
932 }
933}
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Represents an object that allows single concurrent writers but multiple concurrent readers....
string LockStackTrace
Stack trace from lock of object, if RecordStackTraces is true.
MultiReadSingleWriteObject(object Owner, bool RecordStackTraces)
Represents an object that allows single concurrent writers but multiple concurrent readers....
void AssertReading()
Throws an InvalidOperationException if the object is not in a reading state.
string[] QueuedStackTraces
Recorded stack traces waiting for access.
MultiReadSingleWriteObject()
Represents an object that allows single concurrent writers but multiple concurrent readers....
MultiReadSingleWriteObject(bool RecordStackTraces)
Represents an object that allows single concurrent writers but multiple concurrent readers....
virtual async Task< bool > TryBeginRead(int Timeout)
Waits, at most Timeout milliseconds, until object ready for reading. Each successful call to TryBegi...
virtual Task EndWrite()
Ends a writing session of the object. Must be called once for each call to BeginWrite or successful c...
void AssertReadingOrWriting()
Throws an InvalidOperationException if the object is not in a reading or writing state.
virtual Task Unlock()
Unlocks all locks on the object.
bool IsReading
If the object is in a reading state.
virtual async Task< bool > TryBeginRead(CancellationToken Cancel)
Waits until object ready for reading, or the attempt is cancelled. Each successful call to TryBeginRe...
bool RecordStackTraces
If stack traces should be recorded when object is locked. Default value is true in DEBUG mode and fal...
long Token
Returns a token corresponding to the current lock. It is incremented at the start of a lock-cycle (wh...
double MillisecondsLocked
Number of milliseconds the object has been locked.
long TicksLocked
Number of ticks the object has been locked.
virtual async Task< bool > TryBeginWrite(int Timeout)
Waits, at most Timeout milliseconds, until object ready for writing. Each successful call to TryBegi...
virtual async Task BeginWrite()
Waits until object ready for writing. Each call to BeginWrite must be followed by exactly one call to...
string CreatorStackTrace
Stack trace from creation of object, if RecordStackTraces is true.
virtual Task< int > EndRead()
Ends a reading session of the object. Must be called once for each call to BeginRead or successful ca...
void AssertWriting()
Throws an InvalidOperationException if the object is not in a writing state.
virtual async Task< bool > TryBeginWrite(CancellationToken Cancel)
Waits until object ready for writing, or the attempt is cancelled. Each successful call to TryBeginWr...
bool IsReadingOrWriting
If the object is locked for reading or writing.
virtual async Task< int > BeginRead()
Waits until object ready for reading. Each call to BeginRead must be followed by exactly one call to ...
MultiReadSingleWriteObject(object Owner)
Represents an object that allows single concurrent writers but multiple concurrent readers....
An interface for objects that allow single concurrent writers but multiple concurrent readers.