Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change node filters to use a concurrent list instead of a dictionary #698

Merged
merged 5 commits into from
Jun 4, 2019

Conversation

mikedennis
Copy link
Contributor

This is a prerequisite for the work I'm doing in #692

ConcurrentDictionary does not maintain insertion order so it's usage in NodeFiltersCollection does not allow for ordering of Filters.

ThreadSafeList uses a list instead of ConcurrentDictionary and the locking of the collection is done manually. Currently I've only replaced the usage in NodeFiltersCollection but I have run all the tests using ThreadSafeList isntead of ThreadSafeCollection in all occurrences and all the same tests pass. So we may be able to replace other instances as well.

ThreadSafeList will allow insertion of duplicates whereas ThreadSafeCollection will not so that is a key difference to be aware of beyond the ordering difference.


public IEnumerator<T> GetEnumerator()
{
return _Behaviors.ToList().GetEnumerator();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be thread safe you need to lock Behavior, make a copy of the list, and returning the enumerator of this copy.

{
list = _Behaviors.ToList();
}
return list?.GetEnumerator();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then, it is a performance issue because we are duplicating a list everytimes a message come. :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we were also doing that before (this is from ThreadSafeCollection):

		public IEnumerator<T> GetEnumerator()
		{
			return _Behaviors.Select(k => k.Key).GetEnumerator();
		}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are doing all this copying anyways... wondering whether we might be able to use some of the new Immutable collections like ImmutableList, been dying to find a reason to try them out 😄

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_Behaviors.Select(k => k.Key).GetEnumerator() is not a copy.

@NicolasDorier
Copy link
Collaborator

NicolasDorier commented May 29, 2019

An idea, is that you make an array that is a copy of _Behaviors. Everytimes you modify _Behaviors, you set the array to null, else you use it in GetEnumerator.

@mikedennis
Copy link
Contributor Author

Thanks, yes that should work. Also researching this a bit it seems that some people have implemented functionality like this using a ConcurrentDictionary with an integer (item index) as the key which may be a reasonable approach as well.

@mikedennis
Copy link
Contributor Author

Updated to reduce list copying for enumerator as suggested. Reran the tests and they still pass.

}
return list?.GetEnumerator();
return _EnumeratorList?.GetEnumerator();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please capture enumeratorList in a lock variable so we are sure GetEnumerator never returns null.

}
return _EnumeratorList?.GetEnumerator();

return enumerator;
Copy link
Collaborator

@NicolasDorier NicolasDorier Jun 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IEnumerator<T> enumerator = _EnumeratorList?.GetEnumerator();
if (enumerator == null)
{
   lock (_lock)
   {
       var behaviorsList = _Behaviors.ToList();
       _EnumeratorList = behaviorsList;
       enumerator =  behaviorsList.GetEnumerator();
   }
}
return enumerator;

So you lock only if really needed. And it is provable that it is impossible to get a null enumerator out of this function.

@NicolasDorier NicolasDorier merged commit a8854dc into MetacoSA:master Jun 4, 2019
@NicolasDorier
Copy link
Collaborator

I am tempted to revert this. The build is very unstable since I merged this. CanMaintainChainWithSlimChainBehavior is failing here and there, I have no idea why.

@NicolasDorier
Copy link
Collaborator

Tried to refactor a bit, no luck.

@mikedennis
Copy link
Contributor Author

Sure no worries - go ahead and revert and I'll do some more testing with it with that test.

@NicolasDorier
Copy link
Collaborator

I change back to ThreadSafeCollection for now.

@NicolasDorier
Copy link
Collaborator

NicolasDorier commented Jun 8, 2019

I don't understand the issue. Tried replicate on my machine but that did not worked.

@zeptin
Copy link
Contributor

zeptin commented Jun 20, 2019

@mikedennis is SynchronizedCollection<T> unsuitable for what you were trying to do here?

@mikedennis
Copy link
Contributor Author

@zeptin I believe that's only .NET framework. Found the source code though, looks like it would be easy to port https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.ServiceModel/System/ServiceModel/SynchronizedCollection.cs

@zeptin
Copy link
Contributor

zeptin commented Nov 18, 2019

@NicolasDorier I am unable to get that test to fail locally while using the ThreadSafeList. Can we give that modification another try?

@NicolasDorier
Copy link
Collaborator

yes, make a pr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants