views:

1061

answers:

3

I wrote a quick and dirty wrapper around svn.exe to retrieve some content and do something with it, but for certain inputs it occasionally and reproducibly hangs and won't finish. For example, one call is to svn list:

svn list "http://myserver:84/svn/Documents/Instruments/" --xml  --no-auth-cache --username myuser --password mypassword

This command line runs fine when I just do it from a command shell, but it hangs in my app. My c# code to run this is:

string cmd = "svn.exe";
string arguments = "list \"http://myserver:84/svn/Documents/Instruments/\" --xml  --no-auth-cache --username myuser --password mypassword";
int ms = 5000;
ProcessStartInfo psi = new ProcessStartInfo(cmd);
psi.Arguments = arguments;
psi.RedirectStandardOutput = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
StreamReader output = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8);

proc.WaitForExit(ms);
if (proc.HasExited)
{
    return output.ReadToEnd();
}

This takes the full 5000 ms and never finishes. Extending the time doesn't help. In a separate command prompt, it runs instantly, so I'm pretty sure it's unrelated to an insufficient waiting time. For other inputs, however, this seems to work fine.

I also tried running a separate cmd.exe here (where exe is svn.exe and args is the original arg string), but the hang still occurred:

string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";

What could I be screwing up here, and how can I debug this external process stuff?

EDIT:

I'm just now getting around to addressing this. Mucho thanks to Jon Skeet for his suggestion, which indeed works great. I have another question about my method of handling this, though, since I'm a multi-threaded novice. I'd like suggestions on improving any glaring deficiencies or anything otherwise dumb. I ended up creating a small class that contains the stdout stream, a StringBuilder to hold the output, and a flag to tell when it's finished. Then I used ThreadPool.QueueUserWorkItem and passed in an instance of my class:

ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
                                                                          Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);

proc.WaitForExit(ms);
if (proc.HasExited)
{
    bufferHandler.Stop();
    return bufferHandler.ReadToEnd();
}

... and ...

private class ProcessBufferHandler
{
    public Stream stream;
    public StringBuilder sb;
    public Encoding encoding;
    public State state;

    public enum State
    {
        Running,
        Stopped
    }

    public ProcessBufferHandler(Stream stream, Encoding encoding)
    {
        this.stream = stream;
        this.sb = new StringBuilder();
        this.encoding = encoding;
        state = State.Running;
    }
    public void ProcessBuffer()
    {
        sb.Append(new StreamReader(stream, encoding).ReadToEnd());
    }

    public string ReadToEnd()
    {
        return sb.ToString();
    }

    public void Stop()
    {
        state = State.Stopped;
    }
}

This seems to work, but I'm doubtful that this is the best way. Is this reasonable? And what can I do to improve it?

+3  A: 

One standard issue: the process could be waiting for you to read its output. Create a separate thread to read from its standard output while you're waiting for it to exit. It's a bit of a pain, but that may well be the problem.

Jon Skeet
Yeah, that's it. There's only a 2k output buffer, it will hang when you don't empty it. BeginOutputReadLine + BeginErrorReadLine solves it.
Hans Passant
A: 

I know my SVN repos can run slow sometimes, so maybe 5 seconds isn't long enough? Have you copied the string you are passing to the process from a break point so you are positive it's not prompting you for anything?

scottm
Yeah, I can run the exact command without prompts from the command line. This call returns in well under a second from teh command line, and I have tried waiting up to 30 seconds in my app with no love, so it sure seems to be AWOL.
Chris Farmer
Have you tried using the defualtsettings for the StreamReader, ie StreamReader sr = new StreamReader(proc.StandardOutput);
scottm
Yeah, I can't do that in general because I need the specific UTF-8 encoding for the xml, but in specific cases where the default encoding works, there was no change.
Chris Farmer
A: 

Jon Skeet is right on the money!

If you don't mind polling after you launch your svn command try this:

Process command = new Process();
command.EnableRaisingEvents = false;
command.StartInfo.FileName = "svn.exe";
command.StartInfo.Arguments = "your svn arguments here";
command.StartInfo.UseShellExecute = false;
command.StartInfo.RedirectStandardOutput = true;
command.Start();

while (!command.StandardOutput.EndOfStream)
{
    Console.WriteLine(command.StandardOutput.ReadLine());
}
Justin Tanner