2

I'll try to explain this the best I can. If I fall down on this then I'll delete this and move on. Maybe you guys can give me some ideas.

This is a web app. (C# 4.0 (server), and JQuery 1.6.1 Client)

On the Client side we have a bunch of textboxes set up where when the .focusout event is triggered, an AJAX call is fired, sending data to a WCF Service.

On the server side, We have created a custom endpoint behavior as we have some security stuff we have set up so we can grab user info when these AJAX calls are made (This works fine for us so I am not looking for setup help.)

The problem comes when I get creative in JQuery, like if I want to tell a bunch of boxes to update at once (even if it is 2 textboxes!)

$(".blahblahClass").focusout(); //fires a bunch of AJAX calls

BOOM. I get a System.IndexOutOfRangeException (assumed to be a threading error) in my JsonAuthCallContextInitializer below, here, where it tries to ADD a key to this Dictionary in the BeforeInvoke() of my CallContextInitializer:

_PrincipalMap[key] = Thread.CurrentPrincipal;

Its important to mention that I am removing a key from this very same dictionary in the AfterInvoke()

ok.. being a dictionary, this is probably not threadsafe. so I added some locks. (which you will see in the code below)

I went for ReaderWriterLockSlim as I read that lock had some concurrency issues, and ReaderWriterLock had some issues too.

So I added the locks (they are in the code below), using the default LockRecursionPolicy (meaning that I simply left the ReaderWriterLockSlim constructor empty).

When I ran the app again, I would get a LockRecursionException (A read lock may not be acquired with the write lock held in this mode)

throwing LockRecursionPolicy.SupportsRecursion into the ReaderWriterLockSlim constructor made the exception go away, unfortunately, not all of the textboxes in my web page update..

Off the top of my head (which I will be trying today) is perhaps to make the dictionary itself threadsafe. Something like this: What's the best way of implementing a thread-safe Dictionary?

but I'm not convinced it will solve things here.

Update: so I've tried a couple of other things. I decided to use a ConcurrentDictionary, and I even decided what the heck, and got rid of the .Remove in the AfterInvoke(). No Dice. It basically either suppresses the error, and only one textbox on the .html page will update, or busts in the BeforeInvoke() if you have more than a few textboxes that update

FYI, the static Dictionary is intentional

Suggestions? (applicable behavior code below)

        public class JsonAuthEndpointBehavior : IEndpointBehavior
        {
            private string _key;

            public JsonAuthEndpointBehavior(string key)
            {
                if (key == null) throw new ArgumentNullException("key");

                _key = key;
            }

            public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
            {
                var jsonAuthCallContextInitializer = new JsonAuthCallContextInitializer(_key);

                foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)
                {
                    operation.CallContextInitializers.Add(jsonAuthCallContextInitializer);
                }
            }

            protected void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
            {
                // Do nothing
            }

            public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
            {
                // Do nothing
            }

            public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
            {
                // Do nothing
            }

            public void Validate(ServiceEndpoint endpoint)
            {
                // Do nothing
            }
        }


  public class JsonAuthCallContextInitializer : ICallContextInitializer
    {
        private readonly string _key;
        private static readonly Dictionary<int, IPrincipal> _PrincipalMap = new Dictionary<int, IPrincipal>();
        private readonly ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

        public JsonAuthCallContextInitializer(string key)
        {
            if (key == null) throw new ArgumentNullException("key");

            _key = key;
        }

        public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
        {
            if (WebOperationContext.Current == null)
                throw new NotSupportedException("JSON Authentication call context initializer requires HTTP");

            if (Thread.CurrentPrincipal != null)
            {
                var key = Thread.CurrentThread.ManagedThreadId;

                cacheLock.EnterReadLock();
                try
                {
                    //LockRecursionException here
                    _PrincipalMap[key] = Thread.CurrentPrincipal;
                }
                finally
                {
                    cacheLock.ExitReadLock();
                }
            }

            Thread.CurrentPrincipal = new ClaimsPrincipal(new[]
            {
                new ClaimsIdentity((from c in x.Claims select new Claim(blahblah.ToString())).ToArray())
            });

            return null;
        }

        public void AfterInvoke(object correlationState)
        {
            var key = Thread.CurrentThread.ManagedThreadId;

               cacheLock.EnterReadLock();
               try
               {
                   if (!_PrincipalMap.ContainsKey(key)) return;

                   Thread.CurrentPrincipal = _PrincipalMap[key];
               }
               finally
               {
                   cacheLock.ExitReadLock();
               }

               cacheLock.EnterWriteLock();
               try
               {
                    _PrincipalMap.Remove(key);
               }
               catch (Exception)
               {
                   cacheLock.ExitWriteLock();
               }
         }
    }
Community
  • 1
  • 1
KevinDeus
  • 11,988
  • 20
  • 65
  • 97

1 Answers1

0

Well, I read over a bit of the documentation and I am not sure if this is the problem but it looks like you have an issue here:

            cacheLock.EnterReadLock();
            try
            {
                //LockRecursionException here
                _PrincipalMap[key] = Thread.CurrentPrincipal;
            }
            finally
            {
                cacheLock.ExitReadLock();
            }

It should be cacheLock.EnterWriteLock and cacheLock.ExitWriteLock since you are adding/changing the value in the dictionary.

John Kalberer
  • 5,690
  • 1
  • 23
  • 27
  • very good point. alas it didn't fix things. I changed them to WriteLock/ExitWriteLock and I get the following: "Recursive write lock acquisitions not allowed in this mode." Changing the LockRecursionPolicy to .SupportsRecursion gives me the same issue I had before, not all textboxes update. – KevinDeus Jul 28 '11 at 17:17
  • Yeah, I figured your issue was a lot deeper than that. – John Kalberer Jul 28 '11 at 19:18