Skip to content

Commit ad430a1

Browse files
Remove thread contention from Activity Start/Stop (dotnet#107333)
* Remove thread contention from Activity Start/Stop Author: algorithmsarecool <[email protected]> * Fix ref parameters and whitespace Author: algorithmsarecool<[email protected]> * PR feedback. - Reduce duplication - add comments and make code more obvious - Use IndexOf Author: algorithmsarecool <[email protected]> * PR feedback to simplify locking strategy * PR feedback, final nits
1 parent 5a6d100 commit ad430a1

File tree

1 file changed

+53
-83
lines changed
  • src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics

1 file changed

+53
-83
lines changed

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs

Lines changed: 53 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)