Переглянути джерело

Raise the Closed event as part the Close() method to:
* ensure the channel is closed at both ends before we raise this event
* ensure we raise the event before the channel is disposed

Fixes issue #319.

Gert Driesen 8 роки тому
батько
коміт
f854227c0d

+ 10 - 5
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs

@@ -21,7 +21,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private ChannelStub _channel;
         private Stopwatch _closeTimer;
-        private ManualResetEvent _channelClosedWaitHandle;
+        private ManualResetEvent _channelClosedEventHandlerCompleted;
         private List<ChannelEventArgs> _channelClosedRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
 
@@ -43,7 +43,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _closeTimer = new Stopwatch();
             _channelClosedRegister = new List<ChannelEventArgs>();
-            _channelClosedWaitHandle = new ManualResetEvent(false);
+            _channelClosedEventHandlerCompleted = new ManualResetEvent(false);
             _channelExceptionRegister = new List<ExceptionEventArgs>();
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
@@ -80,7 +80,8 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _channel.Closed += (sender, args) =>
                 {
                     _channelClosedRegister.Add(args);
-                    _channelClosedWaitHandle.Set();
+                    Thread.Sleep(50);
+                    _channelClosedEventHandlerCompleted.Set();
                 };
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
@@ -129,12 +130,16 @@ namespace Renci.SshNet.Tests.Classes.Channels
         [TestMethod]
         public void ClosedEventShouldHaveFiredOnce()
         {
-            _channelClosedWaitHandle.WaitOne(100);
-
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }
 
+        [TestMethod]
+        public void DisposeShouldBlockUntilClosedEventHandlerHasCompleted()
+        {
+            Assert.IsTrue(_channelClosedEventHandlerCompleted.WaitOne(0));
+        }
+
         [TestMethod]
         public void ExceptionShouldNeverHaveFired()
         {

+ 22 - 6
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs

@@ -10,6 +10,7 @@ using Renci.SshNet.Messages.Connection;
 namespace Renci.SshNet.Tests.Classes.Channels
 {
     [TestClass]
+    [Ignore]
     public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked
     {
         private Mock<ISession> _sessionMock;
@@ -21,7 +22,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private ChannelStub _channel;
         private Stopwatch _closeTimer;
-        private ManualResetEvent _channelClosedWaitHandle;
+        private ManualResetEvent _channelClosedEventHandlerCompleted;
         private List<ChannelEventArgs> _channelClosedRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
 
@@ -32,6 +33,16 @@ namespace Renci.SshNet.Tests.Classes.Channels
             Act();
         }
 
+        [TestCleanup]
+        public void TearDown()
+        {
+            if (_channelClosedEventHandlerCompleted != null)
+            {
+                _channelClosedEventHandlerCompleted.Dispose();
+                _channelClosedEventHandlerCompleted = null;
+            }
+        }
+
         private void Arrange()
         {
             var random = new Random();
@@ -42,7 +53,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _closeTimer = new Stopwatch();
-            _channelClosedWaitHandle = new ManualResetEvent(false);
+            _channelClosedEventHandlerCompleted = new ManualResetEvent(false);
             _channelClosedRegister = new List<ChannelEventArgs>();
             _channelExceptionRegister = new List<ExceptionEventArgs>();
 
@@ -80,12 +91,13 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _channel.Closed += (sender, args) =>
                 {
                     _channelClosedRegister.Add(args);
-                    _channelClosedWaitHandle.Set();
+                    Thread.Sleep(50);
+                    _channelClosedEventHandlerCompleted.Set();
                 };
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
-            _channel.SendEof();
+            //_channel.SendEof();
         }
 
         private void Act()
@@ -130,12 +142,16 @@ namespace Renci.SshNet.Tests.Classes.Channels
         [TestMethod]
         public void ClosedEventShouldHaveFiredOnce()
         {
-            _channelClosedWaitHandle.WaitOne(100);
-
             Assert.AreEqual(1, _channelClosedRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
         }
 
+        [TestMethod]
+        public void DisposeShouldBlockUntilClosedEventHandlerHasCompleted()
+        {
+            Assert.IsTrue(_channelClosedEventHandlerCompleted.WaitOne(0));
+        }
+
         [TestMethod]
         public void ExceptionShouldNeverHaveFired()
         {

+ 15 - 2
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived.cs

@@ -23,6 +23,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private List<ChannelEventArgs> _channelEndOfDataRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
         private ManualResetEvent _channelClosedReceived;
+        private ManualResetEvent _channelClosedEventHandlerCompleted;
         private Thread _raiseChannelCloseReceivedThread;
 
         private void SetupData()
@@ -39,6 +40,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _channelEndOfDataRegister = new List<ChannelEventArgs>();
             _channelExceptionRegister = new List<ExceptionEventArgs>();
             _channelClosedReceived = new ManualResetEvent(false);
+            _channelClosedEventHandlerCompleted = new ManualResetEvent(false);
             _raiseChannelCloseReceivedThread = null;
         }
 
@@ -106,6 +108,12 @@ namespace Renci.SshNet.Tests.Classes.Channels
                     _raiseChannelCloseReceivedThread.Abort();
                 }
             }
+
+            if (_channelClosedEventHandlerCompleted != null)
+            {
+                _channelClosedEventHandlerCompleted.Dispose();
+                _channelClosedEventHandlerCompleted = null;
+            }
         }
 
         private void Arrange()
@@ -115,7 +123,12 @@ namespace Renci.SshNet.Tests.Classes.Channels
             SetupMocks();
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    Thread.Sleep(50);
+                    _channelClosedEventHandlerCompleted.Set();
+                };
             _channel.EndOfData += (sender, args) => _channelEndOfDataRegister.Add(args);
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
@@ -173,7 +186,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         }
 
         [TestMethod]
-        public void EndOfDataEventShouldHaveFiredOnce()
+        public void EndOfDataEventShouldNotHaveFired()
         {
             Assert.AreEqual(1, _channelEndOfDataRegister.Count);
             Assert.AreEqual(_localChannelNumber, _channelEndOfDataRegister[0].ChannelNumber);

+ 141 - 0
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived_ConnectionExceptionWaitingForChannelCloseMessage.cs

@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived_ConnectionExceptionWaitingForChannelCloseMessage
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+        private List<ChannelEventArgs> _channelEndOfDataRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private SshConnectionException _connectionException;
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelEndOfDataRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _connectionException = new SshConnectionException();
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            var sequence = new MockSequence();
+
+            _sessionMock.InSequence(sequence).Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.InSequence(sequence).Setup(p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber))).Returns(true);
+            _sessionMock.InSequence(sequence).Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                .Callback<WaitHandle>(w =>
+                {
+                    throw _connectionException;
+                });
+        }
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) =>
+            {
+                _channelClosedRegister.Add(args);
+            };
+            _channel.EndOfData += (sender, args) => _channelEndOfDataRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+
+            _sessionMock.Raise(
+                s => s.ChannelEofReceived += null,
+                new MessageEventArgs<ChannelEofMessage>(new ChannelEofMessage(_localChannelNumber)));
+        }
+
+        private void Act()
+        {
+            _channel.Dispose();
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldBeInvokedOnceForChannelCloseMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldNeverBeInvokedForChannelEofMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelEofMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Never);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()), Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+
+        [TestMethod]
+        public void EndOfDataEventShouldNotHaveFired()
+        {
+            Assert.AreEqual(1, _channelEndOfDataRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelEndOfDataRegister[0].ChannelNumber);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
+    }
+}

+ 157 - 0
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived_OperationTimeoutExceptionWaitingForChannelCloseMessage.cs

@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived_OperationTimeoutExceptionWaitingForChannelCloseMessage
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+        private List<ChannelEventArgs> _channelEndOfDataRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private SshOperationTimeoutException _operationTimeoutException;
+        private SshOperationTimeoutException _actualException;
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _localChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _localWindowSize = (uint)random.Next(0, int.MaxValue);
+            _localPacketSize = (uint)random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint)random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint)random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint)random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelEndOfDataRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _operationTimeoutException = new SshOperationTimeoutException();
+            _actualException = null;
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            var sequence = new MockSequence();
+
+            _sessionMock.InSequence(sequence).Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.InSequence(sequence).Setup(p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber))).Returns(true);
+            _sessionMock.InSequence(sequence).Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                .Callback<WaitHandle>(w =>
+                {
+                    throw _operationTimeoutException;
+                });
+        }
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                };
+            _channel.EndOfData += (sender, args) => _channelEndOfDataRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+
+            _sessionMock.Raise(
+                s => s.ChannelEofReceived += null,
+                new MessageEventArgs<ChannelEofMessage>(new ChannelEofMessage(_localChannelNumber)));
+        }
+
+        private void Act()
+        {
+            try
+            {
+                _channel.Dispose();
+            }
+            catch (SshOperationTimeoutException ex)
+            {
+                _actualException = ex;
+            }
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnTrue()
+        {
+            Assert.IsTrue(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldBeInvokedOnceForChannelCloseMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldNeverBeInvokedForChannelEofMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelEofMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Never);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()), Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldNotHaveFired()
+        {
+            Assert.AreEqual(0, _channelClosedRegister.Count);
+        }
+
+        [TestMethod]
+        public void EndOfDataEventShouldNotHaveFired()
+        {
+            Assert.AreEqual(1, _channelEndOfDataRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelEndOfDataRegister[0].ChannelNumber);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void DisposeShouldHaveThrownOperationTimeoutException()
+        {
+            Assert.IsNotNull(_actualException);
+            Assert.AreSame(_operationTimeoutException, _actualException);
+        }
+    }
+}

+ 147 - 0
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_DisposeChannelInClosedEventHandler.cs

@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using Renci.SshNet.Common;
+using Renci.SshNet.Messages.Connection;
+
+namespace Renci.SshNet.Tests.Classes.Channels
+{
+    [TestClass]
+    public class ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_DisposeChannelInClosedEventHandler
+    {
+        private Mock<ISession> _sessionMock;
+        private uint _localChannelNumber;
+        private uint _localWindowSize;
+        private uint _localPacketSize;
+        private uint _remoteChannelNumber;
+        private uint _remoteWindowSize;
+        private uint _remotePacketSize;
+        private ChannelStub _channel;
+        private List<ChannelEventArgs> _channelClosedRegister;
+        private List<ChannelEventArgs> _channelEndOfDataRegister;
+        private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private ManualResetEvent _channelClosedEventHandlerCompleted;
+
+        private void SetupData()
+        {
+            var random = new Random();
+
+            _localChannelNumber = (uint) random.Next(0, int.MaxValue);
+            _localWindowSize = (uint) random.Next(0, int.MaxValue);
+            _localPacketSize = (uint) random.Next(0, int.MaxValue);
+            _remoteChannelNumber = (uint) random.Next(0, int.MaxValue);
+            _remoteWindowSize = (uint) random.Next(0, int.MaxValue);
+            _remotePacketSize = (uint) random.Next(0, int.MaxValue);
+            _channelClosedRegister = new List<ChannelEventArgs>();
+            _channelEndOfDataRegister = new List<ChannelEventArgs>();
+            _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _channelClosedEventHandlerCompleted = new ManualResetEvent(false);
+        }
+
+        private void CreateMocks()
+        {
+            _sessionMock = new Mock<ISession>(MockBehavior.Strict);
+        }
+
+        private void SetupMocks()
+        {
+            var sequence = new MockSequence();
+
+            _sessionMock.InSequence(sequence).Setup(p => p.IsConnected).Returns(true);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)))
+                        .Returns(true);
+            _sessionMock.InSequence(sequence)
+                        .Setup(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()))
+                        .Callback<WaitHandle>(w => w.WaitOne());
+        }
+
+        [TestInitialize]
+        public void Initialize()
+        {
+            Arrange();
+            Act();
+        }
+
+        private void Arrange()
+        {
+            SetupData();
+            CreateMocks();
+            SetupMocks();
+
+            _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    _channel.Dispose();
+                    _channelClosedEventHandlerCompleted.Set();
+                };
+            _channel.EndOfData += (sender, args) => _channelEndOfDataRegister.Add(args);
+            _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
+            _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
+            _channel.SetIsOpen(true);
+        }
+
+        private void Act()
+        {
+            _sessionMock.Raise(
+                s => s.ChannelCloseReceived += null,
+                new MessageEventArgs<ChannelCloseMessage>(new ChannelCloseMessage(_localChannelNumber)));
+        }
+
+        [TestMethod]
+        public void IsOpenShouldReturnFalse()
+        {
+            Assert.IsFalse(_channel.IsOpen);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldBeInvokedOnceForChannelCloseMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelCloseMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Once);
+        }
+
+        [TestMethod]
+        public void TrySendMessageOnSessionShouldNeverBeInvokedForChannelEofMessage()
+        {
+            _sessionMock.Verify(
+                p => p.TrySendMessage(It.Is<ChannelEofMessage>(c => c.LocalChannelNumber == _remoteChannelNumber)),
+                Times.Never);
+        }
+
+        [TestMethod]
+        public void WaitOnHandleOnSessionShouldBeInvokedOnce()
+        {
+            _sessionMock.Verify(p => p.WaitOnHandle(It.IsAny<EventWaitHandle>()), Times.Once);
+        }
+
+        [TestMethod]
+        public void ClosedEventShouldHaveFiredOnce()
+        {
+            Assert.AreEqual(1, _channelClosedRegister.Count);
+            Assert.AreEqual(_localChannelNumber, _channelClosedRegister[0].ChannelNumber);
+        }
+
+        [TestMethod]
+        public void EndOfDataEventShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelEndOfDataRegister.Count);
+        }
+
+        [TestMethod]
+        public void ExceptionShouldNeverHaveFired()
+        {
+            Assert.AreEqual(0, _channelExceptionRegister.Count);
+        }
+
+        [TestMethod]
+        public void ChannelCloseReceivedShouldBlockUntilClosedEventHandlerHasCompleted()
+        {
+            Assert.IsTrue(_channelClosedEventHandlerCompleted.WaitOne(0));
+        }
+    }
+}

+ 14 - 1
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofNotReceived.cs

@@ -21,6 +21,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private IList<ChannelEventArgs> _channelClosedRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private ManualResetEvent _channelClosedEventHandlerCompleted;
         private ChannelStub _channel;
 
         [TestInitialize]
@@ -41,6 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
             _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _channelClosedEventHandlerCompleted = new ManualResetEvent(false);
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
@@ -53,7 +55,12 @@ namespace Renci.SshNet.Tests.Classes.Channels
                 .Callback<WaitHandle>(w => w.WaitOne());
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    Thread.Sleep(100);
+                    _channelClosedEventHandlerCompleted.Set();
+                };
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
@@ -105,5 +112,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
         {
             Assert.AreEqual(0, _channelExceptionRegister.Count, _channelExceptionRegister.AsString());
         }
+
+        [TestMethod]
+        public void ChannelCloseReceivedShouldBlockUntilClosedEventHandlerHasCompleted()
+        {
+            Assert.IsTrue(_channelClosedEventHandlerCompleted.WaitOne(0));
+        }
     }
 }

+ 14 - 1
src/Renci.SshNet.Tests/Classes/Channels/ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofReceived.cs

@@ -21,6 +21,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
         private uint _remotePacketSize;
         private IList<ChannelEventArgs> _channelClosedRegister;
         private IList<ExceptionEventArgs> _channelExceptionRegister;
+        private ManualResetEvent _channelClosedEventHandlerCompleted;
         private ChannelStub _channel;
 
         [TestInitialize]
@@ -41,6 +42,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             _remotePacketSize = (uint)random.Next(0, int.MaxValue);
             _channelClosedRegister = new List<ChannelEventArgs>();
             _channelExceptionRegister = new List<ExceptionEventArgs>();
+            _channelClosedEventHandlerCompleted = new ManualResetEvent(false);
 
             _sessionMock = new Mock<ISession>(MockBehavior.Strict);
 
@@ -55,7 +57,12 @@ namespace Renci.SshNet.Tests.Classes.Channels
                 .Callback<WaitHandle>(w => w.WaitOne());
 
             _channel = new ChannelStub(_sessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
-            _channel.Closed += (sender, args) => _channelClosedRegister.Add(args);
+            _channel.Closed += (sender, args) =>
+                {
+                    _channelClosedRegister.Add(args);
+                    Thread.Sleep(100);
+                    _channelClosedEventHandlerCompleted.Set();
+                };
             _channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
             _channel.InitializeRemoteChannelInfo(_remoteChannelNumber, _remoteWindowSize, _remotePacketSize);
             _channel.SetIsOpen(true);
@@ -110,5 +117,11 @@ namespace Renci.SshNet.Tests.Classes.Channels
         {
             Assert.AreEqual(0, _channelExceptionRegister.Count, _channelExceptionRegister.AsString());
         }
+
+        [TestMethod]
+        public void ChannelCloseReceivedShouldBlockUntilClosedEventHandlerHasCompleted()
+        {
+            Assert.IsTrue(_channelClosedEventHandlerCompleted.WaitOne(0));
+        }
     }
 }

+ 3 - 0
src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj

@@ -111,7 +111,10 @@
     <Compile Include="Classes\Channels\ChannelSessionTest_Open_OnOpenFailureReceived_RetriesAvalable.cs" />
     <Compile Include="Classes\Channels\ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofNotReceived_SendEofInvoked.cs" />
     <Compile Include="Classes\Channels\ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived_ConnectionExceptionWaitingForChannelCloseMessage.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_Dispose_SessionIsConnectedAndChannelIsOpen_EofReceived_OperationTimeoutExceptionWaitingForChannelCloseMessage.cs" />
     <Compile Include="Classes\Channels\ChannelTest_Dispose_SessionIsNotConnectedAndChannelIsOpen.cs" />
+    <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_DisposeChannelInClosedEventHandler.cs" />
     <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelCloseReceived_OnClose_Exception.cs" />
     <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelCloseReceived_SessionIsConnectedAndChannelIsOpen_EofReceived.cs" />
     <Compile Include="Classes\Channels\ChannelTest_OnSessionChannelDataReceived_OnData_Exception.cs" />

+ 23 - 14
src/Renci.SshNet/Channels/Channel.cs

@@ -400,21 +400,13 @@ namespace Renci.SshNet.Channels
         {
             _closeMessageReceived = true;
 
-            // signal that SSH_MSG_CHANNEL_CLOSE message was received from server
-            // we need to signal this before firing the Closed event, as a subscriber
-            // may very well react to the Closed event by closing or disposing the
-            // channel which in turn will wait for this handle to be signaled
+            // Signal that SSH_MSG_CHANNEL_CLOSE message was received from server.
+            // We need to signal this before invoking Close() as it may very well
+            // be blocked waiting for this signal.
             var channelClosedWaitHandle = _channelClosedWaitHandle;
             if (channelClosedWaitHandle != null)
                 channelClosedWaitHandle.Set();
 
-            // raise event signaling that the server has closed its end of the channel
-            var closed = Closed;
-            if (closed != null)
-            {
-                closed(this, new ChannelEventArgs(LocalChannelNumber));
-            }
-
             // close the channel
             Close();
         }
@@ -554,8 +546,9 @@ namespace Renci.SshNet.Channels
                     {
                         _closeMessageSent = true;
 
-                        // wait for channel to be closed if we actually sent a close message (either to initiate closing
-                        // the channel, or as response to a SSH_MSG_CHANNEL_CLOSE message sent by the server
+                        // only wait for the channel to be closed by the server if we didn't send a
+                        // SSH_MSG_CHANNEL_CLOSE as response to a SSH_MSG_CHANNEL_CLOSE sent by the
+                        // server
                         try
                         {
                             WaitOnHandle(_channelClosedWaitHandle);
@@ -567,7 +560,22 @@ namespace Renci.SshNet.Channels
                     }
                 }
 
-                IsOpen = false;
+                if (IsOpen)
+                {
+                    // mark sure the channel is marked closed before we raise the Closed event
+                    // this also ensures don't raise the Closed event more than once
+                    IsOpen = false;
+
+                    if (_closeMessageReceived)
+                    {
+                        // raise event signaling that both ends of the channel have been closed
+                        var closed = Closed;
+                        if (closed != null)
+                        {
+                            closed(this, new ChannelEventArgs(LocalChannelNumber));
+                        }
+                    }
+                }
             }
         }
 
@@ -837,6 +845,7 @@ namespace Renci.SshNet.Channels
             if (_isDisposed)
                 return;
 
+            Console.WriteLine("IN DISPOSE");
             if (disposing)
             {
                 Close();