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;