mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-11 01:36:39 +00:00
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:
+1
-1
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user