Threadsafety and Interlocked.CompareExchange
Hey, i have written a method that updates a dictionary without locks. Of course i have written a test for it and it succeeded for 15 runs. But today i executed all tests and suddendly the test failed, which i cannot reproduce a second time. Can you guys spot what i might have done wrong?
Test failure:
32 Replies
Why not just use a ConcurrentDictionary?
that spin update looks correct to me,
the thing is, u have pre-populated the dictionary with data, can u show how u do that?
it might also be a problem because its all
static
, thus other tests might affect the results (one of the reasons why mutable static states arent really recommended)Sure. Updated post
Really just one test is testing this particular class
Because Dictionary is faster and the add is a not-so-happy path which really should'nt happen in the first place
how is the
Value
property set?the constructor sets it new UpdateQueueStatus(value)
Updated post
hmmm, i dont see where its going wrong, but its basically all we need to see 🤔
im not sure how its with dotnet, but it might be that u need to make
_mappings
volatile
(btw, the naming convention suggest for static private members s_
as prefix)oh thanks for the volatile, that might be it...
s_ is ugly xDDD
that spin loop to update is in the end correct and the rest of the code doesnt seem to be at fault either
yea the spin loop was the hardest thing to understand 😄
Ran the tests 10 times now and no error....
btw if
newMap.TryAdd(value, result);
returns false
u can already stop the spinning cant ya?good point!
thank you!
and, if u have a lot of simultaneous concurrent writes all the time, this will be a nightmare for the gc
xD
but i wont have those
actually not the gc, but the whole copying the data over
cause parsing when the _mappings dont have an entry is the super unhappy path
like every state should already be known
usually if u have lock-free stuff like that ur data structures are node based so that u simply have to replace 1-2 nodes, u are doing full copies
aah, so its just at start that there is contentation, yeah then this makes sense
question is if
volatile
will really fix this or not57 tests passed
the only really accurate result u can get is that the test fails, else its just guessing
The static initializer initializes all known states. There are currently no unknowns. Unknowns (aka Parse adds to the map) will only happen when the caller is "newer", so i dont have to touch every program when implementing a new state
is ur
UpdateQueueStatus.GetMappings()
a simple return _mappings;
btw?yes
yeah then
volatile
should fix this
EditorBrowseable is so useless when the caller is in the same project
thats why Obsolete: Scream at them not to use it 😄
i asked in #allow-unsafe-blocks , lets see what they say, they have deeper insight than me
can u write another unit test without having the
volatile
on it yet?
basically
Succeeds
I will extract the map parts out into a nonstatic class and test that one
Ploxi
I have tested it extensively without volatile (50 times) and it didnt fail.
Ran the whole test project for unrelated reasons and this test failed.
Ran it again and it didnt fail.
Have added
volatile
and ran the test 400 times and it never failed in this runQuoted by
<@233139962277658624> from #allow-unsafe-blocks (click here)
React with ❌ to remove this embed.
u could run the tests all day, turn ur computer into an oven and it doesnt fail, but still could occasionally
i need to read up how volatile works in dotnet/if its the same/similar to java
Volatile.Read Method (System.Threading)
Reads the value of a field. On systems that require it, inserts a memory barrier that prevents the processor from reordering memory operations as follows: If a read or write appears after this method in the code, the processor cannot move it before this method.
...to make it completely safe even for multi processor systems
Do i only need volatile.read in the threadsafe add?
Like so?
nah, in the getter
instead of
var hasAdded = !newMap.TryAdd(key, value);
u can simply write if (!newMap.TryAdd(key, value)) return;
by getter i mean GetMappings()