Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Add concurrent access detection tests to Dictionary<TKey, TValue>#30515

Merged
danmoseley merged 14 commits into
dotnet:masterfrom
MarcoRossignoli:concurrentaccessdetectiontests
Jun 21, 2018
Merged

Add concurrent access detection tests to Dictionary<TKey, TValue>#30515
danmoseley merged 14 commits into
dotnet:masterfrom
MarcoRossignoli:concurrentaccessdetectiontests

Conversation

@MarcoRossignoli
Copy link
Copy Markdown
Member

Assert.False(dic.GetType().GetGenericArguments()[0].IsValueType);
Assert.Equal("ThrowInvalidOperationException_ConcurrentOperationsNotSupported", Assert.Throws<InvalidOperationException>(() => dic.Add(new DummyRefType() { Value = 1 }, new DummyRefType() { Value = 1 })).TargetSite.Name);
Assert.Equal("ThrowInvalidOperationException_ConcurrentOperationsNotSupported", Assert.Throws<InvalidOperationException>(() => dic[new DummyRefType() { Value = 1 }]).TargetSite.Name);
//Remove is not resilient yet
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Remove are not yet resilient to infinite loops...we can merge this(if are good) and after enable tests or wait for coreclr perft tests/merge.

dic.Add(1, 1);

//break internal state
var entriesType = dic.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you please extract common code (eg the reflection code)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok

}
}

public class DummyRefType
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do you need special types and comparers, it seems strings and ints should suffice?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

customThread.Start();

//Wait max 5 seconds, could loop forever
Assert.True(customThread.Join(TimeSpan.FromSeconds(5)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would make these timeouts large (eg 30 or 60 sec). Because if the machine is overloaded, they may fail in the passing case - which is bad. I have seen test machines occasionally be very slow. It is OK if they are large, because unless we regress, they will never hit the timeout. All we care about is the entire test run isn't brought down in that case. Each test library gets 20 minutes total.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok

[Fact]
public static void Add_DictionaryConcurrentAccessDetection_NullComparer_ValueTypeKey()
{
Thread customThread = new Thread(() =>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm probably spacing here -- but where is the concurrent access? There is only one thread accessing it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i'm simulating "concurrent access" breaking internal structure as if concurrent access happened. How i can be "sure" that threads will break internals?Depends on some random factors...i cannot guarantee break in n seconds(at least in my career trying to reproduce concurrent issue is not so easy...samples work well on my machine and doesn't work on other, PerformanceCounter for instance...the behaviour are different on different machine with different number of processor, different parameters to fail test), i use thread only because if for some reason in future access detection will be removed threads doesn't loop forever blocking CI. I hope I've been clear.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Doh - of course - that was the plan and it's what you did. BTW, I wonder if you could await a Task instead of using a Thread - @ViktorHofer the Task/Thread is only to add a timeout - if we wait on a Task perhaps it won't be marshaled to another thread, so there effectively won't be a timeout?

Copy link
Copy Markdown
Member Author

@MarcoRossignoli MarcoRossignoli Jun 19, 2018

Choose a reason for hiding this comment

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

Task perhaps it won't be marshaled to another thread, so there effectively won't be a timeout?

I could do that with Task.Run+Task.Delay+Task.WaitAll maybe...to simulate timeout...but maybe code will be less clear.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You could use a Task with the LongRunning option which forces the TaskScheduler to spin up a new thread.

Copy link
Copy Markdown
Member Author

@MarcoRossignoli MarcoRossignoli Jun 19, 2018

Choose a reason for hiding this comment

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

yes i forget Wait(TimeSpan) overload.

Copy link
Copy Markdown
Member Author

@MarcoRossignoli MarcoRossignoli Jun 19, 2018

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please prefer using Task over Thread if possible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok i'll do!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At that point, would @MarcoRossignoli await the task, and make the test async? I suppose that doesn't give us much - the tests could overlap, but they should be very quick anyway.

public static void Add_DictionaryConcurrentAccessDetection_Comparer_ValueTypeKey()
{
Thread customThread = new Thread(() =>
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: indentation

customThread.Start();

//Wait max 5 seconds, could loop forever
Assert.True(customThread.Join(TimeSpan.FromSeconds(5)));
Copy link
Copy Markdown
Member Author

@MarcoRossignoli MarcoRossignoli Jun 19, 2018

Choose a reason for hiding this comment

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

@danmosemsft In case of timeout and assertion fail thread will run "forever" however...but as background thread at the end of test will be killed. I avoided to call Kill/Abort by myself because there are no guarantee at least on win. We cannot pass some CancellationToken to internals.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A timeout causing an assertion failure sounds good to me.

@MarcoRossignoli MarcoRossignoli changed the title [WIP]Add concurrent access detection tests to Dictionary<TKey, TValue> Add concurrent access detection tests to Dictionary<TKey, TValue> Jun 20, 2018
@MarcoRossignoli
Copy link
Copy Markdown
Member Author

@danmosemsft @ViktorHofer i've simplified test and added Task instead of Thread.

</Compile>
<!-- Generic tests -->
<Compile Include="Generic\Dictionary\Dictionary.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
<Compile Include="Generic\Dictionary\Dictionary.Generic.Tests.ConcurrentAccessDetection.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be great if you could put all netcoreapp condition compile directives into their own ItemGroup.

...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

[Theory]
[InlineData(null)]
[InlineData(typeof(CustomEqualityComparerInt32ValueType))]
async public static Task Add_DictionaryConcurrentAccessDetection_ValueTypeKey(Type comparerType)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

public async Task Add_DictionaryConcurrentAccessDetection_ValueTypeKey. Async should be after the access modifier and there's no need for the method to be static.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok i used static because i'm working also on Span tests refactor...and in that project are all static..is there a guideline?

https://github.com/dotnet/corefx/blob/master/src/System.Memory/tests/ReadOnlySpan/SequenceEqual.char.cs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I and most of us prefer non-static test methods.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@karelz should we have a guideline for static vs non-static test methods?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I and most of us prefer non-static test methods.

Also me and my team.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think Xunit runs the ctor for each test. So non static gives you a fresh instance. Otherwise I'm not sure we care, save consistency. @stephentoub?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So static class for tests are more performant.

}
}

public class DummyRefType
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are those types not inside a namespace? Also please use the most restrictive access modifier possible, in you case either private or internal depending on where you place the types. This also applies to the custom equalitycomparers.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done


public class DummyRefType
{
public int Value { get; set; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: separate methods and properties by a an empty line.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

[Theory]
[InlineData(null)]
[InlineData(typeof(CustomEqualityComparerDummyRefType))]
async public static Task Add_DictionaryConcurrentAccessDetection_ReferenceTypeKey(Type comparerType)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same here.

{
IEqualityComparer<int> customComparer = null;

Dictionary<int, int> dic =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: we usually put the comparison into the same line and only the then and else clause into a new line.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

Task task = Task.Factory.StartNew(() =>
{
//break internal state
var entriesType = dictionary.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
Copy link
Copy Markdown
Member

@ViktorHofer ViktorHofer Jun 20, 2018

Choose a reason for hiding this comment

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

nit: concrete type instead of var where it's not obvious on the right hand side of the declaration.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

is this not obvious? 😄 you'are right, reflection is worste code for var!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

{
//break internal state
FieldInfo entriesType = dictionary.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
object entriesInstance = (Array)entriesType.GetValue(dictionary);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit, this can be declared Array

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

{
Task task = Task.Factory.StartNew(() =>
{
//break internal state
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need to fix unless you reset the PR again...we use single space after //
Also "break internal state" could be more explicit eg " set an empty entries array to simulate a dictionary corrupted by concurrent access".
Am I reading that right? I was assuming you would set an entries array with a loop, how does an empty one work?

Copy link
Copy Markdown
Member Author

@MarcoRossignoli MarcoRossignoli Jun 20, 2018

Choose a reason for hiding this comment

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

Am I reading that right? I was assuming you would set an entries array with a loop, how does an empty one work?

is not empty

 Array entryArray = (Array)Activator.CreateInstance(entriesInstance.GetType(), new object[] { ((IDictionary)dictionary).Count });

the idea is break alignement between states of _buckets and _entries "old" _buckets state avoid exit from loops and new _entries.next generate loop.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, I see (now I'm not reading on the phone). The correct Bucket points to an Entry with .next of 0, so it loops.

}
}

class DummyRefType
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider a comment explaining why you use custom types in your tests

Copy link
Copy Markdown
Member Author

@MarcoRossignoli MarcoRossignoli Jun 20, 2018

Choose a reason for hiding this comment

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

Done...sorry for my very basic english...help me with mistakes please 😅

Copy link
Copy Markdown
Member

@danmoseley danmoseley left a comment

Choose a reason for hiding this comment

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

I tweaked the comment. Thanks @MarcoRossignoli

@MarcoRossignoli
Copy link
Copy Markdown
Member Author

I tweaked the comment

Thank's and sorry for wasted time.

@danmoseley
Copy link
Copy Markdown
Member

@MarcoRossignoli you didn't waste any of my time, you saved me time by doing this work.

@danmoseley
Copy link
Copy Markdown
Member

blocked on dotnet/coreclr#18524

@MarcoRossignoli
Copy link
Copy Markdown
Member Author

blocked on dotnet/coreclr#18524

I've enabled remove tests...i think is ready to go after green CI.

@danmoseley danmoseley merged commit 24b9efc into dotnet:master Jun 21, 2018
@danmoseley
Copy link
Copy Markdown
Member

Thanks @MarcoRossignoli!

@MarcoRossignoli
Copy link
Copy Markdown
Member Author

Thanks @MarcoRossignoli!

mmm...1.28 PM in Italy...deep night in Redmond...i'm suspecting you're a bot @danmosemsft-bot

@MarcoRossignoli MarcoRossignoli deleted the concurrentaccessdetectiontests branch June 21, 2018 12:09
@karelz karelz added this to the 3.0 milestone Jul 8, 2018
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…tnet/corefx#30515)

* add concurrent access detection tests

* add remove overloads

* simplify tests

* address PR feedback

* remove var

* nit extraline

* move comparison into same line

* cleanup unuseful instance

* cleanup typo

* improve readability

* address PR feedback

* Comment

* Access modifier

* enable removes tests


Commit migrated from dotnet/corefx@24b9efc
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants