Just wanted to apologize for my site being either down or extremely slow in the past couple of days... my hosting provider is having a lot of issues with the server i'm currently on, and they are looking into it. Hopefully, it'll be resolved soon.
Archive for February, 2009
Silverlight’s ProgressBar And Possible Memory Leaks
This has got to be the weirdest memory leak i've ever investigated. We have this kick-ass Silverlight application, but unfortunately it suffered from very high memory usage that went up rather rapidly. So i attached windbg to the browser's process, took a memory dump and checked out which objects were still available in the heap. Much to my surprise, pretty much everything we instantiated was retained in memory and never got removed from the heap.
So i started looking into the usual things: making sure disposable instances where disposed of properly, that evenhandlers were unregistered properly, etc. I went over the code and it seemed to be alright. Stepping through the code with a debugger verified that disposables were indeed disposed of, and that all event handlers were unregistered.
So why was pretty much everything kept in memory? Further research with windbg showed that every ProgressBar instance that was ever created (and we use a lot of them, basically every time we make a call to the application server) kept a reference to the UserControl it was placed on and thus, kept the UserControl and all the references it contained alive. In our case, that includes our presentation models and obviously all of the contained child UserControls.
The ProgressBar is defined like this:
<ProgressBar Height="40" Style="{StaticResource ourKickAssStyle}" VerticalAlignment="Center" Width="40" IsIndeterminate="True"/>
The key here is the usage of the IsIndeterminate property... setting this to true causes the ProgressBar to move continuously without respecting any current Value property. You know, basic stuff. The thing is... if i change the definition of the ProgressBar to this:
<ProgressBar Height="40" Style="{StaticResource ourKickAssStyle}" VerticalAlignment="Center" Width="40" />
The memory leak suddenly went away ![]()
Obviously, this isn't a solution because the ProgressBar now doesn't really indicate any progress and we need our kick ass custom animation to retain the coolness of the application.
So for some reason, when you set the ProgressBar's IsIndeterminate property to true, it actually keeps all of its references alive even when the ProgressBar control is removed from its parent control. Happy times.
We now have the following ugly method in one of our base UI classes:
private static void StopProgressBars(DependencyObject dependencyObject)
{
var count = VisualTreeHelper.GetChildrenCount(dependencyObject);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
if (child != null)
{
var progressBar = child as ProgressBar;
if (progressBar != null)
{
progressBar.IsIndeterminate = false;
}
StopProgressBars(child);
}
}
}
Excellent Tool For Silverlight Developers
A coworker of mine pointed this out... if you're doing Silverlight development, make sure you install Silverlight Spy!
The Tests For The Readable Code Challenge
After the uncommented code, and then the commented version of the code, you finally get to see the tests that verify that solution protects the code from the issue it was facing. I think all 3 posts (and the comments on them) sufficiently explain the problem and the solution so i won't go through the trouble of explaining everything in this post. The tests however, might not be very clear to everyone. I'm only posting 3 tests, though there are more but then the post would just be way too long.
These tests use the following 2 fields:
private Broadcaster broadcaster;
private IClientProxyFactory clientFactory;
Which are set up before each test like this:
clientFactory = MockRepository.GenerateMock<IClientProxyFactory>();
broadcaster = new Broadcaster(clientFactory);
First of all, take a look at some of the utility methods that these tests use:
private List<IClientProxy> GetBroadcastersClients()
{
var clientsFieldInfo = typeof(Broadcaster).GetField("clients", BindingFlags.NonPublic | BindingFlags.Instance);
return (List<IClientProxy>)clientsFieldInfo.GetValue(broadcaster);
}
private Exception GetExceptionThrownBy(Action yourCode)
{
try { yourCode(); } catch (Exception e) { return e; }
return null;
}
private void RegisterClientWithBroadcaster(IClientProxy client)
{
clientFactory.Stub(f => f.CreateClientProxyForCurrentContext(null))
.IgnoreArguments().Return(client).Repeat.Once();
broadcaster.Register();
}
private IClientProxy RegisterClientWithImplementationForSend(Action implementation)
{
var client = MockRepository.GenerateMock<IClientProxy>();
client.Stub(c => c.SendNotificationAsynchronously(null))
.IgnoreArguments().WhenCalled(obj => implementation());
RegisterClientWithBroadcaster(client);
return client;
}
private IClientProxy RegisterClientWithEmptySendImplementation()
{
return RegisterClientWithImplementationForSend(() => { });
}
private void RegisterClientsWithImplementationForSend(int number, Action implementation)
{
var clients = new IClientProxy[number];
for (int i = 0; i < number; i++)
{
clients[i] = RegisterClientWithImplementationForSend(implementation);
}
}
And then the actual tests:
[Test]
public void RegisterClientWhileBroadcasting_ClientIsAddedAndBroadcastingDidntThrowException()
{
RegisterClientsWithImplementationForSend(5, () => Thread.Sleep(50));
Exception exceptionFromBroadcastThread = null;
var broadcastThread =
new Thread(() => exceptionFromBroadcastThread = GetExceptionThrownBy(() => broadcaster.Broadcast(null)));
broadcastThread.Start();
Thread.Sleep(50);
var newClient = RegisterClientWithEmptySendImplementation();
broadcastThread.Join();
Assert.IsNull(exceptionFromBroadcastThread);
Assert.That(GetBroadcastersClients().Contains(newClient));
}
[Test]
public void ClientFaultedWhileBroadcasting_FaultedClientIsRemovedFromClientsList()
{
RegisterClientsWithImplementationForSend(2, () => { });
var faultyClient = MockRepository.GenerateMock<IClientProxy>();
faultyClient.Stub(c => c.SendNotificationAsynchronously(null))
.IgnoreArguments().WhenCalled(obj => faultyClient.Raise(c => c.Faulted += null, faultyClient, EventArgs.Empty));
RegisterClientWithBroadcaster(faultyClient);
RegisterClientsWithImplementationForSend(2, () => { });
broadcaster.Broadcast(null);
Assert.IsFalse(GetBroadcastersClients().Contains(faultyClient));
}
[Test]
public void ClientFaultedInSeparateThreadWhileBroadcasting_FaultedClientIsRemovedWithoutExceptionDuringBroadcasting()
{
var faultyClient = RegisterClientWithEmptySendImplementation();
RegisterClientsWithImplementationForSend(10, () => Thread.Sleep(50));
Exception exceptionFromBroadcastThread = null;
var broadcastThread =
new Thread(() => exceptionFromBroadcastThread = GetExceptionThrownBy(() => broadcaster.Broadcast(null)));
broadcastThread.Start();
Thread.Sleep(150);
faultyClient.Raise(c => c.Faulted += null, faultyClient, EventArgs.Empty);
broadcastThread.Join();
Assert.IsNull(exceptionFromBroadcastThread);
Assert.IsFalse(GetBroadcastersClients().Contains(faultyClient));
}
Note: i'm not sure if this is actually the best way to test this code... there will probably be better solutions for testing threading issues.
The Commented Version Of The Readable Code Challenge
Before i post the tests and a further explanation of the code i listed in the readable code challenge, i wanted to post only the commented version of this code:
public class Broadcaster : IBroadcaster
{
private readonly object monitor = new object();
// This reference will be overwritten with a new instance of a clients List whenever
// we need to add or remove a client from the list. The reason for this is because
// we need to be able to loop through the clients list, but during this loop clients
// might try to register or might be removed from the clients list.
// Using the same instance of the clients List and synchronizing all access with the
// monitor object would not be sufficient because if both the loop and the removal
// would lock on monitor, and the loop and removal would happen on the same
// thread then one of the operations won't block, because that thread already has
// acquired the lock. At that point we get a concurrency exception because clients'
// Enumerator would have been modified while looping through it.
private List<IClientProxy> clients;
private readonly IClientProxyFactory clientProxyFactory;
public Broadcaster(IClientProxyFactory clientProxyFactory)
{
this.clientProxyFactory = clientProxyFactory;
clients = new List<IClientProxy>();
}
public void Register()
{
var client = clientProxyFactory.CreateClientProxyForCurrentContext("Nokeos/IBroadcastOverWcf/Receive");
client.Faulted += Client_Faulted;
AddClientToRegisteredClients(client);
}
private void AddClientToRegisteredClients(IClientProxy client)
{
lock (monitor)
{
// we create a new List instance based on the previous list plus the new client
// and then we assign the new list to the clients reference that the rest of
// this class uses. This happens behind a lock to make sure that the clients
// reference isn't overwritten by the RemoveClientFromRegisteredClients method
// simultaneously
clients = new List<IClientProxy>(clients) { client };
}
}
public void Broadcast(Notification notification)
{
// When we enter the foreach loop, we get a reference to the enumerator of
// the _current_ clients reference (the reference might be overwritten while
// we loop, but our enumerator will never be modified) which means we don't
// need to use a lock here
foreach (var client in clients)
{
// if the send operation fails, the client's Faulted event will be
// triggered which causes removal of the client from our clients list.
// this can happen either while we are in this loop, or afterwards in
// a background thread but in both cases, we don't need to worry about it
// here
client.SendNotificationAsynchronously(notification);
}
}
private void Client_Faulted(object sender, System.EventArgs e)
{
var client = (IClientProxy)sender;
client.Faulted -= Client_Faulted;
RemoveClientFromRegisteredClients(client);
}
private void RemoveClientFromRegisteredClients(IClientProxy client)
{
lock (monitor)
{
// we create a new List instance based on the previous list minus the client
// that needs to be removed and then we assign the new list to the clients
// reference. This happens behind a lock to avoid a simultaneuos overwrite
// of the clients reference by the AddClientToRegisteredClients method
var clientList = new List<IClientProxy>(clients);
clientList.Remove(client);
clients = clientList;
}
}
}
I hope the comments clarify the problem sufficiently. If not, tell me what's not clear to you because it might mean i need to clarify the comments more. Also, suggestions on how to make this code more readable and reducing the need for comments would be very welcome ![]()
I'll post the tests tomorrow, and then you can all decide what you think is more communicative: the comments, or the tests.