ReaderWriterLock, Dispose pattern can also increase reliability

[Update 29/07/2004: Just updated the code, the old one was... broken to say the least :)]

Following Ian’s snippet about multithreading, and although I love the lock keyword as much as everybody, a Monitor is in my experience nearly always a bad choice. Why would you ask? What kind of weird thing the good old technologist have against the Monitor?

Well most of the time what you really want is let several threads read, but only one thread only write. Oh, and you don’t want to let someone write while you’re reading, nor do you want anyone reading while you write.

Let’s have a very quick overview of the ReaderWriterLock object. A ReaderWriterLock is a synchronization object that lets several readers acquire a reader lock at the same time. If any reader lock has been acquired, other reader locks will happily be given, but a request for a writer lock won’t be given until all the reader locks are released.

Not so complex you would tell me, and you wouldn’t be wrong. But to be really honest, the api for this object is cluttered, too complex, and as such, the code using it becomes very unreadable… And multi threaded unreadable code is an absolute no-go.

Let’s see what the methods are:

  • AcquireReaderLock to acquire a reader lock and provide a TimeSpan as a timeout value.
  • AcquireWriterLock to acquire a writer lock, and provide a TimeSpan as a timeout value.
  • ReleaseReaderLock to release a reader lock.
  • ReleaseWriterLock to release a writer lock.


Quite simple isn’t it? Now let’s see a few others as well:

  • UpgradeToWriterLock gets tricky, it lets you switch from your reader lock to a writer lock (and of course only if you’re the only reader does it return immediately)
  • DowngradeFromWriterLock let’s you release this upgrade.
  • ReleaseLock, RestoreLock you don’t really want to know about them, won’t need them, they are evil because they don’t timeout. If you want to know more, msdn is your friend.


So why exactly can’t I just AcquireWriterLock when I already have AcquireReaderLock? Remember that I said the ReaderWriterLock doesn’t let you acquire a writer lock unless all reader locks have been released… Which is why you have a different path! So my typical code would look like that:


            private ReaderWriterLock myLock;
            private Hashtable myObjectCache = new Hashtable();
            public void DoSomethingWithALock()
            {
                  myLock.AcquireReaderLock(TimeSpan.FromSeconds(30));
                  try
                  {
                        // let's see if i have this object in cache
                        StrongTypedObject myObject = myObjectCache["noItHasNothingToDoWithAKey"] as StrongTypedObject;

                        if (myObject == null)
                        {
                              // if not i'll load it
                              StrongTypedObject myNewObject = new StrongTypedObject("orWithChains");
                              // and now I need a write access...
                              LockCookie myCookie = myLock.UpgradeToWriterLock(TimeSpan.FromMinutes(1));
                              try
                              {
                                    myObjectCache["noItHasNothingToDoWithAKey"] = myNewObject;
                              }
                              finally
                              {
                                    myLock.DowngradeFromWriterLock(ref myCookie);
                              }
                        }
                  }
                  finally
                  {
                        myLock.ReleaseReaderLock();
                  }
            }



Ok so as you can see, the code is a bit complex. Now the question is, what happens if I want to factor out the creation of the object in a separate method? That’s where it becomes a real challenge. If I do that, I can either put in this new method the lock upgrade, or leave it to the caller to set it up. What is the problem with the lock upgrade being integrated with the method?

Well this method will be called from different points in your code, or worse, in other people code. You then require the caller to set up the correct lock environment (acquiring a reader lock) before calling your method. Not a good thingTM.

The best way to go is obviously to detect if when called, the method is already under a reader lock or not. This is the code doing it:

            public void CreateMyDoSomethingObject()
            {
                  bool isReaderLockHeldOnEntry = myLock.IsReaderLockHeld;
                  LockCookie myCookie = null;
                  if (isReaderLockHeldOnEntry)
                        myCookie = myLock.UpgradeToWriterLock(TimeSpan.FromSeconds(30));
                  else
                        myLock.AcquireWriterLock(TimeSpan.FromSeconds(30));

                  try
                  {
                        StrongTypedObject myNewObject = new StrongTypedObject("orWithChains");
                        myObjectCache["noItHasNothingToDoWithAKey"] = myNewObject;
                  }
                  finally
                  {
                        if (isReaderLockHeldOnEntry)
                              myLock.DowngradeFromWriterLock(ref myCookie);                                 else
                              myLock.ReleaseWriterLock();
                  }
            }

It’s already nearly unreadable. Now try to have several locks, and you end up with a very complex thing that is not readable by mortal people anymore.

Which is why I implemented the RWLock object following Ian’s code. With it, you can rewrite the code as:


            public void CreateMyDoSomethingObject()
            {
                  using (RWLock.LockWriter(myLock, TimeSpan.FromMinutes(1)))
                  {
                        StrongTypedObject myNewObject = new StrongTypedObject("orWithChains");
                        myObjectCache["noItHasNothingToDoWithAKey"] = myNewObject;
                  }
            }



As you can see, it’s much much less cluttered. The LockWriter method either upgrade to a writer lock or acquire a new writer lock, depending on the state of the lock on the current thread when the method was called. Now you can ensure good synchronization, and neither have to have a lot of code or leave the caller to deal himself with setting the locks right.

The full code for the class (also attached as a .zip file)

using System;

using System.Threading;

// (C) 2004 Sebastien Lambla

// Unlimited use license given to Cubiks Ltd for the Cubiks Online platform.

namespace Cubiks.Threading

{

#if DEBUG

      public class RWLock : IDisposable

#else

public struct RWLock : IDisposable

#endif

      {

            private static TimeSpan timeout = TimeSpan.FromSeconds(5);

 

            public static RWLock LockReader(ReaderWriterLock lockObject)

            {

                  return LockReader (lockObject, timeout);

            }

 

            public static RWLock LockReader(ReaderWriterLock o, TimeSpan timeout)

            {

                  RWLock tl = new RWLock(o);

                  try

                  {

                        o.AcquireReaderLock(timeout);

                  }

                  catch (ApplicationException e)

                  {

#if DEBUG

                        System.GC.SuppressFinalize(tl);

#endif

                        throw;

                  }

                  return tl;

            }

            public static RWLock LockWriter(ReaderWriterLock o)

            {

                  return LockWriter(o, timeout);

            }

            public static RWLock LockWriter(ReaderWriterLock o, TimeSpan timeout)

            {

                  RWLock tl = new RWLock(o);

           

                  try

                  {

                        if (o.IsReaderLockHeld)

                        {

                              tl.lockCookie = o.UpgradeToWriterLock(timeout);

                              tl.lockType = LockType.WriterFromReader;

                        }

                        else

                        {

                              o.AcquireWriterLock(timeout);

                              tl.lockType = LockType.Writer;

                        }

                  }

                  catch (ApplicationException)

                  {

#if DEBUG

                        System.GC.SuppressFinalize(tl);

#endif

                        throw;

                  }

                  return tl;

            }

 

            private ReaderWriterLock target;

            private LockType lockType;

            private LockCookie lockCookie;

            private RWLock (ReaderWriterLock o)

            {

                  lockType = LockType.None;

                  lockCookie = new LockCookie();

                  target = o;

            }

 

            public void Dispose ()

            {

           

                  try

                  {

                        switch (this.lockType)

                        {

                              case LockType.Reader:

                              {

                                    target.ReleaseReaderLock();

                                    break;

                              }

                              case LockType.Writer:

                              {

                                    target.ReleaseWriterLock();

                                    break;

                              }

                              case LockType.WriterFromReader:

                              {

                                    target.DowngradeFromWriterLock(ref lockCookie);

                                    break;

                              }

                              default:

                                    System.Diagnostics.Debug.WriteLine("Problem, no lock acquired!");

                                    break;

                        }

                  }

                  catch (ApplicationException exception)

                  {

                        System.Diagnostics.Debug.WriteLine(exception.ToString());

                  }

                  catch (Exception e)

                  {

                        System.Diagnostics.Debug.WriteLine(e.ToString());

                  }

                  // It's a bad error if someone forgets to call Dispose,

                  // so in Debug builds, we put a finalizer in to detect

                  // the error. If Dispose is called, we suppress the

                  // finalizer.

#if DEBUG

                  GC.SuppressFinalize(this);

#endif

            }

 

#if DEBUG

            ~RWLock()

            {

                  // If this finalizer runs, someone somewhere failed to

                  // call Dispose, which means we've failed to leave

                  // a monitor!

                  System.Diagnostics.Debug.Fail("Undisposed lock");

            }

#endif

            private enum LockType

            {

                  None,

                  Reader,

                  Writer,

                  WriterFromReader

            }

      }

}



Ads

Two way syndication, discussion continues

When I posted about Channel9 going live, I im’d Robert Scoble about it. He was kind enough to respond. Please also note from feedster (I have a feed looking at my name, how selfish), that RoudyBob responds as well. (Is that the beginning of a nice discussion or what?)

Let me explain a bit more what I mean by two way syndication. You have current syndication portals, like the http://weblogs.asp.net portal. They effectively aggregate content from the people hosting blogs on their servers. This is simple and effective when you look at it from a syndicator perspective. But what if we are to look from the author perspective? If I publish content on my blog, on specialized web sites, on msdn, and have a bunch of interviews of myself on channel9 (which would be really cool by the way), what happens? All this content is hosted by different tools, with different rights. Reprinting and redistributing of this content doesn’t always depends on the license I decided. My contract with msdn (well, when I’ll have one that is) may force me to publish with a more restrictive redistribution (no right to republish after xx months on another web site, exclusivity, etc).

Should my web site then become an aggregator of myself? Here we don’t necessarily fall into copyright hurdles (as I’m still holding part of my copyright, shared or not with my publisher, unless I am fool enough to get rid of it altogether). We fall, as Bob underlines it, under a licensing, or lack of licensing of the content. My content has no license, so I’m under the default copyright. It means that I could attack anyone republishing my work fully without asking permission, or making content available for money. I will change that (when my content will be valuable enough that anyone will actually care to reproduce it). MSDN will have another license towards its readers.

My point in the previous post was that: people come to my website to see if I have something interesting to say. They don’t come for a company, but to see if I am providing something that is of any interest to them. When my content is diluted across blogs, magazines, aggregators, people can’t find whatever is related to me easily. My point was that, as I put the content, the company should automatically give me:

1.      A license letting me redistribute my own content by my own channel. This can be negotiated when you’re Ingo Rammer, it is much more difficult when you’re Sebastien Lambla;

2.      A technical need by which I can aggregate, on my own site, whatever is related to me, which is a feed of all the content which have any relation to me on the web site.

 

Obviously, both licensing and copyright isssues are to be discussed. The discussion I tried to launch was about the technical merits of such two way integration.

Think about cross blogging as another problem. When I subscribe to the weblogs.asp.net aggregated feed, but I also subscribe to the original feed of one of the authors, I read twice the same data. PDCBloggers is even worse as the format doesn’t let me recognize easily that the content is something that I already read about. Two way syndication is about me publishing my content on my web site, even if I have to be tied to a specific license with each of my publishers.

Ads

ISL 2

Here comes a second wave of links that I didn’t blog about. We’re reaching November 2003 at this point!

Longhorn

·         Felipe gives a super fast introduction to layouts in Avalon, and a more detailed overview of the custom layout API. He is one of the Avalon PM by the way. His new blog here is a MUST for anyone interested in Avalon related content.

·         James talks about the Media API in longhorn. Let’s hope the recent cut in features is not going to get this out of the way.

·         Something that didn’t get much blog attention since the PDC, Automation and Accessibility in Avalon by Scott.

Things we use everyday

·         An IIS6 problem with kernel mode caching, from Brendan… There should be an easier way.

·         The absolute list of ports used by Microsoft products, from Christian (sorry it is in French, still readable by everybody though).

·         Flushing DNS in Windows, very useful, from Richard.

·         XSL-T intellisense for vs.net, even more useful, from Jesse Ezell.

·         Well, we all use Robert Scoble enough that we could effectively send him a thanks postcard. I will do that soon. Robert if you’re reading this, would you prefer Monaco or London?

·         An introduction to Code Access Security from CodeProject, by Sam Gentile (again).

Things we wish we’d use everyday (or not)

·         An example on gotdotnet about using Rigid Body Dynamics in Managed DirectX…

·         From Sam Gentile, Raymond on calling conventions Parts 1, 2 and 3.

Fun

·         Well yes I’m late to the party, but an uber-cool design for a windows oriented blog. Who’s going to make a glass like blog design first?

·         Project Management Proverbs from Alan Dean. Goes fine with the top developer excuses I blogged about in ISL 1.

·         D0m1, from Eric Gunnerson, posts something I’ve read conscientiously for the last few months, http://www.thingsmygirlfriendandihavearguedabout.com.

·         The Internet Helpdesk from matt. Way too funny.

I think that’s enough for a second list, don’t you?

Ads