Quantcast
Channel: sshnet Issue Tracker Rss Feed
Viewing all articles
Browse latest Browse all 1026

Created Unassigned: [Proposed Fix] Chasing a serious Event Handle leak,... Part III [1760]

$
0
0
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 evident; with heavy use causing the system to run out of resources and crash after weeks of continuous operations.

> Using Revision 28765 - SshCommand.cs

After the circular references were resolved and the object destructors were being called by the garbage collector, (See __[Proposed Fix] Chasing a serious Event Handle leak,... Part II__ for additional background), I ran into a more synister defect. An earlier, and ineffective, attempt at explicitly calling all of the __Dispose()__ methods gave me a hunch of the problem and remedy; now with the distructors being called I had my confirmation.

Note that the __Dispose()__ method had been coded strictly according to the Microsoft C# promoted design and pattern,... But in this case, the MS experts are promulgating a system resource leaking flaw.

The setup is that the __IDisposable::Dispose()__ and the object distructor are both supposed to call a virtual __Dispose(__ _bool disposing_ __)__ protected method. __IDisposable::Dispose()__ is supposed to call it with _true_ and the object distructor is supposed to call it with _false_. Additionally, the __Dispose(__ _bool disposing_ __)__ private method is supposed to have a class instance boolean to determine if the unmanaged resources had been released. The recommended patter is:
```
public void Dispose() // This is the IDisposable::Dispose() // no parameters
{
Dispose(true); // Call the protected class method
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!this._isDisposed)
{
if (disposing)
{
// release all managed resources.
}

// release all unmanaged resources.
// Note disposing has been done.
this._isDisposed = true;
}
}

~Object()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
Dispose(false);
}
```

The problem is that this pattern only works if the protected __Dispose(__ _bool disposing_ __)__ is called with _disposing = true_ on the first call. Thus the __IDisposable::Dispose()__ must be called ahead of the object destructor. Otherwise the outer boolean is set to exclude the inner boolean from ever being checked in subsequent calls; only releasing the unmanaged resources and leaking the managed resources. Because of the Microsoft brain power and backing I had expected that the garbage collector must be forcing an __IDisposable::Dispose()__ call ahead of the object destructor. When it was found not to be the case, all of the __Dispose()__ methods had to be rewritten to avoid this broken pattern. Essentially we unfolded the nested if statements. Starting from line 518
```
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (this._isDisposed)
return;

// Dispose managed ResourceMessages.
if (this.OutputStream != null)
{
this.OutputStream.Dispose();
this.OutputStream = null;
}

// Dispose managed ResourceMessages.
if (this.ExtendedOutputStream != null)
{
this.ExtendedOutputStream.Dispose();
this.ExtendedOutputStream = null;
}

// Dispose managed ResourceMessages.
if (this._sessionErrorOccuredWaitHandle != null)
{
this._sessionErrorOccuredWaitHandle.Dispose();
this._sessionErrorOccuredWaitHandle = null;
}

// Dispose managed ResourceMessages.
if (this._channel != null)
{
this._channel.Dispose();
this._channel = null;
}

// Note disposing has been done.
this._isDisposed = true;
}
```

* The first if block was turned into a simple return if true.
* Next the second if block was unfolded (removed) in to the remaining body of the method.
* Next, in line with Part II of this Proposed Fix, the deregistration of the Session eventhandlers was moved from the __Dispose()__ method to the __EndExecute()__ method.
* Next the eventhandlers on the channel container of the command object were moved to the __EndExecute()__ method (see Part II).
* Finnally, the gating flag was set to true so that future calls would have nothing to do.

In hindsight, since the __Dispose()__ method is intended to be reentrant it would good idea to put a lock around the "test and set" of the _isDisposed_ gating flag to gaurantee a single winning call. E.g.:

```
bool alreadyDisposed;
lock(this)
{
alreadyDisposed = this._isDisposed;
if (!this._isDisposed)
this._isDisposed = true;
}

if (alreadyDisposed)
return;

// else do some disposing,...
```

Viewing all articles
Browse latest Browse all 1026

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>