Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singleton pattern on persistent in-memory cache

Using what I judged was the best of all worlds on the Implementing the Singleton Pattern in C# amazing article, I have been using with success the following class to persist user-defined data in memory (for the very rarely modified data):

public class Params
{
  static readonly Params Instance = new Params();

  Params()
  {
  }

  public static Params InMemory
  {
    get
    {
      return Instance;
    }
  }

  private IEnumerable<Localization> _localizations;

  public IEnumerable<Localization> Localizations
  {
    get
    {
      return _localizations ?? (_localizations = new Repository<Localization>().Get());
    }
  }

  public int ChunkSize
  {
    get
    {
      // Loc uses the Localizations impl
      LC.Loc("params.chunksize").To<int>();
    }
  }

  public void RebuildLocalizations()
  {
    _localizations = null;
  }

  // other similar values coming from the DB and staying in-memory,
  // and their refresh methods

}

My usage would look something like this:

var allLocs = Params.InMemory.Localizations; //etc

Whenever I update the database, the RefreshLocalizations gets called, so only part of my in-memory store is rebuilt. I have a single production environment out of about 10 that seems to be misbehaving when the RefreshLocalizations gets called, not refreshing at all, but this is also seems to be intermittent and very odd altogether.

My current suspicions goes towards the singleton, which I think does the job great and all the unit tests prove that the singleton mechanism, the refresh mechanism and the RAM performance all work as expected.

That said, I am down to these possibilities:

  1. This customer is lying when he says their environment is not using loading balance, which is a setting I am not expecting the in-memory stuff to work properly (right?)
  2. There is some non-standard pool configuration in their IIS which I am testing against (maybe in a Web Garden setting?)
  3. The singleton is failing somehow, but not sure how.

Any suggestions?

.NET 3.5 so not much parallel juice available, and not ready to use the Reactive Extensions for now

Edit1: as per the suggestions, would the getter look something like:

public IEnumerable<Localization> Localizations
{
  get
  {
    lock(_localizations) {
      return _localizations ?? (_localizations = new Repository<Localization>().Get());
    }
  }
}
like image 297
Francisco Aquino Avatar asked Oct 09 '22 00:10

Francisco Aquino


2 Answers

To expand on my comment, here is how you might make the Localizations property thread safe:

public class Params
{
  private object _lock = new object();

  private IEnumerable<Localization> _localizations;    
  public IEnumerable<Localization> Localizations
  {
    get
    {
      lock (_lock) {
         if ( _localizations == null ) {
            _localizations = new Repository<Localization>().Get();
         }

         return _localizations;
      }
    }
  }

  public void RebuildLocalizations()
  {
     lock(_lock) {
        _localizations = null;
     }
  }

  // other similar values coming from the DB and staying in-memory,
  // and their refresh methods

}
like image 139
Eric Petroelje Avatar answered Oct 13 '22 10:10

Eric Petroelje


There is no point in creating a thread safe singleton, if your properties are not going to be thread safe.

You should either lock around assignment of the _localization field, or instantiate in your singleton's constructor (preferred). Any suggestion which applies to singleton instantiation applies to this lazy-instantiated property.

The same thing further applies to all properties (and their properties) of Localization. If this is a Singleton, it means that any thread can access it any time, and simply locking the getter will again do nothing.

For example, consider this case:

    Thread 1                              Thread 2

    // both threads access the singleton, but you are "safe" because you locked
1.  var loc1 = Params.Localizations;      var loc2 = Params.Localizations;

    // do stuff                           // thread 2 calls the same property...
2.  var value = loc1.ChunkSize;           var chunk = LC.Loc("params.chunksize");

    // invalidate                         // ...there is a slight pause here...
3.  loc1.RebuildLocalizations();

                                          // ...and gets the wrong value
4.                                        var value = chunk.To();

If you are only reading these values, then it might not matter, but you can see how you can easily get in trouble with this approach.

Remember that with threading, you never know if a different thread will execute something between two instructions. Only simple 32-bit assignments are atomic, nothing else.

This means that, in this line here:

return LC.Loc("params.chunksize").To<int>();

is, as far as threading is concerned, equivalent to:

var loc = LC.Loc("params.chunksize");
Thread.Sleep(1); // anything can happen here :-(
return loc.To<int>();

Any thread can jump in between Loc and To.

like image 23
Groo Avatar answered Oct 13 '22 11:10

Groo