Improve internal server window size managment

Fix exception handeling when multiple threads share one channel that can caus application hanging
Improve cipher perfomance
Other fixes
This commit is contained in:
olegkap_cp
2013-02-07 19:47:47 +00:00
parent 759aa4c9ee
commit 8934b5d156
10 changed files with 127 additions and 61 deletions
@@ -29,7 +29,7 @@ namespace Renci.SshNet.Tests.Classes.Messages.Connection
public void ChannelExtendedDataMessageConstructorTest1()
{
uint localChannelNumber = 0; // TODO: Initialize to an appropriate value
ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber);
//ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber, null, null);
Assert.Inconclusive("TODO: Implement code to verify target");
}
}
@@ -49,6 +49,7 @@ namespace Renci.SshNet.Tests.Classes
client.HostKeyReceived += delegate(object sender, HostKeyEventArgs e)
{
hostKeyValidated = true;
if (e.FingerPrint.SequenceEqual(new byte[] { 0x00, 0x01, 0x02, 0x03 }))
{
e.CanTrust = true;
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading;
using Renci.SshNet.Common;
using Renci.SshNet.Messages;
@@ -12,14 +13,16 @@ namespace Renci.SshNet.Channels
/// </summary>
internal abstract class Channel : IDisposable
{
private EventWaitHandle _channelClosedWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
private EventWaitHandle _channelWindowAdjustWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _channelServerWindowAdjustWaitHandle = new ManualResetEvent(false);
private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
private EventWaitHandle _disconnectedWaitHandle = new ManualResetEvent(false);
private object _serverWindowSizeLock = new object();
private bool _closeMessageSent = false;
private uint _initialWindowSize = 0x100000;
@@ -266,7 +269,11 @@ namespace Renci.SshNet.Channels
/// <param name="bytesToAdd">The bytes to add.</param>
protected virtual void OnWindowAdjust(uint bytesToAdd)
{
this.ServerWindowSize += bytesToAdd;
lock (this._serverWindowSizeLock)
{
this.ServerWindowSize += bytesToAdd;
}
this._channelServerWindowAdjustWaitHandle.Set();
}
/// <summary>
@@ -410,20 +417,36 @@ namespace Renci.SshNet.Channels
/// Sends channel data message to the servers.
/// </summary>
/// <remarks>This method takes care of managing the window size.</remarks>
/// <param name="message">Channel data message.</param>
/// <param name="message">Channel data message.</param>
protected void SendMessage(ChannelDataMessage message)
{
// Send channel messages only while channel is open
if (!this.IsOpen)
return;
if (this.ServerWindowSize < 1)
var messageLength = message.Data.Length;
do
{
// Wait for window to be adjust
this._session.WaitHandle(this._channelWindowAdjustWaitHandle);
}
lock (this._serverWindowSizeLock)
{
var serverWindowSize = this.ServerWindowSize;
if (serverWindowSize < messageLength)
{
// Wait for window to be big enough for this message
this._channelServerWindowAdjustWaitHandle.Reset();
}
else
{
this.ServerWindowSize -= (uint)messageLength;
break;
}
}
// Wait for window to change
this.WaitHandle(this._channelServerWindowAdjustWaitHandle);
} while (true);
this.ServerWindowSize -= (uint)message.Data.Length;
this._session.SendMessage(message);
}
@@ -438,13 +461,29 @@ namespace Renci.SshNet.Channels
if (!this.IsOpen)
return;
if (this.ServerWindowSize < 1)
var messageLength = message.Data.Length;
do
{
// Wait for window to be adjust
this._session.WaitHandle(this._channelWindowAdjustWaitHandle);
}
lock (this._serverWindowSizeLock)
{
var serverWindowSize = this.ServerWindowSize;
if (serverWindowSize < messageLength)
{
// Wait for window to be big enough for this message
this._channelServerWindowAdjustWaitHandle.Reset();
}
else
{
this.ServerWindowSize -= (uint)messageLength;
break;
}
}
// Wait for window to change
this.WaitHandle(this._channelServerWindowAdjustWaitHandle);
} while (true);
this.ServerWindowSize -= (uint)message.Data.Length;
this._session.SendMessage(message);
}
@@ -496,7 +535,6 @@ namespace Renci.SshNet.Channels
}
}
private void Session_Disconnected(object sender, EventArgs e)
{
// If objected is disposed or being disposed don't handle this event
@@ -546,8 +584,6 @@ namespace Renci.SshNet.Channels
if (e.Message.LocalChannelNumber == this.LocalChannelNumber)
{
this.OnWindowAdjust(e.Message.BytesToAdd);
this._channelWindowAdjustWaitHandle.Set();
}
}
@@ -672,10 +708,10 @@ namespace Renci.SshNet.Channels
this._channelClosedWaitHandle.Dispose();
this._channelClosedWaitHandle = null;
}
if (this._channelWindowAdjustWaitHandle != null)
if (this._channelServerWindowAdjustWaitHandle != null)
{
this._channelWindowAdjustWaitHandle.Dispose();
this._channelWindowAdjustWaitHandle = null;
this._channelServerWindowAdjustWaitHandle.Dispose();
this._channelServerWindowAdjustWaitHandle = null;
}
if (this._errorOccuredWaitHandle != null)
{
@@ -28,9 +28,11 @@
/// Initializes a new instance of the <see cref="ChannelExtendedDataMessage"/> class.
/// </summary>
/// <param name="localChannelNumber">The local channel number.</param>
public ChannelExtendedDataMessage(uint localChannelNumber)
public ChannelExtendedDataMessage(uint localChannelNumber, uint dataTypeCode, byte[] data)
{
this.LocalChannelNumber = localChannelNumber;
this.DataTypeCode = dataTypeCode;
this.Data = data;
}
/// <summary>
+2 -6
View File
@@ -91,7 +91,7 @@ namespace Renci.SshNet
/// <summary>
/// WaitHandle to signal that exception was thrown by another thread.
/// </summary>
private EventWaitHandle _exceptionWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _exceptionWaitHandle = new ManualResetEvent(false);
/// <summary>
/// WaitHandle to signal that key exchange was completed.
@@ -637,11 +637,7 @@ namespace Renci.SshNet
switch (EventWaitHandle.WaitAny(waitHandles, this.ConnectionInfo.Timeout))
{
case 0:
{
var exception = this._exception;
this._exception = null;
throw exception;
}
throw this._exception;
case System.Threading.WaitHandle.WaitTimeout:
this.SendDisconnect(DisconnectReason.ByApplication, "Operation timeout");
throw new SshOperationTimeoutException("Session operation has timed out");
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Globalization;
using Renci.SshNet.Common;
namespace Renci.SshNet.Sftp
{
@@ -27,6 +28,9 @@ namespace Renci.SshNet.Sftp
/// <exception cref="ArgumentNullException"><paramref name="sftpSession"/> or <paramref name="fullName"/> is null.</exception>
internal SftpFile(SftpSession sftpSession, string fullName, SftpFileAttributes attributes)
{
if (sftpSession == null)
throw new SshConnectionException("Client not connected.");
if (attributes == null)
throw new ArgumentNullException("attributes");
@@ -209,6 +209,9 @@ namespace Renci.SshNet.Sftp
internal SftpFileStream(SftpSession session, string path, FileMode mode, FileAccess access, int bufferSize, bool useAsync)
{
// Validate the parameters.
if (session == null)
throw new SshConnectionException("Client not connected.");
if (path == null)
{
throw new ArgumentNullException("path");
+52 -20
View File
@@ -143,6 +143,9 @@ namespace Renci.SshNet
if (path == null)
throw new ArgumentNullException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
this._sftpSession.ChangeDirectory(path);
}
@@ -176,6 +179,9 @@ namespace Renci.SshNet
if (path.IsNullOrWhiteSpace())
throw new ArgumentException(path);
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
this._sftpSession.RequestMkDir(fullPath);
@@ -194,6 +200,9 @@ namespace Renci.SshNet
if (path.IsNullOrWhiteSpace())
throw new ArgumentException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
this._sftpSession.RequestRmDir(fullPath);
@@ -212,6 +221,9 @@ namespace Renci.SshNet
if (path.IsNullOrWhiteSpace())
throw new ArgumentException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
this._sftpSession.RequestRemove(fullPath);
@@ -250,6 +262,9 @@ namespace Renci.SshNet
if (newPath == null)
throw new ArgumentNullException("newPath");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var oldFullPath = this._sftpSession.GetCanonicalPath(oldPath);
var newFullPath = this._sftpSession.GetCanonicalPath(newPath);
@@ -281,6 +296,9 @@ namespace Renci.SshNet
if (linkPath.IsNullOrWhiteSpace())
throw new ArgumentException("linkPath");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
var linkFullPath = this._sftpSession.GetCanonicalPath(linkPath);
@@ -376,6 +394,9 @@ namespace Renci.SshNet
if (path == null)
throw new ArgumentNullException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
var attributes = this._sftpSession.RequestLStat(fullPath);
@@ -397,6 +418,9 @@ namespace Renci.SshNet
if (path.IsNullOrWhiteSpace())
throw new ArgumentException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetFullRemotePath(path);
if (this._sftpSession.RequestRealPath(fullPath, true) == null)
@@ -755,6 +779,9 @@ namespace Renci.SshNet
if (path == null)
throw new ArgumentNullException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
return this._sftpSession.RequestStatVfs(fullPath);
@@ -1285,6 +1312,9 @@ namespace Renci.SshNet
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <b>null</b>.</exception>
public SftpFileAttributes GetAttributes(string path)
{
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
return this._sftpSession.RequestLStat(fullPath);
@@ -1298,6 +1328,9 @@ namespace Renci.SshNet
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <b>null</b>.</exception>
public void SetAttributes(string path, SftpFileAttributes fileAttributes)
{
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
this._sftpSession.RequestSetStat(fullPath, fileAttributes);
@@ -1328,6 +1361,9 @@ namespace Renci.SshNet
if (path == null)
throw new ArgumentNullException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
var handle = this._sftpSession.RequestOpenDir(fullPath);
@@ -1380,6 +1416,9 @@ namespace Renci.SshNet
if (path.IsNullOrWhiteSpace())
throw new ArgumentException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
var handle = this._sftpSession.RequestOpen(fullPath, Flags.Read);
@@ -1434,6 +1473,9 @@ namespace Renci.SshNet
if (path.IsNullOrWhiteSpace())
throw new ArgumentException("path");
if (this._sftpSession == null)
throw new SshConnectionException("Client not connected.");
var fullPath = this._sftpSession.GetCanonicalPath(path);
var handle = this._sftpSession.RequestOpen(fullPath, flags);
@@ -1444,7 +1486,7 @@ namespace Renci.SshNet
var bytesRead = input.Read(buffer, 0, buffer.Length);
var expectedResponses = 0;
var expectedResponsesLock = new object();
var responseReceivedWaitHandle = new AutoResetEvent(false);
do
{
@@ -1467,25 +1509,18 @@ namespace Renci.SshNet
{
if (s.StatusCode == StatusCodes.Ok)
{
lock (expectedResponsesLock)
expectedResponses--;
responseReceivedWaitHandle.Set();
// Call callback to report number of bytes written
if (uploadCallback != null)
{
expectedResponses--;
// Call callback to report number of bytes written
if (uploadCallback != null)
{
// Execute callback on different thread
this.ExecuteThread(() => { uploadCallback(writtenBytes); });
}
Monitor.Pulse(expectedResponsesLock);
// Execute callback on different thread
this.ExecuteThread(() => { uploadCallback(writtenBytes); });
}
}
});
lock (expectedResponsesLock)
{
expectedResponses++;
}
expectedResponses++;
offset += (uint)bytesRead;
@@ -1494,10 +1529,7 @@ namespace Renci.SshNet
else if (expectedResponses > 0)
{
// Wait for expectedResponses to change
lock (expectedResponsesLock)
{
Monitor.Wait(expectedResponsesLock);
}
this._sftpSession.WaitHandle(responseReceivedWaitHandle, this.OperationTimeout);
}
} while (expectedResponses > 0 || bytesRead > 0);
+1 -5
View File
@@ -484,11 +484,7 @@ namespace Renci.SshNet
switch (EventWaitHandle.WaitAny(waitHandles, this.CommandTimeout))
{
case 0:
{
var exception = this._exception;
this._exception = null;
throw exception;
}
throw this._exception;
case System.Threading.WaitHandle.WaitTimeout:
throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Command '{0}' has timed out.", this.CommandText));
default:
@@ -27,9 +27,9 @@ namespace Renci.SshNet.Sftp
private Exception _exception;
private EventWaitHandle _errorOccuredWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _errorOccuredWaitHandle = new ManualResetEvent(false);
private EventWaitHandle _channelClosedWaitHandle = new AutoResetEvent(false);
private EventWaitHandle _channelClosedWaitHandle = new ManualResetEvent(false);
/// <summary>
/// Specifies a timeout to wait for operation to complete
@@ -165,11 +165,7 @@ namespace Renci.SshNet.Sftp
switch (EventWaitHandle.WaitAny(waitHandles, operationTimeout))
{
case 0:
{
var exception = this._exception;
this._exception = null;
throw exception;
}
throw this._exception;
case 1:
throw new SshException("Channel was closed.");
case System.Threading.WaitHandle.WaitTimeout:
@@ -197,7 +193,7 @@ namespace Renci.SshNet.Sftp
#region IDisposable Members
private bool _isDisposed = false;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>