Sunday, February 27, 2011

Parallel Bug #1

This is the first of a collection of bugs from my code (or code in the project I'm involved in). Right now, I'll just collect them. Maybe, at some time in the future, I find time to lay out my ideas about how to deal with this sort of bugs ...

Here is the code for Parallel Bug #1:

    1 using System;
    2 using System.Collections.Generic;
    3 using System.Linq;
    4 using System.Reflection;
    5 using System.Threading.Tasks;
    6 using BF = System.Reflection.BindingFlags;
    7 
    8 #region A Framework For "Extended Enums"
    9 
   10 public abstract class BaseEnum<T, TEnum> 
   11         where T : BaseEnum<T, TEnum> {
   12     public TEnum Enum { get; private set; }
   13     public string Abbr { get; private set; }
   14     public string Name { get; private set; }
   15 
   16     private readonly static IDictionary<TEnum, T> _repository
   17                             = new SortedDictionary<TEnum, T>();
   18 
   19     protected BaseEnum(TEnum @enum, string abbr, string name) {
   20         Enum = @enum;
   21         Abbr = abbr;
   22         Name = name;
   23         _repository.Add(Enum, (T)this);
   24     }
   25 
   26     private static void EnsureInstantiation() {
   27         // Attempt at double checked locking ...
   28         if (_repository.Count == 0) {
   29             lock (_repository) {
   30                 if (_repository.Count == 0) {
   31                     ForceStaticConstructorCall();
   32                 }
   33             }
   34         }
   35     }
   36 
   37     private static void ForceStaticConstructorCall() {
   38         ConstructorInfo constructor = typeof(T)
   39           .GetConstructors(BF.Public | BF.NonPublic | BF.Instance)[0];
   40         object[] pars = constructor.GetParameters()
   41               .Select(p => p.ParameterType.IsValueType
   42                           ? Activator.CreateInstance(p.ParameterType)
   43                           : null)
   44               .ToArray();
   45         try {
   46             T dummy = (T) constructor.Invoke(pars);
   47             // If we get here, we DID add the dummy to the _repository -
   48             // probably because 0 is not in the enum.
   49             _repository.Remove(dummy.Enum);
   50         } catch {
   51             // ignored - we only trigger the static constructor here.
   52         }
   53     }
   54 
   55     public static IEnumerable<T> GetAll() {
   56         EnsureInstantiation();
   57         return _repository.Values;
   58     }
   59 
   60     public static T Get(TEnum e) {
   61         EnsureInstantiation();
   62         return _repository[e];
   63     }
   64 }
   65 
   66 #endregion A Framework For "Extended Enums"
   67 
   68 #region An Application
   69 
   70 public enum Units { km, m, cm, mm, g, kg }
   71 
   72 public class Unit : BaseEnum<Unit, Units> {
   73     public Unit BaseUnit { get; private set; }
   74     public decimal FactorToBaseUnit { get; private set; }
   75 
   76     private Unit(Units @enum, string abbrev, string longName, 
   77                  Unit baseUnit = null, decimal factor = 1.0m)
   78         : base(@enum, abbrev, longName) {
   79         BaseUnit = baseUnit ?? this;
   80         FactorToBaseUnit = factor;
   81     }
   82 
   83     static Unit() {
   84         var m = new Unit(Units.m, "m", "meter");
   85         new Unit(Units.cm, "cm", "centimeter", m, 100);
   86         new Unit(Units.mm, "mm", "millimeter", m, 1000);
   87         new Unit(Units.km, "km", "kilometer", m, 0.001m);
   88         var kg = new Unit(Units.kg, "kg", "kilogram");
   89         new Unit(Units.g, "g", "gram", kg, 1000);
   90     }
   91 }
   92 
   93 class Program {
   94     static void Main() {
   95         var t1 = new Task<Unit>(() => Unit.Get(Units.cm));
   96         var t2 = new Task<Unit>(() => Unit.Get(Units.mm));
   97         t1.Start();
   98         t2.Start();
   99         Console.WriteLine(t1.Result.FactorToBaseUnit / t2.Result.FactorToBaseUnit);
  100     }
  101 }
  102 
  103 #endregion An Application


If you run the program, you see as output 0.1. However, this output is not reliable: The program can throw an exception if one of the tasks runs EnsureInstantiation while the other is in the middle of the static constructor - then, only part of _repository will be filled, and Get might not find a key. The bug is, of course, that in the double checked locking code, Count == 0 is not a sufficient condition to check that the initialization is complete.

No comments:

Post a Comment