1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00
Files
winscp/dotnet/internal/SessionLogReader.cs
Martin Prikryl 01f7c61d51 Bug 1683: Failure when .NET assembly timeouts waiting for WinSCP process
https://winscp.net/tracker/1683

Source commit: 481861f7a70e83540accc30718eb99ae81bf8442
2018-09-28 01:13:20 +02:00

265 lines
8.7 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Text;
using System.Threading;
namespace WinSCP
{
internal class SessionLogReader : CustomLogReader
{
public SessionLogReader(Session session) :
base(session)
{
_position = 0;
}
public void SetTimeouted()
{
_timeouted = true;
}
public override void Dispose()
{
using (Session.Logger.CreateCallstack())
{
LogContents();
Cleanup();
}
base.Dispose();
}
private void Cleanup()
{
if (_stream != null)
{
Session.Logger.WriteLine("Closing log");
_stream.Dispose();
_stream = null;
}
if (_reader != null)
{
((IDisposable)_reader).Dispose();
_reader = null;
}
}
public override bool Read(LogReadFlags flags)
{
using (Session.Logger.CreateCallstack())
{
bool result;
if (_timeouted)
{
Session.Logger.WriteLine("Not reading, session has timed out");
result = false;
}
else
{
bool retry;
do
{
result = DoRead();
retry = false;
if (result &&
IsNonEmptyElement("failure"))
{
SessionRemoteException e = SessionRemoteException.ReadFailure(this);
Session.RaiseFailed(e);
if ((flags & LogReadFlags.ThrowFailures) == 0)
{
retry = true;
}
else
{
throw Session.Logger.WriteException(e);
}
}
}
while (retry);
}
return result;
}
}
private bool DoRead()
{
int interval = 50;
bool result;
do
{
if (_reader == null)
{
OpenLog();
}
Debug.Assert(_reader != null);
try
{
result = _reader.Read();
if (result)
{
++_position;
Session.Logger.WriteLine("Read node {0}: {1} {2}{3}{4}",
_position, _reader.NodeType, _reader.Name,
(_reader.HasValue && !string.IsNullOrEmpty(_reader.Name) && !string.IsNullOrEmpty(_reader.Value) ? "=" : string.Empty),
_reader.Value);
Session.GotOutput();
}
else
{
Session.Logger.WriteLine("Cannot read");
if (!_closed)
{
// this should not happen as when the log is not closed,
// we should get XmlException on reaching the end
_closed = true;
Cleanup();
}
Session.CheckForTimeout();
}
}
catch (XmlException e)
{
Cleanup();
// check if the the root cause was session abort
Session.CheckForTimeout();
LogContents();
string message = "Error parsing session log file";
// This is possibly a race condition, as we may not have processed the event with the error yet
// The ExeSessionProcess loops every 100ms
Thread.Sleep(200);
string s = Session.GetErrorOutputMessage();
if (!string.IsNullOrEmpty(s))
{
message += " - " + s;
}
throw Session.Logger.WriteException(new SessionLocalException(Session, message, e));
}
if (!result && !_closed)
{
Session.Logger.WriteLine("Waiting for log update and dispatching events for {0}", interval);
Session.DispatchEvents(interval);
if (interval < 500)
{
interval *= 2;
}
}
}
while (!result && !_closed);
if (result)
{
LogContents();
}
return result;
}
private void LogContents()
{
if (Session.Logger.Logging)
{
try
{
// alterative to File.ReadAllText with write-sharing
// (note that the StreamReader disposes the Stream)
using (StreamReader reader = new StreamReader(new FileStream(Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.UTF8))
{
string contents = reader.ReadToEnd();
if ((_logged == null) || (_logged != contents))
{
Session.Logger.WriteLine("Log contents:\n{0}", contents);
_logged = contents;
}
else
{
Session.Logger.WriteLine("Log contents has not changed");
}
}
}
catch (Exception e)
{
Session.Logger.WriteLine("Error logging log contents [{0}]", e.Message);
}
}
}
private void OpenLog()
{
if (_closed)
{
throw Session.Logger.WriteException(new InvalidOperationException("Log was closed already"));
}
try
{
Session.Logger.WriteLine("Opening log without write sharing");
// First try to open file without write sharing.
// This fails, if WinSCP is still writing to the log file.
// This is done only as a way to detect that log file is not complete yet.
_stream = new PatientFileStream(Session, Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.Read);
_closed = true;
LogContents();
}
catch (IOException)
{
Session.Logger.WriteLine("Opening log with write sharing");
// If log file is still being written to, open it with write sharing
_stream = new PatientFileStream(Session, Session.XmlLogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_closed = false;
}
Session.Logger.WriteLine("Log opened");
// Allow control characters in log
var settings = new XmlReaderSettings() { CheckCharacters = false };
_reader = XmlReader.Create(_stream, settings);
int skip = _position;
Session.Logger.WriteLine("Skipping {0} nodes", skip);
while (skip > 0)
{
if (!_reader.Read())
{
throw Session.Logger.WriteException(new SessionLocalException(Session, "Read less nodes than in previous log parsing"));
}
--skip;
}
}
internal override XmlReader Reader
{
get
{
if (_reader == null)
{
throw Session.Logger.WriteException(new SessionLocalException(Session, "Reading has not commenced yet"));
}
return _reader;
}
}
private int _position;
private XmlReader _reader;
private PatientFileStream _stream;
private bool _closed;
private string _logged;
private bool _timeouted;
}
}