Skip to content

Commit

Permalink
[BUG] Trigger exception on concurrent access to PollingPool. (#228)
Browse files Browse the repository at this point in the history
* Create draft PR for #216

* changed instance of PoolingPool to concurent dictionary

* concurent poolign test design concept

* PollingTasks dictionary chnage to concurrent

* add more concurency into polling test

---------

Co-authored-by: kuh0005 <kuh0005@users.noreply.github.com>
Co-authored-by: blazej.kuhajda <blazej.kuhajda@mts.sk>
Co-authored-by: Peter Kurhajec <61538034+PTKu@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 20, 2023
1 parent 91b8aa3 commit 69e71c8
Show file tree
Hide file tree
Showing 7 changed files with 450 additions and 29 deletions.
260 changes: 260 additions & 0 deletions src/AXSharp.connectors/src/AXSharp.Connector/Polling/ConcurrentSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
using System;
using System.Collections.Concurrent;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;


namespace AXSharp.Connector
{
/// <summary>
/// Concurent implementation HasSet comes from:
/// https://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework
/// https://stackoverflow.com/questions/4306936/how-to-implement-concurrenthashset-in-net
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConcurrentSet<T> : IEnumerable<T>, ISet<T>, ICollection<T>
{
private readonly ConcurrentDictionary<T, byte> _dictionary = new ConcurrentDictionary<T, byte>();

/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<T> GetEnumerator()
{
return _dictionary.Keys.GetEnumerator();
}

/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
public bool Remove(T item)
{
return TryRemove(item);
}

/// <summary>
/// Gets the number of elements in the set.
/// </summary>
public int Count
{
get { return _dictionary.Count; }
}

/// <summary>
/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
/// </returns>
public bool IsReadOnly { get { return false; } }

/// <summary>
/// Gets a value that indicates if the set is empty.
/// </summary>
public bool IsEmpty
{
get { return _dictionary.IsEmpty; }
}

public ICollection<T> Values
{
get { return _dictionary.Keys; }
}

/// <summary>
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
void ICollection<T>.Add(T item)
{
if (!Add(item))
throw new ArgumentException("Item already exists in set.");
}

/// <summary>
/// Modifies the current set so that it contains all elements that are present in both the current set and in the specified collection.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void UnionWith(IEnumerable<T> other)
{
foreach (var item in other)
TryAdd(item);
}

/// <summary>
/// Modifies the current set so that it contains only elements that are also in a specified collection.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void IntersectWith(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
foreach (var item in this)
{
if (!enumerable.Contains(item))
TryRemove(item);
}
}

/// <summary>
/// Removes all elements in the specified collection from the current set.
/// </summary>
/// <param name="other">The collection of items to remove from the set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void ExceptWith(IEnumerable<T> other)
{
foreach (var item in other)
TryRemove(item);
}

/// <summary>
/// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both.
/// </summary>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public void SymmetricExceptWith(IEnumerable<T> other)
{
throw new NotImplementedException();
}

/// <summary>
/// Determines whether a set is a subset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a subset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsSubsetOf(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return this.AsParallel().All(enumerable.Contains);
}

/// <summary>
/// Determines whether the current set is a superset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a superset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsSupersetOf(IEnumerable<T> other)
{
return other.AsParallel().All(Contains);
}

/// <summary>
/// Determines whether the current set is a correct superset of a specified collection.
/// </summary>
/// <returns>
/// true if the <see cref="T:System.Collections.Generic.ISet`1"/> object is a correct superset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set. </param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsProperSupersetOf(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return this.Count != enumerable.Count && IsSupersetOf(enumerable);
}

/// <summary>
/// Determines whether the current set is a property (strict) subset of a specified collection.
/// </summary>
/// <returns>
/// true if the current set is a correct subset of <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool IsProperSubsetOf(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return Count != enumerable.Count && IsSubsetOf(enumerable);
}

/// <summary>
/// Determines whether the current set overlaps with the specified collection.
/// </summary>
/// <returns>
/// true if the current set and <paramref name="other"/> share at least one common element; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool Overlaps(IEnumerable<T> other)
{
return other.AsParallel().Any(Contains);
}

/// <summary>
/// Determines whether the current set and the specified collection contain the same elements.
/// </summary>
/// <returns>
/// true if the current set is equal to <paramref name="other"/>; otherwise, false.
/// </returns>
/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
public bool SetEquals(IEnumerable<T> other)
{
var enumerable = other as IList<T> ?? other.ToArray();
return Count == enumerable.Count && enumerable.AsParallel().All(Contains);
}

/// <summary>
/// Adds an element to the current set and returns a value to indicate if the element was successfully added.
/// </summary>
/// <returns>
/// true if the element is added to the set; false if the element is already in the set.
/// </returns>
/// <param name="item">The element to add to the set.</param>
public bool Add(T item)
{
return TryAdd(item);
}

public void Clear()
{
_dictionary.Clear();
}

public bool Contains(T item)
{
return _dictionary.ContainsKey(item);
}

/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
public void CopyTo(T[] array, int arrayIndex)
{
Values.CopyTo(array, arrayIndex);
}

public T[] ToArray()
{
return _dictionary.Keys.ToArray();
}

public bool TryAdd(T item)
{
return _dictionary.TryAdd(item, default(byte));
}

public bool TryRemove(T item)
{
byte donotcare;
return _dictionary.TryRemove(item, out donotcare);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
Expand All @@ -13,7 +14,8 @@ namespace AXSharp.Connector
internal class Polling
{

private static HashSet<ITwinPrimitive> PollingPool { get; } = new HashSet<ITwinPrimitive>();
private static ConcurrentSet<ITwinPrimitive> PollingPool { get; } = new();


private Polling(ITwinElement twinObject,
object holdingObject,
Expand All @@ -22,7 +24,7 @@ private Polling(ITwinElement twinObject,

}

private static Dictionary<int, Task> PollingTasks { get; } = new();
private static ConcurrentDictionary<int, Task> PollingTasks { get; } = new();


internal static void Add(ITwinElement obj, int interval, object holder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ CONFIGURATION MyConfiguration
p_shadow_plain: all_primitives;
p_plain_shadow: all_primitives;

StartPolling_should_update_cyclic_property : RealMonsterData.RealMonster;
StartPolling_should_update_cyclic_property : RealMonsterData.RealMonster;

StartPolling_ConcurentOverload : RealMonsterData.RealMonster;

GH_ISSUE_183 : GH_ISSUE_183.GH_ISSUE_183_1;
END_VAR
Expand Down
51 changes: 51 additions & 0 deletions src/tests.integrations/integrated/src/ax/src/program.st
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,60 @@ PROGRAM MyProgram
VAR_EXTERNAL
StartPolling_should_update_cyclic_property : RealMonsterData.RealMonster;
GH_ISSUE_183 : GH_ISSUE_183.GH_ISSUE_183_1;

StartPolling_ConcurentOverload : RealMonsterData.RealMonster;
END_VAR
StartPolling_should_update_cyclic_property.Id := StartPolling_should_update_cyclic_property.Id + ULINT#1;

StartPolling_should_update_cyclic_property.DriveA.NestedLevelOne.NestedLevelTwo.NestedLevelThree.Position := StartPolling_should_update_cyclic_property.DriveA.NestedLevelOne.NestedLevelTwo.NestedLevelThree.Position + REAL#1.0;

StartPolling_ConcurentOverload.Id := StartPolling_ConcurentOverload.Id + ULINT#1;

StartPolling_ConcurentOverload.ArrayOfBytes[0] := StartPolling_ConcurentOverload.ArrayOfBytes[1];
StartPolling_ConcurentOverload.ArrayOfBytes[1] := StartPolling_ConcurentOverload.ArrayOfBytes[2];
StartPolling_ConcurentOverload.ArrayOfBytes[2] := StartPolling_ConcurentOverload.ArrayOfBytes[3];
StartPolling_ConcurentOverload.ArrayOfBytes[3] := TO_BYTE( TO_INT(StartPolling_ConcurentOverload.ArrayOfBytes[0]) + 1 );

StartPolling_ConcurentOverload.ArrayOfDrives[0].Position := StartPolling_ConcurentOverload.ArrayOfDrives[1].Position ;
StartPolling_ConcurentOverload.ArrayOfDrives[0].Velo := StartPolling_ConcurentOverload.ArrayOfDrives[1].Velo ;
StartPolling_ConcurentOverload.ArrayOfDrives[0].Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[1].Acc ;
StartPolling_ConcurentOverload.ArrayOfDrives[0].Acc := StartPolling_ConcurentOverload.ArrayOfDrives[1].Dcc ;

StartPolling_ConcurentOverload.ArrayOfDrives[1].Position := StartPolling_ConcurentOverload.ArrayOfDrives[2].Position ;
StartPolling_ConcurentOverload.ArrayOfDrives[1].Velo := StartPolling_ConcurentOverload.ArrayOfDrives[2].Velo ;
StartPolling_ConcurentOverload.ArrayOfDrives[1].Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[2].Acc ;
StartPolling_ConcurentOverload.ArrayOfDrives[1].Acc := StartPolling_ConcurentOverload.ArrayOfDrives[2].Dcc ;

StartPolling_ConcurentOverload.ArrayOfDrives[2].Position := StartPolling_ConcurentOverload.ArrayOfDrives[3].Position ;
StartPolling_ConcurentOverload.ArrayOfDrives[2].Velo := StartPolling_ConcurentOverload.ArrayOfDrives[3].Velo ;
StartPolling_ConcurentOverload.ArrayOfDrives[2].Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[3].Acc ;
StartPolling_ConcurentOverload.ArrayOfDrives[2].Acc := StartPolling_ConcurentOverload.ArrayOfDrives[3].Dcc ;

StartPolling_ConcurentOverload.ArrayOfDrives[3].Position := StartPolling_ConcurentOverload.ArrayOfDrives[0].Position + 0.02;
StartPolling_ConcurentOverload.ArrayOfDrives[3].Velo := StartPolling_ConcurentOverload.ArrayOfDrives[0].Velo + 0.03;
StartPolling_ConcurentOverload.ArrayOfDrives[3].Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[0].Acc + 0.04;
StartPolling_ConcurentOverload.ArrayOfDrives[3].Acc := StartPolling_ConcurentOverload.ArrayOfDrives[0].Dcc + 0.05;


StartPolling_ConcurentOverload.DriveA.Position := StartPolling_ConcurentOverload.ArrayOfDrives[0].Position ;
StartPolling_ConcurentOverload.DriveA.Velo := StartPolling_ConcurentOverload.ArrayOfDrives[0].Velo ;
StartPolling_ConcurentOverload.DriveA.Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[0].Acc ;
StartPolling_ConcurentOverload.DriveA.Acc := StartPolling_ConcurentOverload.ArrayOfDrives[0].Dcc ;

StartPolling_ConcurentOverload.DriveA.NestedLevelOne.Position := StartPolling_ConcurentOverload.ArrayOfDrives[1].Position ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.Velo := StartPolling_ConcurentOverload.ArrayOfDrives[1].Velo ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[1].Acc ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.Acc := StartPolling_ConcurentOverload.ArrayOfDrives[1].Dcc ;

StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.Position := StartPolling_ConcurentOverload.ArrayOfDrives[2].Position ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.Velo := StartPolling_ConcurentOverload.ArrayOfDrives[2].Velo ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[2].Acc ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.Acc := StartPolling_ConcurentOverload.ArrayOfDrives[2].Dcc ;

StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.NestedLevelThree.Position := StartPolling_ConcurentOverload.ArrayOfDrives[3].Position ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.NestedLevelThree.Velo := StartPolling_ConcurentOverload.ArrayOfDrives[3].Velo ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.NestedLevelThree.Dcc := StartPolling_ConcurentOverload.ArrayOfDrives[3].Acc ;
StartPolling_ConcurentOverload.DriveA.NestedLevelOne.NestedLevelTwo.NestedLevelThree.Acc := StartPolling_ConcurentOverload.ArrayOfDrives[3].Dcc ;


END_PROGRAM
Loading

0 comments on commit 69e71c8

Please sign in to comment.