| At D2L we have a large C# code base which gets deployed in a few different subsets, but is largely a monolithic fleet of web servers. To prevent these kind of problems we have a few approaches, but the main way is to prevent shared mutable state. To do this we have a custom C# code analyzer (source here: https://github.com/Brightspace/D2L.CodeStyle/tree/master/src... , but it's not documented for external consumption at this point... ImmutableDefinitionChecker is the main place to look.) It goes like this: [Immutable]
public class Foo : Bar {
// object is a scary field type, but "new object()"
// is always an immutable value.
private readonly object m_lock = new object();
// we'll check that ISomething is [Immutable] here
private readonly ISomething m_something;
// Fine because this lambdas in initializers can't
// close over mutable state that we wouldn't
// otherwise catch.
private readonly Action m_abc = () => {};
// see the constructor
private readonly Func<int, int> m_xyz;
// danger: not readonly
private int m_zzz;
// danger: arrays are always mutable
private readonly int[] m_array1 new[]{ 1, 2, 3 };
// ok
private readonly ImmutableArray<int> m_array2
= ImmutableArray.Create( 1, 2, 3 );
public Foo( ISomething something ) {
m_something = something;
// ok: static lambdas can't close over mutable state
m_xyz = static _ => 3;
// danger: lambdas can in general close over mutable
// state.
int i = 0;
m_xyz = x => { i += x; return i; };
}
}
Here we see that a type has the [Immutable] attribute on this class, so we will check that all the members are readonly and also contain immutable values (via looking at all assignments, which is easy for readonly fields/properties). Additionally, we will check that instances of Bar (our base class) are known to be immutable.Any class that were to extend Foo (as a base class) will be required to be [Immutable] as well. There's a decent number of aspects to this analysis (e.g. a generic type can be "conditionally immutable" -- ImmutableArray<int> is, but ImmutableArray<object> is not), check the source if you're interested. We require all static (global) variables be immutable, and any "singleton" type (of which we have tens of thousands if I remember correctly) also must be. Another important thing we do to is to cut off any deferred boot-up code from accessing customer data (via a thread-local variable that is checked before access through the data layer). This prevents accidentally caching something for a particular customer inside, say, a persistent Lazy<T> (or the constructor for a singleton, etc.) We've adapted our code base to be very strict about this over the last few years and it discovered many obscure thread-safety bugs and doubtlessly prevented many from happening since. |