Solved

C# async ping memory leak

Posted on 2011-02-11
10
2,010 Views
Last Modified: 2013-12-17
I have a program using async ping to check response from equipment. Every ping allocates memory while it is outstanding (have not yet returned and released its allocated memory yet). Sometimes a ping does NOT return with any reply and the allocated memory remains allocated for ever and eventually the app stops with out of mem error. I don't know how to dispose of these "hanging" pings end consequently release the memory

Any help would be appreciated!
0
Comment
Question by:deeMvee
  • 5
  • 4
10 Comments
 
LVL 4

Expert Comment

by:HawyLem
Comment Utility
Try to null the reference once done, the garbage collector should take care of that
0
 

Author Comment

by:deeMvee
Comment Utility
Have tried to null and dispose of the ping but still when a ping does not return, it remains hanging somewhere in the system.. I will try to reorganize the code but strange how this has happened after two years of relatively smooth running...
0
 
LVL 33

Expert Comment

by:Todd Gerbert
Comment Utility
Can you post some code?  Are you using a new Ping object for each ping sent, one per address, or just one?

I've been running this loop, pinging a non-existent address on my network, and memory usage isn't budging at all.

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

namespace ConsoleApplication1
{
	class Program
	{
		static ManualResetEvent resetEvent = new ManualResetEvent(true);

		static void Main(string[] args)
		{
			IPAddress target = new IPAddress(new byte[] { 192, 168, 1, 3 });
			Ping ping = new Ping();

			ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted);

			while (true)
			{
				resetEvent.WaitOne(-1);
				resetEvent.Reset();
				ping.SendAsync(target, 50, null);
				Thread.Sleep(100);
			}
		}

		static void ping_PingCompleted(object sender, PingCompletedEventArgs e)
		{
			if (e.Cancelled)
				Console.WriteLine("Ping cancelled.");
			else
				Console.WriteLine(Enum.GetName(typeof(IPStatus), e.Reply.Status));
			resetEvent.Set();
		}
	}
}

Open in new window

0
 

Author Comment

by:deeMvee
Comment Utility
It seems to me that you are using the same Ping (no new) all the time in your loop and let the loop wait till the reply comes back to your callback. Then your callback will kick loop to restart. Almost like sync.send?

This will probably not produce my error, at least not on the same level since you never have more than one ping outstanding.

Unfortunately I cannot wait for each of my 5000 pings (to 5000 different addresses) to return one by one since it would be too time consuming so I have to send them all out in one go. This is the reason I NEW my ping every time in the loop. I also use the userToken (fourth param) to set the ID so I know which one that is returning. And here is the core of the prob. Occasionally suddenly lets say 2500 pings never return... And I don't know why, but I think it has to do with downtime on some equipment. The prog is running on a virtual server 2008 and c# from VS-studio 2008 .Net 3.5..

The ping.send does not give an error, Still they never return but are stealing mem...
0
 
LVL 33

Expert Comment

by:Todd Gerbert
Comment Utility
That's why I asked...

I don't think it's the fact that there's an outstanding ICMP echo request that's your issue, so much as the fact that you're instantiating thousands of objects quickly.  You need to be sure to Dispose() evey Ping as soon as you get a reply, or it times out.  You may also want to manually initiate a garbage collection periodically so the .Net runtime actually frees the memory of disposed Ping's.  Of course running a garbage collection is going to cost CPU cycles - your best bet will probably be a combination of Dispose()'ing objects, collecting garbage, and running your ping in manageable groups.

Here's a stupid example, sending 65,535 pings; without GC.Collect() it peaks at about 700MB of memory usage and hovers around 500MB, with GC.Collect() it peaks at 500MB and stays around 400MB, with GC.Collect(2) it maxed at, and stayed near, 300MB.  Note that I just pass the IP Address as the userToken, and that the PingCompleted event handler disposes of whatever Ping raised the event.

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

namespace ConsoleApplication1
{
	class Program
	{
		static void Main(string[] args)
		{
			PingBigRange(new byte[] { 192, 168, 0, 0 });

			Console.ReadKey();
		}

		static void PingBigRange(byte[] address)
		{
			for (int i = 0; i < 256; i++)
			{
				for (int j = 0; j < 256; j++)
				{
					Ping ping = new Ping();
					ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted);

					IPAddress target = new IPAddress(address);
					Console.WriteLine("Pinging {0}...", target);
					ping.SendAsync(target, 5000, target);

					address[3]++;
				}

				GC.Collect(2);
				address[2]++;
			}
		}

		static void ping_PingCompleted(object sender, PingCompletedEventArgs e)
		{
			Console.WriteLine("Response from {0}: {1}", (IPAddress)e.UserState, e.Reply.Status);

			((Ping)sender).Dispose();
		}
	}
}

Open in new window

0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 

Author Comment

by:deeMvee
Comment Utility
Thanks, I am testing the GC in the ping sending loop to see the result tonight.
I tried the "((Ping)sender).Dispose();" but it does not seem to work as mem is building up. When I set it back to "((IDisposable)sender).Dispose();" it works fine and mem is freeing up nicely. Do you know why?

Anyway, will come back to you tomorrow :) And thanks for your time so far!
0
 
LVL 33

Accepted Solution

by:
Todd Gerbert earned 500 total points
Comment Utility
There shouldn't be any difference between ((Ping)sender).Dispose() and ((IDisposable)sender).Dispose() - both will result in the same method being run (I didn't see any difference on my system).
0
 

Author Comment

by:deeMvee
Comment Utility
I still have not found the cause of this problem. I have tried various things but they all lead to nothing when this happens... Here is a part from the log. There is one line for each ping loop. It sends 7000 pings async. Counts the pings in the callback and wait for 20 secs before deciding no more pings will arrive. If there still are outstanding pings it will write a line like this: "18.02.2011 19:30:27: DEVpinger pings lost: 624" which means that of 7000 pings 624 did never return, neither did they produce an error. This can be seen from the error count which is 0 almost always.. For every line 7000 pings went OK but occasionally somethings happens and it gets probs...

18.02.2011 19:02:19: Available Memory: 1064091648 ErrCount: 0
18.02.2011 19:03:56: Available Memory: 1068335104 ErrCount: 0
18.02.2011 19:05:01: Available Memory: 1068888064 ErrCount: 0
18.02.2011 19:06:38: Available Memory: 1066934272 ErrCount: 0
18.02.2011 19:07:40: Available Memory: 1066065920 ErrCount: 0
18.02.2011 19:09:17: Available Memory: 1066119168 ErrCount: 0
18.02.2011 19:10:55: Available Memory: 1066201088 ErrCount: 0
18.02.2011 19:12:33: Available Memory: 1066459136 ErrCount: 0
18.02.2011 19:14:11: Available Memory: 1066696704 ErrCount: 0
18.02.2011 19:15:49: Available Memory: 1065127936 ErrCount: 0
18.02.2011 19:17:26: Available Memory: 1067212800 ErrCount: 0
18.02.2011 19:19:04: DEVpinger pings lost: 1251
18.02.2011 19:19:04: Available Memory: 979968000 ErrCount: 0
18.02.2011 19:20:41: Available Memory: 979763200 ErrCount: 0
18.02.2011 19:22:19: DEVpinger pings lost: 1336
18.02.2011 19:22:19: Available Memory: 889532416 ErrCount: 0
18.02.2011 19:23:56: Available Memory: 890941440 ErrCount: 0
18.02.2011 19:25:34: DEVpinger pings lost: 316
18.02.2011 19:25:34: Available Memory: 872071168 ErrCount: 0
18.02.2011 19:27:11: Available Memory: 874082304 ErrCount: 0
18.02.2011 19:28:49: Available Memory: 873857024 ErrCount: 0
18.02.2011 19:30:27: DEVpinger pings lost: 624
18.02.2011 19:30:27: Available Memory: 827629568 ErrCount: 0
18.02.2011 19:32:04: DEVpinger pings lost: 835
18.02.2011 19:32:05: Available Memory: 769146880 ErrCount: 0
18.02.2011 19:33:09: Available Memory: 776126464 ErrCount: 0
18.02.2011 19:34:47: DEVpinger pings lost: 29
18.02.2011 19:34:47: Available Memory: 773337088 ErrCount: 0

I seem to need more time to investigate so I'll accept your help as solution :)

And if it of any help, it definitely do NOT release mem if I don't use ((IDisposable)sender).Dispose() (See above)

Thanks for help!
0
 
LVL 33

Expert Comment

by:Todd Gerbert
Comment Utility
I would respectfully disagree regarding the difference between IDisposable.Dispose() and Ping.Dispose() - so far as I understand it's impossible for the two to behave differently.  Consider the example below, and note that the exact same code is executed for both ((DisposableObject)sender).Dispose() and ((IDisposable)sender).Dispose().
using System;

class Program
{
	static void Main(string[] args)
	{
		DisposableObject obj = new DisposableObject();

		Test(obj);
	}

	static void Test(object sender)
	{
		((DisposableObject)sender).Dispose();
		((IDisposable)sender).Dispose();
	}
}

class DisposableObject : IDisposable
{
	public void Dispose()
	{
		Console.WriteLine("Dispose has been called! (press any key)");
		Console.ReadKey();
	}
}

Open in new window


However, that's neither really here nor there (albeit an interesting discussion).  Perhaps this code might help - this wraps up the pinging logic inside it's own class, which implements IDisposable.  The Ping's are disposed as their response is known (whether success or error), and the Dispose method of the PingSender class will clean up any others that have been left hanging around.  Putting the PingSender inside a "using" block should just about guarantee it's Dispose() method will always be called.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Net;
using System.Net.NetworkInformation;

namespace ConsoleApplication1
{
	class Program
	{
		private static Process currentProcess = Process.GetCurrentProcess();

		static void Main(string[] args)
		{
			List<IPAddress> networksToPing = new List<IPAddress>();
			for (int i = 0; i < 30; i++)
				networksToPing.Add(new IPAddress(new byte[] { 192, 168, (byte)i, 0 }));

			while (true) // loop until CTRL-C
			{
				using (PingSender pingSender = new PingSender(networksToPing))
				{
					int sent = 0, recvd = 0, error = 0, timeout = 0;

					pingSender.SendPings(ref sent, ref recvd, ref timeout, ref error);

					Console.WriteLine("Pings sent: {0}, Responses received: {1}, Timed-out: {2}, Errors: {3}",
						sent, recvd, timeout, error);
				} // The "using" statement forces pingSender.Dispose() here

				// Attempt to reclaim memory
				GC.Collect();

				Console.WriteLine("Memory usage: {0:F2}MB\r\n\r\n", Program.MemoryUsageMB);
			}
		}

		public static double MemoryUsageMB
		{
			get
			{
				currentProcess.Refresh();
				return currentProcess.PrivateMemorySize64 / 1048576d;
			}
		}
	}

	public class PingSender : IDisposable
	{
		private bool disposed = false;
		private List<Ping> pings;
		private List<IPAddress> networks;
		private int sent, responded, errored;

		public PingSender(IEnumerable<IPAddress> NetworksToPing)
		{
			pings = new List<Ping>();
			networks = new List<IPAddress>();
			networks.AddRange(NetworksToPing);
		}

		~PingSender()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if (!disposed)
			{
				disposed = true;

				if (disposing)
				{
					// Cancel any outstanding ping requests
					// and dispose of any remaining Ping objects
					foreach (Ping p in pings)
						if (p != null)
						{
							p.SendAsyncCancel();
							p.Dispose();
						}

					// Clear references to other objects
					pings.Clear();
					networks.Clear();
				}
			}
		}

		public void SendPings(ref int PingsSent, ref int ResponseReceived, ref int TimedOut, ref int Errors)
		{
			if (disposed)
				throw new ObjectDisposedException("PingSender");

			foreach (IPAddress network in networks)
			{
				byte[] addressBytes = network.GetAddressBytes();

				for (int i = 0; i < 254; i++) // Skip .0 and .254 for my class-C nets
				{
					addressBytes[3]++;
					IPAddress target = new IPAddress(addressBytes);

					Ping ping = new Ping();
					ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted);
					sent++;
					ping.SendAsync(target, target); // If not specified, timeout of ping defaults to 5 sec
				}
			}

			// Wait 20 seconds
			Thread.Sleep(20000);

			PingsSent = sent;
			ResponseReceived = responded;
			Errors = errored;
			TimedOut = sent - responded - errored;
		}

		void ping_PingCompleted(object sender, PingCompletedEventArgs e)
		{
			if (!disposed)
			{
				if (e.Error != null)
					errored++;
				else if (e.Reply.Status == IPStatus.Success)
					responded++;

				// We no longer need this ping object since we
				// have it's reply, so remove it from the list
				// and Dispose() it
				if (pings != null)
					pings.Remove((Ping)sender);
				((IDisposable)sender).Dispose();
			}
		}
	}
}

Open in new window


In my simple tests, with approximately 7,000 Ping objects (most of which fail on my network), memory usage fluctuates a bit but seems to hover right around 160MB.  Incidentally, ((IDisposable)sender).Dispose() vs. ((Ping)sender).Dispose() had zero effect on reported memory usage in my tests; if you still see that difference it may indicate a logic error elsewhere in your code.
0
 
LVL 33

Expert Comment

by:Todd Gerbert
Comment Utility
I was wrong, they could be different (though I still don't see any memory difference in practice), and I would suggest it's probably best to call the most derived version of Dispose().  That is, calling IDispose.Dispose() might result in a base type's implementation, and that base type may not have knowledge of the network resources allocated by the Ping object; whereas Ping.Dispose() would know to release network resources.

using System;

class Program
{
	static void Main(string[] args)
	{
		OtherDisposableObject obj = new OtherDisposableObject();
		Test(obj);
	}

	static void Test(object sender)
	{
		((OtherDisposableObject)sender).Dispose();
		((IDisposable)sender).Dispose();
	}
}

class DisposableObject : IDisposable
{
	public void Dispose()
	{
		Console.WriteLine("DisposableObject.Dispose()");
		Console.ReadKey();
	}
}

class OtherDisposableObject : DisposableObject, IDisposable
{
	void IDisposable.Dispose()
	{
		Console.WriteLine("IDisposable.Dispose()");
		Console.ReadKey();
	}
}

Open in new window

0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

In my previous article (http://www.experts-exchange.com/Programming/Languages/.NET/.NET_Framework_3.x/A_4362-Serialization-in-NET-1.html) we saw the basics of serialization and how types/objects can be serialized to Binary format. In this blog we wi…
Entity Framework is a powerful tool to help you interact with the DataBase but still doesn't help much when we have a Stored Procedure that returns more than one resultset. The solution takes some of out-of-the-box thinking; read on!
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now