Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous pinging

Ran into a weird "problem". Have an application that pingsweeps entire networks. Works great until you get to a network with 255.255.0.0 netmask (which is 65k + addresses).

I send my pings like this:

    foreach (string str in ListContainingAddresses)
        {
            using (Ping ping = new Ping())
            {
                if (pingCounter == 10000) { Thread.Sleep(10000); pingCounter = 0; }
                //Make an eventhandler
                ping.PingCompleted += new PingCompletedEventHandler(pingCompleted);
                //Send the pings asynchronously
                ping.SendAsync(IPAddress.Parse(str), 1000);
                sentPings++;

                //This counts pings being sent out
                pingCounter++;
            }
        }

And recive them like this:

    public void pingCompleted(object sender, PingCompletedEventArgs e)
    {
        //This counts recieved addresses 
        recievedIpAddresses++;

        if (e.Reply.Status == IPStatus.Success)
        {
            //Do something
        }
        else
        {
            /*Computer is down*/
        }
        //This checks if sent equals recieved
        if (recievedIpAddresses == sentPings )
        {
            //All returned
        }
    }

Problem is that a) Sometimes (very rarely) it doesnt complete (Condition not met). b) When it does complete the numbers dont match? If i print sent and recieved just now they are

    Sent: 65025 Recieved: 64990

Despite this the condition is met and application moves on? I dont know why and how this is happening. Is the code executing to fast for the application to update two ints? Do some pings get lost along the way? If I try it on a subnetwork with 255 addresses this problem never happens. Cant use CountDownEvent instead of variables since its .NET 3.5

like image 597
Gvs Avatar asked May 31 '13 09:05

Gvs


People also ask

What is continuous Ping on Windows?

Continuous ping by a Windows computer to the IP address 93.184.216.34 For each incoming response packet, ping issues an entry on the standard output (stdout).

How to run ping in macOS as a continuous test?

Follow the instructions below to run ping in MacOS as a continuous test: Step 1: Open the terminal. You’ll find the Mac terminal under “ Applications” in the subfolder “Utilities ”. Step 2: Run the ping command with the address of the target computer.

What does Ping sendasync do?

First on the list: Ping.SendAsync (): Asynchronously attempts to send an Internet Control Message Protocol (ICMP) echo message to a computer, and receive a corresponding ICMP echo reply message from that computer. - MSDN

How do I ping a specific time in Linux?

If you’d like to have the continuous ping issue a timestamp, use ping with the -D option. In this case, the output for each incoming response packet is preceded by a UNIX timestamp. If you don’t want to run ping on an endless loop in Linux, define the ping quantity with the -c option according to the following example.


1 Answers

Do you have any locking in place at all? That looks like your problem to me. I can see all sorts of race conditions and memory processor cache issues potentially in your code.

Try using lock for protecting the recievedIpAddresses == sentPings and

sentPings++;
//This counts pings being sent out
pingCounter++;

Using lock

For instance:

private readonly object SyncRoot = new object();

public void MainMethod()
{
    foreach (string str in ListContainingAddresses)
    { ... }
    lock (SyncRoot) { sentPings++; }
    ....
}

public void pingCompleted(object sender, PingCompletedEventArgs e)
{
    //This counts recieved addresses 
    lock (SyncRoot) { recievedIpAddresses++; } // lock this if it is used on other threads

    if (e.Reply.Status == IPStatus.Success)
    {
        //Do something
    }
    else
    {
        /*Computer is down*/
    }
    lock (SyncRoot) { // lock this to ensure reading the right value of sentPings
        //This checks if sent equals recieved
        if (recievedIpAddresses == sentPings )
        {
            //All returned
        }
    }
}

The above sample will force reads and writes from shared memory so that different CPU cores aren't reading different values. However, depending on your code you may need much more coarse grained locking, where the first loop protects both sentPings and pingCounter in one lock, and maybe even the second method is completely protected with a lock.

People can say not to use lock as it causes performance issues, and lock free is very trendy. The bottom line is lock is simpler than other alternatives most of the time. You may need to make your locking more coarse grained than the above sample because you potentially have race conditions as well. It's hard to give a better sample without seeing your entire program.

Interlocked.Increment

The main reason to use lock here is to force each read and write to come from memory, not CPU cache, and therefore you should get consistent values. An alternative to lock is to use Interlocked.Increment, but if you use that on two separate variables you need to watch carefully for race conditions.

Race conditions

(Edit)

Even if you lock you potentially have a problem. Watch this timeline for 13 target addresses (unlucky for some). If you aren't comfortable with why this is, then have a look at "Managed Threading Basics" and "Threading in C# - Joseph Albahari"

  • T1: 1 time
    • T1: Ping sent
    • T1: sentPings++
  • T2: 1 time
    • recievedIpAddresses++;
    • T2: other stuff
  • meanwhile T1: 12 times
    • T1: Ping sent
    • T1: sentPings++ (now equal to 13)
  • T2: recievedIpAddresses == sentPings test - now fails as they are not equal
  • T3 through to T14: enters pingCompleted and does recievedIpAddresses++;
  • T1 finishes, application gets to writing out the ping counts (or worse still exits entirely) before the other 12 threads have returned in the background

You need to watch carefully for this type of race condition in your code, and adjust it accordingly. The whole thing about threads is that they overlap their operations.

SyncRoot

Footnote:

Why is SyncRoot declared as: private readonly object SyncRoot = new object();?

  • It is a class field to protect class fields, if you have a static console app, it will need to be static. But, if you use static in a class, then every instance will lock on the same object, so there will be contention
  • It is readonly to declare intent, and prevent you (or other team members) overwriting it later
  • It is an object as:
    • you don't need anything other than an object
    • you can't lock on a value type
    • you shouldn't lock your class instance (to prevent deadlocks in more complicated code)
    • you shouldn't make it public (also to prevent deadlocks)
  • It is instantiated along with the class (in a thread safe manner) by this statement
  • It is called SyncRoot as an example; Visual Studio has historically called it that in it's snippets
like image 181
Andy Brown Avatar answered Sep 23 '22 22:09

Andy Brown