This project is read-only.

Better way to do this? (The infamous tail -f)

Jun 30, 2011 at 8:02 PM
Edited Jul 5, 2011 at 4:15 PM

Hey Oleg,

Im trying to implement some form of hacky tail based off your example in the issue tracker and have had some success... but I have the feeling that there has to be another way.

Okay so I have the connection code in a seperate thread.  This thread supports cancellation requets (which will allow the the connection to gracefully exit, the only way to ensure the process unix side is killed).  Here's my code thus far:

PasswordConnectionInfo connectionInfo = new PasswordConnectionInfo(lineIP, userName, password);

string command = "cd /logs; tail -f " + BuildFileName() + " \r\n";

using (var ssh = new SshClient(connectionInfo))
{
    ssh.Connect();

    var wait = new AutoResetEvent(false);
    var output = new MemoryStream();
    var shell = ssh.CreateShell(Encoding.ASCII, command, output, output);

    shell.Start();

    long positionLastWrite = 0;

    while (!TestBackgroundWorker.CancellationPending) //checks for cancel request  
    {
        output.Position = positionLastWrite;

        var result = new StreamReader(output, Encoding.ASCII).ReadToEnd();
        positionLastWrite = output.Position;
        UpdateTextBox(result);

        Thread.Sleep(1000);
    }

    shell.Stop();
    e.Cancel = true;
}

The UpdateAP2TextBox() function is a thread-safe way of updating a textbox from a different thread. The positionLastWrite stuff is an attempt to make sure I dont loose any data in between the Thread.Sleep(1000).  Now Im not sure about 2 things, first being that I have the feeling I might be missing out on some data each time with the whole changing memorystream position thing, and the second being that the whole sleep for 1 second then update again thing seems pretty archaic and inefficent... any thoughts?

Coordinator
Jun 30, 2011 at 8:18 PM

Hi,

 

Yes, so to avoid sleep I always prefer to use WaitHandle.

This way you can signal from one thread to another when something changed and need to be done.

I can see you already created a wait variable but not using it. So just instead of Thread.Sleep just do wait.WaitOne().

And then some other place, where you read the results I assume, just signal this handler by wait.Set().

Now, another things is, since you using Shell, there suppose to be a way to send a kill signal, similar to Ctrl-C, which will tell terminal to kill this command, so I would look into that.

I have an example on how to do it in SshCommand file and should be used something like on of those choice:

 

this._channel.SendData(Encoding.ASCII.GetBytes("~."));
this._channel.SendExecRequest("\0x03");
this._channel.SendSignalRequest("ABRT");
this._channel.SendSignalRequest("INT");

 

I think I tried that but it didnt work well for me, but again, I dont think I tried it with Shell, so you might give it a shot and see if it works.

As far as reading more efficient. I have a class called PipeStream, you might want to use that.

Then you just need to create it once and not recreate it every time since once you read the information from it, it will be removed from the stream.

 

Hope it helps,

Thanks,

Oleg

Jun 30, 2011 at 8:26 PM

Thanks for the quick response! PipeStream sounds extremely useful... would you mind posting an example on how to use it when you get the chance? Thanks!

Coordinator
Jun 30, 2011 at 8:43 PM

You can use it just like any other stream,

See example in SshCommand class where I use this class.

It just something that you need to keep in mind when you work with it is then when you read something from it will be gone from the stream and cannot be read again.

 

Oleg

Jun 30, 2011 at 9:18 PM
Edited Jul 5, 2011 at 4:15 PM

Well I tried this:

using (var ssh = new SshClient(connectionInfo))
{
    ssh.Connect();
                
    var output = new PipeStream();
    var shell = ssh.CreateShell(Encoding.ASCII, command, output, output);

    shell.Start();

    while (!TestBackgroundWorker.CancellationPending) //checks for cancel request  
    {
        var result = String.Empty;

        if (output != null && output.Length > 0)
        {
            using (var sr = new StreamReader(output, Encoding.ASCII))
            {
                result = sr.ReadToEnd();
            }
        }

        UpdateTextBox(result);

        Thread.Sleep(1000);
    }

    shell.Stop();
    e.Cancel = true;
}

But nothing happens after the sr.ReadToEnd... control is returned to the main UI thread and it seems like the actual thread crashes.

EDIT: I wrapped the while loop in a try { } finally { } and after the sr.ReadToEnd() it never hits the code in the finally..... so I guess that means its just infinitaly reading since data is still coming in? I am confused haha

Jul 5, 2011 at 4:14 PM
Edited Jul 5, 2011 at 7:21 PM

Well I seem to have the PipeStream stuff figured out, just for anyone's elses edification the code I got to work is:

using (var ssh = new SshClient(connectionInfo))
{
    ssh.Connect();
                
    var output = new PipeStream();
    var shell = ssh.CreateShell(Encoding.ASCII, command, output, output);

    shell.Start();

    try
    {
        while (!TestBackgroundWorker.CancellationPending) //checks for cancel request  
        {
            byte[] tempBytes = new byte[output.Length];
            output.Read(tempBytes, 0, tempBytes.Length);

            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            string result = enc.GetString(tempBytes);

            UpdateTextBox(result);
	   // Will look into using Wait Events
            Thread.Sleep(1000);
        }
    }
    finally // Always stop shell
    {
        shell.Stop();
        // May be overkill, just making sure its killed  
        output.Close();
        output.Flush();
        output.Dispose();

        e.Cancel = true;
    }
}