From 6cd224a0e40c608ee4b41bc653ed72efbf2eae63 Mon Sep 17 00:00:00 2001 From: olegkap_cp Date: Thu, 11 Nov 2010 18:31:26 +0000 Subject: [PATCH] Fix max open channels for session type channel Fix some CA warnings --- .../Renci.SshClient/Channels/Channel.cs | 19 ++++ .../Channels/ChannelDirectTcpip.cs | 2 +- .../Channels/ChannelSession.cs | 94 +++++++++---------- .../Channels/ChannelSessionExec.cs | 63 +++++++++++-- .../Renci.SshClient/ForwardedPortRemote.cs | 44 ++++++++- Renci.SshClient/Renci.SshClient/Session.cs | 34 +++++++ Renci.SshClient/Renci.SshClient/SshClient.cs | 57 +++++++++-- Renci.SshClient/Renci.SshClient/SshCommand.cs | 1 - 8 files changed, 246 insertions(+), 68 deletions(-) diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs index 36fd0abc..8929451f 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs @@ -41,6 +41,10 @@ namespace Renci.SshClient.Channels get { return this._session.IsConnected; } } + /// + /// Gets the connection info. + /// + /// The connection info. protected ConnectionInfo ConnectionInfo { get @@ -49,6 +53,21 @@ namespace Renci.SshClient.Channels } } + /// + /// Gets the session semaphore to control number of session channels + /// + /// The session semaphore. + protected SemaphoreSlim SessionSemaphore + { + get + { + return this._session.SessionSemaphore; + } + } + + /// + /// Initializes a new instance of the class. + /// internal Channel() { } diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelDirectTcpip.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelDirectTcpip.cs index 53d6a5d2..1c602d70 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelDirectTcpip.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelDirectTcpip.cs @@ -103,7 +103,7 @@ namespace Renci.SshClient.Channels } } } - catch (Exception exp) + catch (Exception) { readerTaskError.Set(); throw; diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs index 8e35dff8..c882a4db 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs @@ -7,34 +7,19 @@ namespace Renci.SshClient.Channels { internal abstract class ChannelSession : Channel { - // TODO: Some debug information to be removed later - private static volatile int _totalOpenRequests; - private static volatile int _totalConfirmation = 0; - private static volatile int _totalClose = 0; - private static volatile int _totalFailed = 0; - - private volatile static int _channelSessionCounter = 0; - - private static object _lock = new object(); - /// /// Counts faile channel open attempts /// private int _failedOpenAttempts; + /// + /// Wait handle to signal when response was received to open the channel + /// private EventWaitHandle _channelOpenResponseWaitHandle = new AutoResetEvent(false); - public bool CanCreateChannel - { - get - { - if (ChannelSession._channelSessionCounter < 10) - return true; - else - return false; - } - } - + /// + /// Opens the channel + /// public virtual void Open() { if (!this.IsOpen) @@ -53,46 +38,58 @@ namespace Renci.SshClient.Channels } } + /// + /// Called when chanel is open + /// + /// The remote channel number. + /// Initial size of the window. + /// Maximum size of the packet. protected override void OnOpenConfirmation(uint remoteChannelNumber, uint initialWindowSize, uint maximumPacketSize) { base.OnOpenConfirmation(remoteChannelNumber, initialWindowSize, maximumPacketSize); - ChannelSession._channelSessionCounter++; - Debug.WriteLine(string.Format("channel {0} open. open channels {1}", this.RemoteChannelNumber, ChannelSession._channelSessionCounter)); - - _totalConfirmation++; + Debug.WriteLine(string.Format("channel {0} open.", this.RemoteChannelNumber)); this._channelOpenResponseWaitHandle.Set(); } + /// + /// Called when channel is closed + /// protected override void OnClose() { base.OnClose(); - ChannelSession._channelSessionCounter--; + Debug.WriteLine(string.Format("channel {0} closed", this.RemoteChannelNumber)); - Debug.WriteLine(string.Format("channel {0} closed. open channels {1}", this.RemoteChannelNumber, ChannelSession._channelSessionCounter)); - _totalClose++; + // This timeout needed since when channel is closed it does not immidiatly becomes availble + // but it takes time for the server to clean up resource and allow new channels to be created. + Thread.Sleep(100); - _slim.Release(); + this.SessionSemaphore.Release(); } + /// + /// Called when channel failed to open + /// + /// The reason code. + /// The description. + /// The language. protected override void OnOpenFailure(uint reasonCode, string description, string language) { - // TODO: See why occasionaly open channel will fail when try to utilze maximum number of channels - this._failedOpenAttempts++; - Debug.WriteLine(string.Format("Local channel: {0} attempts: {1} max channels: {2}", this.LocalChannelNumber, this._failedOpenAttempts, ChannelSession._channelSessionCounter)); + Debug.WriteLine(string.Format("Local channel: {0} attempts: {1}.", this.LocalChannelNumber, this._failedOpenAttempts)); - _totalFailed++; - - _slim.Release(); + this.SessionSemaphore.Release(); this._channelOpenResponseWaitHandle.Set(); } + /// + /// Called when object is being disposed. + /// protected override void OnDisposing() { if (this._channelOpenResponseWaitHandle != null) @@ -101,23 +98,24 @@ namespace Renci.SshClient.Channels } } - private static SemaphoreSlim _slim = new SemaphoreSlim(10); - + /// + /// Sends the channel open message. + /// protected void SendChannelOpenMessage() { - _slim.Wait(); - - Debug.WriteLine(string.Format("send open new channel, total channels {0}", ChannelSession._channelSessionCounter)); - - this.SendMessage(new ChannelOpenMessage + lock (this.SessionSemaphore) { - ChannelType = ChannelTypes.Session, - LocalChannelNumber = this.LocalChannelNumber, - InitialWindowSize = this.LocalWindowSize, - MaximumPacketSize = this.PacketSize, - }); + // Ensure that channels are available + this.SessionSemaphore.Wait(); - _totalOpenRequests++; + this.SendMessage(new ChannelOpenMessage + { + ChannelType = ChannelTypes.Session, + LocalChannelNumber = this.LocalChannelNumber, + InitialWindowSize = this.LocalWindowSize, + MaximumPacketSize = this.PacketSize, + }); + } } } } diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs index 0afb36f5..37989892 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSessionExec.cs @@ -8,12 +8,16 @@ namespace Renci.SshClient.Channels { internal class ChannelSessionExec : ChannelSession { + /// + /// Holds channel data stream + /// private Stream _channelData; + /// + /// Holds channel extended data stream + /// private Stream _channelExtendedData; - private Exception _exception; - private ChannelAsyncResult _asyncResult; private AsyncCallback _callback; @@ -23,16 +27,36 @@ namespace Renci.SshClient.Channels get { return ChannelTypes.Session; } } + /// + /// Gets or sets a value indicating whether this channel has error. + /// + /// true if this instance has error; otherwise, false. public bool HasError { get; set; } + /// + /// Gets or sets the exit status. + /// + /// The exit status. public uint ExitStatus { get; private set; } + /// + /// Initializes a new instance of the class. + /// public ChannelSessionExec() : base() { } + /// + /// Begins the execute. + /// + /// The command. + /// The output. + /// The extended output. + /// The callback. + /// The state. + /// internal ChannelAsyncResult BeginExecute(string command, Stream output, Stream extendedOutput, AsyncCallback callback, object state) { // Prevent from executing BeginExecute before calling EndExecute @@ -67,6 +91,10 @@ namespace Renci.SshClient.Channels return _asyncResult; } + /// + /// Ends the execute. + /// + /// The result. internal void EndExecute(IAsyncResult result) { ChannelAsyncResult channelAsyncResult = result as ChannelAsyncResult; @@ -82,15 +110,11 @@ namespace Renci.SshClient.Channels this.Close(); this._asyncResult = null; - - if (this._exception != null) - { - var exception = this._exception; - this._exception = null; // Clean exception - throw exception; - } } + /// + /// Called when channel is closed + /// protected override void OnClose() { base.OnClose(); @@ -114,6 +138,10 @@ namespace Renci.SshClient.Channels ((EventWaitHandle)_asyncResult.AsyncWaitHandle).Set(); } + /// + /// Called when channel receives data. + /// + /// The data. protected override void OnData(string data) { base.OnData(data); @@ -132,6 +160,11 @@ namespace Renci.SshClient.Channels } } + /// + /// Called when channel receives extended data. + /// + /// The data. + /// The data type code. protected override void OnExtendedData(string data, uint dataTypeCode) { base.OnExtendedData(data, dataTypeCode); @@ -150,6 +183,14 @@ namespace Renci.SshClient.Channels } } + /// + /// Called when channel request command is called. + /// + /// Name of the request. + /// if set to true then need to send reply to server. + /// The command. + /// Name of the subsystem. + /// The exit status. protected override void OnRequest(ChannelRequestNames requestName, bool wantReply, string command, string subsystemName, uint exitStatus) { base.OnRequest(requestName, wantReply, command, subsystemName, exitStatus); @@ -183,7 +224,9 @@ namespace Renci.SshClient.Channels } } - + /// + /// Called when object is being disposed. + /// protected override void OnDisposing() { } diff --git a/Renci.SshClient/Renci.SshClient/ForwardedPortRemote.cs b/Renci.SshClient/Renci.SshClient/ForwardedPortRemote.cs index ca94f6a2..f300c3d8 100644 --- a/Renci.SshClient/Renci.SshClient/ForwardedPortRemote.cs +++ b/Renci.SshClient/Renci.SshClient/ForwardedPortRemote.cs @@ -6,7 +6,7 @@ using Renci.SshClient.Channels; using Renci.SshClient.Messages.Connection; namespace Renci.SshClient { - public class ForwardedPortRemote : ForwardedPort + public class ForwardedPortRemote : ForwardedPort, IDisposable { private bool _requestStatus; @@ -96,5 +96,47 @@ namespace Renci.SshClient this._globalRequestResponse.Set(); } + + #region IDisposable Members + + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + // Dispose managed resources. + if (this._globalRequestResponse != null) + { + this._globalRequestResponse.Dispose(); + } + } + + // Note disposing has been done. + disposed = true; + } + } + + ~ForwardedPortRemote() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + Dispose(false); + } + + #endregion } } diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs index 6bea6f95..b641745e 100644 --- a/Renci.SshClient/Renci.SshClient/Session.cs +++ b/Renci.SshClient/Renci.SshClient/Session.cs @@ -93,6 +93,35 @@ namespace Renci.SshClient /// private DisconnectMessage _disconnectMessage; + /// + /// Hold session specific semaphores + /// + private List _semaphores = new List(); + + private SemaphoreSlim _sessionSemaphore; + /// + /// Gets the session semaphore that controls session channels. + /// + /// The session semaphore. + public SemaphoreSlim SessionSemaphore + { + get + { + if (this._sessionSemaphore == null) + { + lock (this) + { + if (this._sessionSemaphore == null) + { + this._sessionSemaphore = new SemaphoreSlim(this.ConnectionInfo.MaxSessions); + } + } + } + + return this._sessionSemaphore; + } + } + private uint _nextChannelNumber; /// /// Gets the next channel number. @@ -1140,6 +1169,11 @@ namespace Renci.SshClient { this._keyExhcange.Dispose(); } + + if (this._sessionSemaphore != null) + { + this._sessionSemaphore.Dispose(); + } } // Note disposing has been done. diff --git a/Renci.SshClient/Renci.SshClient/SshClient.cs b/Renci.SshClient/Renci.SshClient/SshClient.cs index 7bcdbdbe..71c463ff 100644 --- a/Renci.SshClient/Renci.SshClient/SshClient.cs +++ b/Renci.SshClient/Renci.SshClient/SshClient.cs @@ -1,15 +1,16 @@ - +using System; using System.Collections.Generic; + namespace Renci.SshClient { - public class SshClient + public class SshClient : IDisposable { private Session _session; - private ConnectionInfo _connectionInfo; - private List _forwardedPorts = new List(); + public ConnectionInfo ConnectionInfo { get; private set; } + private Sftp _sftp; /// /// Gets the shell. @@ -37,8 +38,8 @@ namespace Renci.SshClient public SshClient(ConnectionInfo connectionInfo) { - this._connectionInfo = connectionInfo; - this._session = new Session(this._connectionInfo); + this.ConnectionInfo = connectionInfo; + this._session = new Session(connectionInfo); } public SshClient(string host, int port, string username, string password) @@ -85,7 +86,7 @@ namespace Renci.SshClient public void Connect() { - this._session = new Session(this._connectionInfo); + this._session = new Session(this.ConnectionInfo); this._session.Connect(); } @@ -131,5 +132,47 @@ namespace Renci.SshClient { this._forwardedPorts.Remove(port); } + + #region IDisposable Members + + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!this.disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + // Dispose managed resources. + if (this._session != null) + { + this._session.Dispose(); + } + } + + // Note disposing has been done. + disposed = true; + } + } + + ~SshClient() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + Dispose(false); + } + + #endregion } } diff --git a/Renci.SshClient/Renci.SshClient/SshCommand.cs b/Renci.SshClient/Renci.SshClient/SshCommand.cs index 687facd3..90862821 100644 --- a/Renci.SshClient/Renci.SshClient/SshCommand.cs +++ b/Renci.SshClient/Renci.SshClient/SshCommand.cs @@ -107,7 +107,6 @@ namespace Renci.SshClient return this.Execute(); } - #region IDisposable Members private bool disposed = false;