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 lock
s. (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();
}
}
}