Mysterious behavior of Dictionary

2019-04-23 17:22发布

I'm working on a huge system based on Asp.net MVC 3.0 and working on Mono-2.10.8 (Windows 7).

Everything was fine until a moment couple of days ago.

Inside my API I have several utility classes using dictionaries. For example, like this one:

public static class KeyUtility  
{
  static KeyUtility() {
    Alphabet = new[] {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 
      'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 
      'T', 'U', 'V', 'X', 'Y', 'Z', '0', '1', 
      '2', '3', '4', '5', '6', '7', '8', '9'
    };

    ReverseAlphabet = Alphabet
      .Select((c, i) => new { Char = c, Value = i })
      .ToDictionary(k => k.Char, v => (byte) v.Value);
  }

  internal static char[] Alphabet;      
  private static IDictionary<char, byte> ReverseAlphabet;

  public static string ToKey(byte[] key, int groupSize)
  {
    //Accessing Alphabet to generate string key from bytes
  }

  public static byte[] FromKey(string key)
  {
    //Accessing ReverseAlphabet to get bytes from string key
  }
}

And randomly I get exceptions like this:

System.IndexOutOfRangeException: Array index is out of range.
at System.Collections.Generic.Dictionary`2<char, byte>.TryGetValue (char,byte&) <0x000a1>
at MyAPI.KeyUtility.FromKey (string) <0x0009a>
at MyApp.Controllers.AboutController.Index () <0x002df>
at (wrapper dynamic-method) object.lambda_method (System.Runtime.CompilerServices.Closure,System.Web.Mvc.ControllerBase,object[]) <0x0002f>
at System.Web.Mvc.ActionMethodDispatcher.Execute (System.Web.Mvc.ControllerBase,object[]) <0x0001b>
at System.Web.Mvc.ReflectedActionDescriptor.Execute (System.Web.Mvc.ControllerContext,System.Collections.Generic.IDictionary`2<string, object>) <0x000ff>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod (System.Web.Mvc.ControllerContext,System.Web.Mvc.ActionDescriptor,System.Collections.Generic.IDictionary`2<string, object>) <0x00019>
at System.Web.Mvc.ControllerActionInvoker/<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12 () <0x00066>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter (System.Web.Mvc.IActionFilter,System.Web.Mvc.ActionExecutingContext,System.Func`1<System.Web.Mvc.ActionExecutedContext>) <0x000b8>

Most of the time everything is fine and KeyUtility works correct, but on rare occasions I get such an exception. Despite it looks like thread safety issue, the dictionary ReverseAlphabet is always accessed for reading only and never for writing. Once it's created in static constructor it's only accessed with TryGetValue. As I understand from MSDN article it should be thread safe in this case. Moreover, I haven't seen this problem before.

What should I look at? I even can't create a reproduction as I have completely no idea what is wrong.

Mono-2.10.8 and older versions proved to be stable with dictionaries. I've been using it for a couple of years intensively and have never seen this sort of exception before.

How to fix this?

UPD:

I've remembered that near the time of the begining of troubles what i've done is statically linked mono with my executable (i'm embedding mono into my application). I simply downloaded sources of mono. Compilled it without any changes except i set up libmono's output to static library. I've also linked with libeay32 and sqlite3. All multithread (MT). Maybe this change could affect an application? Unfortunately i can't check this under standalone mono. Before this i was linking all libraries dynamically and everything was fine.

UPD2: Here is the link to the complete sources: http://pastebin.com/RU4RNCki

1条回答
戒情不戒烟
2楼-- · 2019-04-23 17:46

what i've done is statically linked mono with my executable

You already know the problem I think, that certainly would break mono. The most important thing that doesn't happen anymore when you link it statically is the DllMain() callback that Windows makes whenever a new thread starts executing in the process. That's how the CLR becomes aware of threads that may execute managed code. Static constructors are indeed a likely failure mode, a thread must be blocked from executing any code in the class until the cctor() finished executing. The CLR cannot do this if it doesn't know about the thread.

If you want to make this work then you'll at least need to provide an alternative for the mono_thread_info_attach() call in your own DllMain() function. In general, I'd say this was one optimization too many.

查看更多
登录 后发表回答