@@ -407,105 +407,94 @@ internal void NotifyActivityAddException(Activity activity, Exception exception,
407407 }
408408 }
409409
410- // SynchronizedList<T> is a helper collection which ensure thread safety on the collection
411- // and allow enumerating the collection items and execute some action on the enumerated item and can detect any change in the collection
412- // during the enumeration which force restarting the enumeration again.
413- // Caution: We can have the action executed on the same item more than once which is ok in our scenarios.
410+ // This class uses a copy-on-write design to ensure thread safety all operations are thread safe.
411+ // However, it is possible for read-only operations to see stale versions of the item while a change
412+ // is occurring.
414413 internal sealed class SynchronizedList < T >
415414 {
416- private readonly List < T > _list ;
417- private uint _version ;
418-
419- public SynchronizedList ( ) => _list = new List < T > ( ) ;
415+ private readonly object _writeLock ;
416+ // This array must not be mutated directly. To mutate, obtain the lock, copy the array and then replace it with the new array.
417+ private T [ ] _volatileArray ;
418+ public SynchronizedList ( )
419+ {
420+ _volatileArray = [ ] ;
421+ _writeLock = new ( ) ;
422+ }
420423
421424 public void Add ( T item )
422425 {
423- lock ( _list )
426+ lock ( _writeLock )
424427 {
425- _list . Add ( item ) ;
426- _version ++ ;
428+ T [ ] newArray = new T [ _volatileArray . Length + 1 ] ;
429+
430+ Array . Copy ( _volatileArray , newArray , _volatileArray . Length ) ; // copy existing items
431+ newArray [ _volatileArray . Length ] = item ; // copy new item
432+
433+ _volatileArray = newArray ;
427434 }
428435 }
429436
430437 public bool AddIfNotExist ( T item )
431438 {
432- lock ( _list )
439+ lock ( _writeLock )
433440 {
434- if ( ! _list . Contains ( item ) )
441+ int index = Array . IndexOf ( _volatileArray , item ) ;
442+
443+ if ( index >= 0 )
435444 {
436- _list . Add ( item ) ;
437- _version ++ ;
438- return true ;
445+ return false ;
439446 }
440- return false ;
447+
448+ T [ ] newArray = new T [ _volatileArray . Length + 1 ] ;
449+
450+ Array . Copy ( _volatileArray , newArray , _volatileArray . Length ) ; // copy existing items
451+ newArray [ _volatileArray . Length ] = item ; // copy new item
452+
453+ _volatileArray = newArray ;
454+
455+ return true ;
441456 }
442457 }
443458
444459 public bool Remove ( T item )
445460 {
446- lock ( _list )
461+ lock ( _writeLock )
447462 {
448- if ( _list . Remove ( item ) )
463+ int index = Array . IndexOf ( _volatileArray , item ) ;
464+
465+ if ( index < 0 )
449466 {
450- _version ++ ;
451- return true ;
467+ return false ;
452468 }
453- return false ;
469+
470+ T [ ] newArray = new T [ _volatileArray . Length - 1 ] ;
471+
472+ Array . Copy ( _volatileArray , newArray , index ) ; // copy existing items before index
473+
474+ Array . Copy (
475+ _volatileArray , index + 1 , // position after the index, skipping it
476+ newArray , index , _volatileArray . Length - index - 1 // remaining items accounting for removed item
477+ ) ;
478+
479+ _volatileArray = newArray ;
480+ return true ;
454481 }
455482 }
456483
457- public int Count => _list . Count ;
484+ public int Count => _volatileArray . Length ;
458485
459486 public void EnumWithFunc < TParent > ( ActivitySource . Function < T , TParent > func , ref ActivityCreationOptions < TParent > data , ref ActivitySamplingResult samplingResult , ref ActivityCreationOptions < ActivityContext > dataWithContext )
460487 {
461- uint version = _version ;
462- int index = 0 ;
463-
464- while ( index < _list . Count )
488+ foreach ( T item in _volatileArray )
465489 {
466- T item ;
467- lock ( _list )
468- {
469- if ( version != _version )
470- {
471- version = _version ;
472- index = 0 ;
473- continue ;
474- }
475-
476- item = _list [ index ] ;
477- index ++ ;
478- }
479-
480- // Important to call the func outside the lock.
481- // This is the whole point we are having this wrapper class.
482490 func ( item , ref data , ref samplingResult , ref dataWithContext ) ;
483491 }
484492 }
485493
486494 public void EnumWithAction ( Action < T , object > action , object arg )
487495 {
488- uint version = _version ;
489- int index = 0 ;
490-
491- while ( index < _list . Count )
496+ foreach ( T item in _volatileArray )
492497 {
493- T item ;
494- lock ( _list )
495- {
496- if ( version != _version )
497- {
498- version = _version ;
499- index = 0 ;
500- continue ;
501- }
502-
503- item = _list [ index ] ;
504- index ++ ;
505- }
506-
507- // Important to call the action outside the lock.
508- // This is the whole point we are having this wrapper class.
509498 action ( item , arg ) ;
510499 }
511500 }
@@ -517,27 +506,8 @@ public void EnumWithExceptionNotification(Activity activity, Exception exception
517506 return ;
518507 }
519508
520- uint version = _version ;
521- int index = 0 ;
522-
523- while ( index < _list . Count )
509+ foreach ( T item in _volatileArray )
524510 {
525- T item ;
526- lock ( _list )
527- {
528- if ( version != _version )
529- {
530- version = _version ;
531- index = 0 ;
532- continue ;
533- }
534-
535- item = _list [ index ] ;
536- index ++ ;
537- }
538-
539- // Important to notify outside the lock.
540- // This is the whole point we are having this wrapper class.
541511 ( item as ActivityListener ) ! . ExceptionRecorder ? . Invoke ( activity , exception , ref tags ) ;
542512 }
543513 }
0 commit comments