1

I have client-server application developed in C#.NET 3.5. This application uses 3 programs to complete the required work. Process A(Server) and Process B(remote launcher) are developed in .NET and Process C is a third party console application whose development language is not known. Using the Administrative credentials of remote computer, Process A copies Process B on that and schedules Process B as task on remote computer. After this, Process B starts by task scheduler and it creates log.txt file to log the messages. Then Process B starts Process C using Process.Start() semantics and redirects its standard output and error to write into log.txt file. Process A uses Process.GetProcesses(remotecomputername) semantics to monitor whether Process C is still running on remote computer. Process A is also reading the log.txt file using network share read like \\RemoteComputerName\C$\RemoteDir\log.txt and displaying the messages on its window.

My issue is, all the output and error are not getting logged on log.txt. And Process A is not able to read correctly from log.txt. If the output/error logged using DebugView they are getting logged correctly.Is this synchronization/access rights issue? How to get rid of it? Any pointers/hints will be truly valuable. Unable to share full code due to restrictions.

Sample code given below

Process A

//Below method is called every 2 seconds from Server to read new messages.
//path passed as `\\RemoteComputerName\C$\RemoteDir\log.txt`
private void ReadRemoteLog(string path)
{
    try
    {
        string[] lines = File.ReadAllLines(path);
        while (RemoteLogPosition < lines.LongLength)
        {
            string msg = lines[RemoteLogPosition].Trim();
            if (!string.IsNullOrEmpty(msg))
            {
                Trace.WriteLine("# " +msg); //Writing on DebugView
                OnProgressChanged(msg);
            }
            RemoteLogPosition++; //This is global variable to keep track of last read position.
        }      
    }   
}

Process B's code for starting Process C

ProcessStartInfo ps = new ProcessStartInfo();
ps.UseShellExecute = false;
ps.FileName = <Path to process C>;
ps.Arguments = <Commandline args to Process C>;
ps.WorkingDirectory = @"C:\RemoteDir";
ps.RedirectStandardError = true;
ps.RedirectStandardOutput = true;

Process p = new Process();
p.StartInfo = ps;

p.OutputDataReceived += (s, e) => { WriteLog(e.Data.Trim());};
p.ErrorDataReceived += (s, e) => { WriteLog(e.Data.Trim()); };
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
WriteLog("Process Started - "+ps.FileName + ps.Arguments);
p.WaitForExit();

Process B's WriteLog Method -

private void WriteLog(string message)
{
    using (FileStream fs = new FileStream(@"C:\\RemoteDir\log.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Inheritable))
    using(StreamWriter sw = new StreamWriter(fs))
    {
        sw.WriteLine("#" + message);
    }
}
Pvria Ansari
  • 406
  • 4
  • 20
Omkar
  • 2,129
  • 8
  • 33
  • 59
  • Do you try in any point to start a non-elevated process from an elevated one? – anefeletos Sep 10 '18 at 18:04
  • When you say _My issue is, all the output and error are not getting logged on log.txt._ do you mean only some of them get logged, but not all? I did have this issue once where the output was so much the file would be locked from the previous write so that part of the logs went missing. I have had success with `public static void Log(string message) { using (StreamWriter w = File.AppendText("logfiles.txt")) { w.WriteLine(message); } }` – AsheraH Sep 12 '18 at 08:33
  • @AsheraH - You are correct. Some logging statements are missing. Earlier I tried your solution but did not worked as expected. I will have to try it one more time. Will update results soon. – Omkar Sep 13 '18 at 10:01

3 Answers3

1

Remember File get exclusive lock when its getting written. So if two threads are writing at same time one of them will get into exception. Reading is not a problem. The easy solution is make multiple log file or use database. The other way is keep accumulating message and write once in an hour or when message are up to 100 . Make list of string

List<string> messagelst= new List<string>();
private void WriteLog(string message)
{


        messagelst.Add("New York");
        messagelst.Add("Mumbai");
        messagelst.Add("Berlin");
        messagelst.Add("Istanbul");
if(messagelst.length==100){
string message= string.Join(",", messagelst.ToArray());

    using (FileStream fs = new FileStream(@"C:\\RemoteDir\log.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Inheritable))
    using(StreamWriter sw = new StreamWriter(fs))
    {
        sw.WriteLine("#" + message);
    }

messagelst= new List<string>();

}
}

The other way is write async. You may loose sequence .

static Task ProcessWrite()
{
    string filePath = @"c:\temp2\temp2.txt";
    string text = "Hello World\r\n";

    return WriteTextAsync(filePath, text);
}

static async Task WriteTextAsync(string filePath, string text)
{
    byte[] encodedText = Encoding.Unicode.GetBytes(text);

    using (FileStream sourceStream = new FileStream(filePath,
        FileMode.Append, FileAccess.Write, FileShare.None,
        bufferSize: 4096, useAsync: true))
    {
        await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
    };
}
Jin Thakur
  • 2,711
  • 18
  • 15
1

Short answer: File.ReadAllLines will not let the other process write to the file. Related SO question which has some workarounds: File.ReadLines without locking it?

The fact that ReadAllLines is opening the file with FileShare.Read can be found by looking at the .Net reference source code: https://referencesource.microsoft.com/#mscorlib/system/io/file.cs,5150c565446cd5fd

By tracing the calls, it would eventually reach:

Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost);

The FileShare.Read says

Allows subsequent opening of the file for reading. If this flag is not specified, any request to open the file for reading (by this process or another process) will fail until the file is closed.

Even though ReadAllLines will close the file, during the process of reading, the file is not available for other process to write to.

You essentially, need to open the file with FileShare.ReadWrite (in your Process A) to allow the other process (your Process C) to continue to write this file.

Subbu
  • 2,130
  • 1
  • 19
  • 28
0

You can try using an object as a lock while writing to file:

private static object fileLock = new Object();

Now in your WriteLog Method: you can use lock(fileLock) {} use using to dispose off the stream.

Also you can have a look at EventWaitHandle. It allows to create an instance of wait handle for every process.

var wait = new EventWaitHandle(true, EventResetMode.AutoReset, "Common_to_all_process");

Then after first process has finished, it can pass it on to the next process by signalling:

wait.WaitOne();
/* write to the file*/
wait.Set();

One can use a GUID for “Common_to_all_process”.

Gauravsa
  • 6,330
  • 2
  • 21
  • 30