Skip to content

Fixing #334 by making AttributesToAvoidReplicating thread safe#335

Merged
jonorossi merged 3 commits intocastleproject:masterfrom
BrunoJuchli:master
Jan 25, 2018
Merged

Fixing #334 by making AttributesToAvoidReplicating thread safe#335
jonorossi merged 3 commits intocastleproject:masterfrom
BrunoJuchli:master

Conversation

@BrunoJuchli
Copy link
Copy Markdown
Contributor

@BrunoJuchli BrunoJuchli commented Jan 24, 2018

This PR fixes #334 by making AttributesToAvoidReplicating thread safe by replacing the backing collection instead of updating it (...while consumers are enumerating it...).

It employs Interlocked.CompareExchange to replace the collection if it's still the same as the original. If replacing fails (because another thread has already exchanged the collection) it retries until it succeeds.

Alternative would have been to replace the backing collection but to lock the Add method so concurrent Add-ing would be sequentialised.

@stakx
Copy link
Copy Markdown
Member

stakx commented Jan 24, 2018

Two detail questions about Add, line 50:

while (!(originalAttributes = attributes).Contains(attribute))
  1. Does the runtime guarantee in this case that attributes will be re-read on each iteration, even without a memory barrier, and even when attributes isn't declared volatile (which it probably shouldn't be)?

    Please disregard, according to https://stackoverflow.com/a/1716587/240733 Interlocked.CompareExchange has a memory barrier already.

  2. Would a spin wait between two iterations be a good idea (to reduce superfluous allocations when another thread is rapidly updating attributes), or is it unnecessary in this case?

    Please disregard as well, like your issue showed it's probably very unlikely for AttributesToAvoidReplicating to be updated simultaneously by several concurrent threads, so preventing a few allocations would most likely be a premature optimization.

Copy link
Copy Markdown
Member

@stakx stakx left a comment

Choose a reason for hiding this comment

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

Just a few comments regarding your use of lock, in case you end up using this kind of lock. (Castle has its own lock type based on ReaderWriterLockSlim I believe.)


public static class AttributesToAvoidReplicating
{
private static readonly object lockObject = new object();
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.

Is this needed? I think you could just lock on attributes directly.

Copy link
Copy Markdown
Contributor Author

@BrunoJuchli BrunoJuchli Jan 25, 2018

Choose a reason for hiding this comment

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

As the list is not accessible externally of the class this is of course a viable alternative.

The only reason not to do so is to prevent future issues where collections could be made available externally and an external consumer could lock it. Let's say you'd introduce

public static IReadOnlyList<Type> Types => attributes;

I'm fine either way. I won't go around locking other "people's" objects, so I most likely won't be dealing with that issue, should it ever arise ;-)

IList<Type> originalAttributes;
while (!(originalAttributes = attributes).Contains(attribute))
// note: this class is made thread-safe by replacing the backing list rather than adding to it
lock (lockObject)
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 you also need to put locks around places where attributes is accessed for reading; that is, in Contains and in ShouldAvoid. (Not sure about the former, but almost certain in case of the latter.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IMO not necessary. Exchanging object references is atomic. So Contains and ShouldAvoid will either get the old reference or the new reference.
Bug #334 is fixed by just replacing the list instead of extending it. However, the lock is necessary to safeguard against concurrent calls to Add. Even though they are not very likely, of course.

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.

Oops, I mis-read the diff (or rather, I didn't read all of it). You're absolutely right, of course.

@jonorossi jonorossi merged commit 0bfef05 into castleproject:master Jan 25, 2018
@jonorossi
Copy link
Copy Markdown
Member

Thanks @BrunoJuchli, merged.

@jonorossi jonorossi added this to the vNext milestone Jan 25, 2018
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.

Static AttributesToAvoidReplicating is not Thread Safe

3 participants