DeviceDetector.NET icon indicating copy to clipboard operation
DeviceDetector.NET copied to clipboard

Concurrency Issue

Open arjun-khadka-beyondid opened this issue 5 years ago • 8 comments

Code USed: Startup.cs

       DeviceDetector.SetVersionTruncation(VersionTruncation.VERSION_TRUNCATION_NONE); 

       var userAgent = context.Request.Headers["User-Agent"]; 

        var deviceDetector = new DeviceDetector(userAgent); 

        deviceDetector.Parse(); 

Issue encounter: System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.

at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)

at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)

at DeviceDetectorNET.Cache.DictionaryCache.Contains(String id)

at DeviceDetectorNET.Cache.DictionaryCache.Fetch(String id)

at DeviceDetectorNET.Parser.ParserAbstract`2.GetRegexes()

at DeviceDetectorNET.Parser.Client.LibraryParser..ctor()

at DeviceDetectorNET.Parser.Client.ClientType.get_Library()

at DeviceDetectorNET.DeviceDetector.AddClientsParser()

at DeviceDetectorNET.DeviceDetector..ctor(String userAgent)

arjun-khadka-beyondid avatar Aug 10 '20 10:08 arjun-khadka-beyondid

I'm getting the same issue, somewhat random. Aside from restarting the process, how can I avoid getting this error? Once its in a corrupt state, it seems only a restart can solve it.

cpkuo avatar Jun 09 '22 00:06 cpkuo

Same thing, this bug suddenly appeared today.

schneidenbach avatar Jun 10 '22 14:06 schneidenbach

In the meantime I've just implemented a cache check with concurrent dictionary before making a call to DeviceDetectorNET

cpkuo avatar Jun 16 '22 17:06 cpkuo

@cpkuo care to share the code? :)

schneidenbach avatar Jun 21 '22 00:06 schneidenbach

Sure, there is still the possibility that DeviceDetector will write to it's internal cache collection on concurrent requests but this seems to have helped me in a high load environment. I wonder if setting a new instance of DictonaryCache on each request in combination with the custom ConcurrentDictionary cache will completely eliminate the error. I will need to review the source code at some point, in the meantime here is some code.

`private static ConcurrentDictionary<string, DeviceInfo> cache = new ConcurrentDictionary<string, DeviceInfo>(); private static DictionaryCache dicationaryCache = new DictionaryCache();

public DeviceInfo Get(string userAgent)
{
    if (cache.ContainsKey(userAgent))
    {
        return cache[userAgent];
    }

    //Domain class
    var info = new DeviceInfo();

    DeviceDetector.SetVersionTruncation(VersionTruncation.VERSION_TRUNCATION_NONE);
    var dd = new DeviceDetector(userAgent);

    dd.SetCache(dicationaryCache);
    dd.Parse();

    var client = dd.GetClient();

    //Transfer result into domain class DeviceInfo
    //  .......

    cache.TryAdd(userAgent, info);

    return info;
}`

cpkuo avatar Jun 21 '22 01:06 cpkuo

So I went into the source code to match up with the stack trace for this exception and realized I was using a dotnet core version package of DeviceDetector.NET that I don't believe exists anymore. Nuget package manager was not notifying me of updating. So I removed it and started using the latest package of DeviceDetector.NET which now supports the latest .net standard. So far, I have not run into this issue.

cpkuo avatar Jul 12 '22 23:07 cpkuo

This can still happen:

System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
  at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
  at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
  at DeviceDetectorNET.Cache.DictionaryCache.Contains(String id)
  at DeviceDetectorNET.Cache.DictionaryCache.Fetch(String id)
  at DeviceDetectorNET.Parser.ParserAbstract`2.GetRegexes()
  at DeviceDetectorNET.Parser.Client.MediaPlayerParser..ctor()
  at DeviceDetectorNET.Parser.Client.ClientType.get_MediaPlayer()
  at DeviceDetectorNET.DeviceDetector.AddClientsParser()
  at DeviceDetectorNET.DeviceDetector..ctor(String userAgent)
  at

I'm now following the advice to cache userAgents and I set SetVersionTruncation once statically

adamhathcock avatar Nov 08 '23 15:11 adamhathcock

I did my final fix has having a custom ICache with ConcurrentDictionary instead of just Dictionary

adamhathcock avatar Nov 09 '23 08:11 adamhathcock