Caching Application Block - Sleep(0)

Topics: Caching Application Block
Jan 26, 2010 at 1:37 PM

Hi,

 

I've past last few days trobleshooting a performance problem in my app. I the app was wasting too much CPU and easily getting 100% CPU.

After some analysis with a .NET Profiler the method that was consuming a lot of CPU was Microsoft.Practices.EnterpriseLibrary.Caching.Cache.GetData(string key). 

Looking to the method implementation there is a code block that may be controversial:

            do {
                lock (inMemoryCache.SyncRoot) {
                    cacheItemBeforeLock = (CacheItem)inMemoryCache[key];
                    if (IsObjectInCache(cacheItemBeforeLock)) {
			instrumentationProvider.FireCacheAccessed(key, false);
                        return null;
                    }

                    lockWasSuccessful = Monitor.TryEnter(cacheItemBeforeLock);
                }

                if (lockWasSuccessful == false) {
                    Thread.Sleep(0);
                }
            } while (lockWasSuccessful == false);

 

 

The caching block is trying to acquire a lock on the CacheItem, but when its locked by someone else it tries do release the thread execution via Thread.Sleep(0), and that's the point.

There as a few article on the net about this theme: http://blogs.msdn.com/oldnewthing/archive/2005/10/04/476847.aspxhttp://stackoverflow.com/questions/1413630/switchtothread-thread-yield-vs-thread-sleep0-vs-thead-sleep1.

 

I've did some tests with Sleep API and Sleep(0) turns out to be evil to me.

In .NET:

App with 4 threads running only Thread.Sleep(0) inside a loop:  100% CPU.

App with 4 threads running only Thread.Sleep(1) inside a loop:  0% CPU.

In C using Sleep API the same results.

 

            do
            {
                lock (inMemoryCache.SyncRoot)
                {
                    cacheItemBeforeLock = (CacheItem)inMemoryCache[key];
                    if (IsObjectInCache(cacheItemBeforeLock))
                    {
instrumentationProvider.FireCacheAccessed(key, false);
                        return null;
                    }
                    lockWasSuccessful = Monitor.TryEnter(cacheItemBeforeLock);
                }
                if (lockWasSuccessful == false)
                {
                    Thread.Sleep(0);
                }
            } while (lockWasSuccessful == false);

 

 

 

 

So, there is a reason to use Thread.Sleep(0) over here? 

Someone else is getting the same behavior?

 

 

Tkz,

Jorge Rocha Gualtieri

Jan 27, 2010 at 2:25 AM

As you have said, it was there in order to wait for the lock to be released on the cache item.  I don't know if there would be any other reason, we'll ask the Entlib team about this.

 

Sarah Urmeneta
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com

Feb 2, 2010 at 5:10 PM

Hi Sarah,

 

Could this wait be changed to Sleep(1) instead of Sleep(0)?

I've wrote a simple app to show my point. The code is in the end of the post.

 

If you run the app like: ThreadSleep 4 0 the CPU goes skyes.

but if you run the app like: ThreadSleep 4 1 the CPU keeps its normal usage.

 

This same behavior is happening in EntLib caching.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace CSThreadSleep {
    class Program {

        static uint _numThreads = 0;
        static int _sleepTime = 0;
        

        static void Main(string[] args) {
            Console.WriteLine("ThreadSleep v0.1 - test thread sleep CPU behavior");
            Console.WriteLine();

            if (!ParseParameters(args)) {
                ShowHelp();
                return;
            }

            for (int i = 0; i < _numThreads; i++) {
                Thread t = new Thread(new ThreadStart(DoThreadSleep));
                t.IsBackground = true;
                t.Start();
            }

            Console.WriteLine("Check CPU usage with current settings.");
            Console.WriteLine("Press any key to exit.");
            Console.Read();
        }

        private static void ShowHelp() {
            Console.WriteLine("Usage: ThreadSleep <numThreads> <sleepTime>");
            Console.WriteLine();
        }


        private static bool ParseParameters(string[] args) {
            try {
                _numThreads = uint.Parse(args[0]);
                _sleepTime = int.Parse(args[1]);

                return true;
            } catch (Exception e) {
                Console.Error.WriteLine("ERR: {0}", e.Message);
                return false;
            }
        }

        static void DoThreadSleep() {
            while (true) {
                Thread.Sleep(_sleepTime);
            }
        }
    }
}

 

Jorge Rocha Gualtieri

 

May 20, 2010 at 10:51 PM

Jorge and Sarah,

I am having the same issue with my production web servers, memory dumps show all threads are running GetData() from EnterpriseLibrary Cache Manager (4.1) while the CPU is 100%.

Is the solution changing the sleep(0) to sleep(1) ?

any other ideas? what are the consequences of waiting for 1 mili second?

 

Thanks

Robert Broomandan

 

May 21, 2010 at 7:30 AM

Just trying to help, this might help you decide - http://msdn.microsoft.com/en-us/library/d00bd51t(VS.80).aspx (bottom of the page)

summers

 

May 21, 2010 at 7:34 PM

Hi broomandan

I've did one workaround over here. I've added the values into one SafeDictionary and added the dictionary instance into the EL Cache. 

On the code that reads the cached values I look most of the times into my dictionary instance instead of checking the EL cache block all the times.

The performance is very good but not solves the caching block issues.

 

JRG

May 24, 2010 at 3:08 PM

Thank you summers, it was a helpful article.

May 24, 2010 at 7:00 PM

Hi broomandan

 

Changing to Thread.Sleep(1) solved your issue?

After a lot of tests I found that the Caching Application Block was blocking my threads more than needed.

I got a good result creating a custom dictionary that uses ReaderWriterLockSlim, using this class as the lock manager makes readers non-blocking to each other, only writers block all threads trying to use the dictionary.

 

JRG

May 24, 2010 at 8:22 PM

Hi Jorge,

Changing to Thread.Sleep(1) did not solve my issue, it seems like this change will fix thread starvations which are caused by having threads with custom priority which I don't have any.

However, The problem in my case was caused by a bad piece of code which was calling GetData() in a loop, I solved it by adding locking to that peice of code. 

Your solution totally makes sense, since Enterprise Library locks the cachItem during reads and writes and treats them the same.

 

Found below blog very helpful to understand the problem of Sleep(0) 

http://www.bluebytesoftware.com/blog/PermaLink,guid,1c013d42-c983-4102-9233-ca54b8f3d1a1.aspx

 

Thanks,

Robert broomandan