I use renci.sshnet in a multi-threaded/multi-concurrent-connections environment. Under heavy testing we were seeing a leakage of around 1,500 event handles a minute. Under normal use, the leak was still eveident; with heavy use causing the system to run out of resources and crash after weeks of continuous operations.
Initially it was puzzling why the dispose was not being called on several of the classes creating event handles for synchronization. With the hope of clarity I will divide the proposed code changes out, file by file, discussing only the fix rational relevant to the file being discussed:
> Using Revision 28765 - BaseClient.cs
At first I thought this change was significant because we were taking the approach of explicity calling the __Dispose()__ method as objects went out of scope. This turned out to not be the main cause of the handle leak, and most of our changes to explicitly call the __Dispose()__ method were retracted. Though an optional and minor change, I left this one in because the session object's life span is longer, warrenting an explicit call to __Dispose()__. Please consider the following code block (lines 93 through 120):
```
public BaseClient(ConnectionInfo connectionInfo)
{
if (connectionInfo == null)
throw new ArgumentNullException("connectionInfo");
this.ConnectionInfo = connectionInfo;
this.Session = new Session(connectionInfo);
//! The above session object is immediately replaced upon Connect()
}
/// <summary>
/// Connects client to the server.
/// </summary>
public void Connect()
{
this.OnConnecting();
if (this.IsConnected)
{
this.Session.Disconnect();
}
//! Session object created in the constructor is abandoned
//! Cleanup session resources prior to replacement.
if (this.Session != null)
this.Session.Dispose();
this.Session = new Session(this.ConnectionInfo);
this.Session.HostKeyReceived += Session_HostKeyReceived;
this.Session.ErrorOccured += Session_ErrorOccured;
this.Session.Connect();
this.OnConnected();
}
```
Note, the more agressive fix would be to either keep the session object instantiated from the constructor or wait to instantiate the session object from the __Connect()__ method. I chose another option to simply call the __Session.Dispose()__ method prior to the Session object's replacement in order to return system resources more quickly. This is the only benefit, as it was found that the event handle leak was not related to this double construction.
Comments: ** Comment from web user: drieseng **
Most, if not all, event handler leaks should be fixed now.
I agree that we should dispose the previous (broken) session, before creating a new session.
I added the following TODO to our Session class:
// TODO (see issue #1758):
// we're not stopping the keep-alive timer and disposing the session here
//
// we could do this but there would still be side effects as concrete
// implementations may still hang on to the original session
//
// therefore it would be better to actually invoke the Disconnect method
// (and then the Dispose on the session) but even that would have side effects
// eg. it would remove all forwarded ports from SshClient
//
// I think we should modify our concrete clients to better deal with a
// disconnect. In case of SshClient this would mean not removing the
// forwarded ports on disconnect (but only on dispose ?) and link a
// forwarded port with a client instead of with a session
//
// To be discussed with Oleg (or whoever is interested)
As mentioned, I'd like to have a discussion on this change first.