Ejemplo n.º 1
0
        public void AuthenticationFailed()
        {
            var sentMutex = new ManualResetEventSlim(false);

            using (var serverStub = new ServerMock())
            {
                IMemcacheRequest authenticationRequest = null;
                // a token that fails
                var authenticatorTokenFailing = new Moq.Mock<IAuthenticationToken>();
                authenticatorTokenFailing
                    .Setup(t => t.StepAuthenticate(Moq.It.IsAny<TimeSpan>(), out authenticationRequest))
                    .Returns(Status.TemporaryFailure);
                // a token that works
                var authenticatorTokenOk = new Moq.Mock<IAuthenticationToken>();
                authenticatorTokenOk
                    .Setup(t => t.StepAuthenticate(Moq.It.IsAny<TimeSpan>(), out authenticationRequest))
                    .Returns(Status.NoError);
                // an authenticator that returns one failing token followed by working tokens
                bool alreadyFailed = false;
                var authenticator = new Moq.Mock<IMemcacheAuthenticator>();
                authenticator
                    .Setup(auth => auth.CreateToken())
                    .Returns(() =>
                        {
                            if (alreadyFailed)
                                return authenticatorTokenOk.Object;
                            alreadyFailed = true;
                            return authenticatorTokenFailing.Object;
                        });

                // setup the request to send
                bool requestFailed = false;
                bool requestAchieved = false;
                var request = new Moq.Mock<IMemcacheRequest>();
                request
                    .Setup(r => r.Fail())
                    .Callback(() =>
                        {
                            requestFailed = true;
                            sentMutex.Set();
                        });
                request
                    .Setup(r => r.HandleResponse(
                        Moq.It.Is<Headers.MemcacheResponseHeader>(h => h.Status == Status.NoError),
                        Moq.It.IsAny<byte[]>(),
                        Moq.It.IsAny<byte[]>(),
                        Moq.It.IsAny<byte[]>()))
                    .Callback(() =>
                        {
                            requestAchieved = true;
                            sentMutex.Set();
                        });
                var queryBuffer = new byte[MemcacheRequestHeader.Size];
                new MemcacheRequestHeader().ToData(queryBuffer);
                request
                    .Setup(r => r.GetQueryBuffer())
                    .Returns(queryBuffer);

                IMemcacheTransport transportToWork = null;
                var transportToFail = new MemcacheTransport(
                    serverStub.ListenEndPoint,
                    new MemcacheClientConfiguration
                    {
                        SocketTimeout = TimeSpan.Zero,
                        Authenticator = authenticator.Object,
                    },
                    _ => { },
                    t =>
                    {
                        Interlocked.Exchange(ref transportToWork, t);
                    },
                    false,
                    () => false);
                new MemcacheResponseHeader
                    {
                        Status = Status.NoError,
                        Opcode = Opcode.Get,
                    }.ToData(serverStub.ResponseHeader);

                Exception raised = null;
                transportToFail.TransportError += e =>
                        // when the transport fails collect the exception
                        Interlocked.Exchange(ref raised, e);
                var sent = transportToFail.TrySend(request.Object);

                Assert.IsFalse(sent, "The request send should fail");
                Assert.IsNotNull(raised, "The authentication should have failed");

                // wait for reconnection to happen (should be done in a instant timer)
                Assert.That(ref transportToWork, (!Is.Null).After(1000, 10), "The working transport should have been set");

                sent = transportToWork.TrySend(request.Object);
                Assert.IsTrue(sent, "The request should have been sent");
                var received = sentMutex.Wait(TimeSpan.FromMinutes(5));
                Assert.IsTrue(received, "The response should have been received");
                Assert.IsFalse(requestFailed, "The request should not have failed");
                Assert.IsTrue(requestAchieved, "The request should have achieved");
            }
        }
Ejemplo n.º 2
0
        public void MemcacheTransportDisposeTransportNotInPoolTest()
        {
            int createdTransports = 0;
            int disposedTransports = 0;
            var mutex1 = new ManualResetEventSlim(false);
            var mutex2 = new ManualResetEventSlim(false);
            Status returnStatus = Status.NoError;

            // Memcache client config
            var config = new MemcacheClientConfiguration
            {
                DeadTimeout = TimeSpan.FromSeconds(1),
                TransportConnectTimerPeriod = TimeSpan.FromMilliseconds(100),
                TransportFactory = (_, __, ___, ____, _____, ______) =>
                    new MemcacheTransportForTest(_, __, ___, ____, _____, ______, () => { createdTransports++; }, () => { disposedTransports++; mutex1.Set(); }),
                PoolSize = 1,
            };

            MemcacheClient memcacheClient;
            using (var serverMock = new ServerMock())
            {
                config.NodesEndPoints.Add(serverMock.ListenEndPoint);
                serverMock.ResponseBody = new byte[24];

                // Create Memcache client
                memcacheClient = new MemcacheClient(config);

                // Test the number of transports that have been created
                Assert.AreEqual(1, createdTransports);

                // Do a get to initialize the transport
                Assert.IsTrue(memcacheClient.Get("whatever", (Status s, byte[] o) => { returnStatus = s; mutex2.Set(); }));
                Assert.IsTrue(mutex2.Wait(1000), "Timeout on the get request");
                Assert.AreEqual(Status.InternalError, returnStatus);
                mutex2.Reset();
            }

            // Wait for the ServerMock to be fully disposed
            Thread.Sleep(100);

            // Attempt to send a request to take the transport out of the pool
            Assert.IsTrue(memcacheClient.Get("whatever", (Status s, byte[] o) => { returnStatus = s; mutex2.Set(); }));
            Assert.IsTrue(mutex2.Wait(1000), "Timeout on the get request");
            Assert.AreEqual(Status.InternalError, returnStatus);
            mutex2.Reset();

            // The initial transport should now be disposed, a new transport has been allocated and
            // is periodically trying to reconnect
            Assert.IsTrue(mutex1.Wait(1000), "Timeout on initial transport disposal");
            Assert.AreEqual(1, disposedTransports, "Expected the initial transport to be disposed");
            Assert.AreEqual(2, createdTransports, "Expected a new transport to be created to replace the disposed one");

            mutex1.Reset();

            // Dispose the client
            memcacheClient.Dispose();

            // Wait enough time for the reconnect timer to fire at least once
            Assert.IsTrue(mutex1.Wait(4000), "MemcacheTransport was not disposed before the timeout");

            // Check that all transports have been disposed
            Assert.AreEqual(2, disposedTransports);
            Assert.AreEqual(createdTransports, disposedTransports);
        }
Ejemplo n.º 3
0
        public void QueueFullTest()
        {
            var config = new Configuration.MemcacheClientConfiguration
            {
                QueueLength = 1,
            };
            int transportAvailablized = 0;

            using (var serverMock = new ServerMock())
            using (var transportToTest = new MemcacheTransport(serverMock.ListenEndPoint, config, t => { }, t => Interlocked.Increment(ref transportAvailablized), false, null))
            {
                var requestHeader = new MemcacheResponseHeader
                {
                    Cas = 1,
                    DataType = 2,
                    ExtraLength = 4,
                    KeyLength = 0,
                    Opaque = 42,
                    Opcode = Opcode.Get,
                    Status = Headers.Status.KeyNotFound,
                    TotalBodyLength = 4,
                };
                requestHeader.ToData(serverMock.ResponseHeader);
                serverMock.ResponseBody = new byte[4];

                serverMock.ReceiveMutex = new ManualResetEventSlim();
                var clientMutex = new ManualResetEventSlim();
                var request1 = new GetRequest
                {
                    RequestId = (uint)42,
                    Key = "Hello, world".Select(c => (byte)c).ToArray(),
                    RequestOpcode = Opcode.Get,
                    CallBack = (r, v) => clientMutex.Set(),
                };
                var request2 = new GetRequest
                {
                    RequestId = (uint)42,
                    Key = "Hello, world".Select(c => (byte)c).ToArray(),
                    RequestOpcode = Opcode.Get,
                    CallBack = (r, v) => { },
                };

                // we sent a first request and let the server wait before respond
                Assert.IsTrue(transportToTest.TrySend(request1), "The first request failed to be sent");
                Assert.That(() => transportAvailablized, Is.EqualTo(2).After(1000, 50));
                // we check that the queue is full, and the transport fail to send a new request
                Assert.IsFalse(transportToTest.TrySend(request2), "The second request should not have been sent");
                // unblocks both server response and callback from client
                serverMock.ReceiveMutex.Set();
                Assert.IsTrue(clientMutex.Wait(1000), "The response callback has not been triggered for the first request");
                // make sure that we triggered the transport available after the queue is not full anymore
                Assert.That(() => transportAvailablized, Is.EqualTo(3).After(1000, 50));
                // checks if we can send a new request since the queue is not full anymore
                Assert.IsTrue(transportToTest.TrySend(request2), "The third request failed to be sent");
                Assert.That(() => transportAvailablized, Is.EqualTo(4).After(1000, 50));
            }
        }
Ejemplo n.º 4
0
        public void MemcacheTransportDisposeBasicTest(int nbOfNodes, int nbOfTransportsPerNode)
        {
            int createdTransports = 0;
            int disposedTransports = 0;

            // Memcache client config
            var config = new MemcacheClientConfiguration
            {
                DeadTimeout = TimeSpan.FromSeconds(1),
                TransportConnectTimerPeriod = TimeSpan.FromMilliseconds(100),
                TransportFactory = (_, __, ___, ____, _____, ______) =>
                    new MemcacheTransportForTest(_, __, ___, ____, _____, ______, () => { createdTransports++; }, () => { disposedTransports++; }),
                PoolSize = nbOfTransportsPerNode,
            };

            var serverMocks = new List<ServerMock>(nbOfNodes);
            try
            {
                for (int p = 0; p < nbOfNodes; p++)
                {
                    var serverMock = new ServerMock();
                    config.NodesEndPoints.Add(serverMock.ListenEndPoint);
                    serverMocks.Add(serverMock);
                }

                // Create Memcache client
                var memcacheClient = new MemcacheClient(config);

                // Test the number of transports that have been created
                Assert.AreEqual(nbOfNodes * nbOfTransportsPerNode, createdTransports, "Expected number of transports = number of nodes * poolSize");

                // Dispose the client and test that the number of transports is back to zero
                memcacheClient.Dispose();
                Assert.AreEqual(createdTransports, disposedTransports, "Expected all the transports to be disposed");
            }
            finally
            {
                foreach (var mock in serverMocks)
                {
                    mock.Dispose();
                }
            }
        }
Ejemplo n.º 5
0
        public void MemcacheSocketTest([Values(0, 2, 10)]int maxByteSentByServer)
        {
            using (var serverMock = new ServerMock())
            {
                var endPoint = serverMock.ListenEndPoint;
                serverMock.MaxSent = maxByteSentByServer;

                // random header
                var requestHeader = new MemcacheRequestHeader
                {
                    Cas = 1,
                    DataType = 2,
                    ExtraLength = 5,
                    KeyLength = 3,
                    Opaque = 42,
                    Opcode = Opcode.Increment,
                    TotalBodyLength = 12,
                };
                // body with the size defined in the header
                var requestBody = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

                // build the request buffer
                var requestBuffer = new byte[MemcacheRequestHeader.Size + requestHeader.TotalBodyLength];
                requestHeader.ToData(requestBuffer);
                Array.Copy(requestBody, 0, requestBuffer, MemcacheRequestHeader.Size, requestHeader.TotalBodyLength);

                // build the response header
                var responseHeader = new MemcacheResponseHeader
                {
                    Cas = 8,
                    DataType = 12,
                    ExtraLength = 3,
                    KeyLength = 0,
                    // must be the same or it will crash : TODO add a test to ensure we detect this fail
                    Opaque = requestHeader.Opaque,
                    Status = Status.UnknownCommand,
                    Opcode = Opcode.Prepend,
                    TotalBodyLength = 15,
                };
                // body with the size defined in the header
                var responseExtra = new byte[] { 1, 2, 3 };
                var responseMessage = new byte[] { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

                // build the request buffer
                var responseBody = new byte[responseHeader.TotalBodyLength];
                Array.Copy(responseExtra, 0, responseBody, 0, responseHeader.ExtraLength);
                Array.Copy(responseMessage, 0, responseBody, responseHeader.ExtraLength, responseHeader.TotalBodyLength - responseHeader.ExtraLength);

                // set the things to answer to the server
                serverMock.ResponseBody = responseBody;
                responseHeader.ToData(serverMock.ResponseHeader);

                var request = new RequestMock
                {
                    QueryBuffer = requestBuffer,
                    RequestId = requestHeader.Opaque
                    /*ResponseHeader = responseHeader,
                    Message = responseMessage,
                    Extra = responseExtra,*/
                };

                using (var transport = new MemcacheTransport(endPoint, new MemcacheClientConfiguration(), _ => { }, _ => { }, false, null))
                {
                    Assert.IsTrue(transport.TrySend(request));

                    Assert.IsTrue(request.Mutex.Wait(TimeSpan.FromSeconds(10)), "The request has not been completed on less than 10 sec");

                    // and now, assert that we sent what we had to send and we received what the server sent
                    Assert.AreEqual(requestHeader, serverMock.LastReceivedHeader, "Sent header differ from header received by the server");
                    CollectionAssert.AreEqual(requestBody, serverMock.LastReceivedBody, "Sent body is different than received by the server");

                    Assert.AreEqual(responseHeader, request.ResponseHeader, "Received header differ from header sent by the server");
                    CollectionAssert.AreEqual(responseExtra, request.Extra, "Received extra is different than sent by the server");
                    CollectionAssert.AreEqual(responseMessage, request.Message, "Received message is different than sent by the server");
                }
            }
        }
Ejemplo n.º 6
0
        public void AuthenticationTest()
        {
            var config = new Configuration.MemcacheClientConfiguration
            {
                Authenticator = Configuration.MemcacheClientConfiguration.SaslPlainAuthenticatorFactory(string.Empty, "NoLogin", "NoPass"),
            };

            using (var serverMock = new ServerMock())
            using (var transport = new MemcacheTransport(serverMock.ListenEndPoint, config,
                t => { },
                t => { }, false, null))
            {
                var mutex = new ManualResetEventSlim();
                new MemcacheResponseHeader
                {
                    Opaque = 0,
                    Opcode = Opcode.StartAuth,
                    Status = Status.NoError,
                }.ToData(serverMock.ResponseHeader);

                Status responseStatus = Status.InternalError;
                Assert.IsTrue(transport.TrySend(new NoOpRequest { RequestId = 0, Callback = h => { mutex.Set(); responseStatus = h.Status; } })
                    , "Unable to send a request on authenticated transport");
                Assert.IsTrue(mutex.Wait(1000), "No response retrived on authenticated transport");
                Assert.AreEqual(Status.NoError, responseStatus, "The response returned on error");
            }
        }
Ejemplo n.º 7
0
        public void NodeWorkingTransportsTest(int nbOfTransportsPerNode)
        {
            int createdTransports = 0;
            int rememberPort;
            var mutex = new ManualResetEventSlim(false);
            Status returnStatus = Status.NoError;
            MemcacheNode theNode = null;

            // Memcache client config
            var config = new MemcacheClientConfiguration
            {
                DeadTimeout = TimeSpan.FromSeconds(1),
                TransportFactory = (_, __, ___, ____, _____, ______) =>
                    new MemcacheTransportForTest(_, __, ___, ____, _____, ______, () => { createdTransports++; }, () => { }),
                NodeFactory = (_, __) =>
                    theNode = MemcacheClientConfiguration.DefaultNodeFactory(_, __) as MemcacheNode,
                PoolSize = nbOfTransportsPerNode,
            };

            MemcacheClient memcacheClient;
            using (var serverMock1 = new ServerMock())
            {
                config.NodesEndPoints.Add(serverMock1.ListenEndPoint);
                rememberPort = serverMock1.ListenEndPoint.Port;

                serverMock1.ResponseBody = new byte[24];

                // Create a Memcache client with one node
                memcacheClient = new MemcacheClient(config);

                // Check that we hooked to the MemcacheNode
                Assert.IsNotNull(theNode, "Did not hook to the MemcacheNode while creating the client");

                // Check the number of transports that have been created
                Assert.AreEqual(nbOfTransportsPerNode, createdTransports, "The number of created should be the number of configured transport");

                // Check the number of working transports (meaning, connected to the server) and the node state
                // By default, the transport are marked as being available upon creation of the client
                Assert.AreEqual(nbOfTransportsPerNode, theNode.WorkingTransports, "The number of working transport should be the number of created transport (1)");
                Assert.IsFalse(theNode.IsDead, "The node should be alive (1)");
                Assert.AreEqual(1, memcacheClient.AliveNodes, "Number of alive nodes is incorrect (1)");

                // Do a get to initialize one of the transports
                Assert.IsTrue(memcacheClient.Get("whatever", (Status s, byte[] o) => { returnStatus = s; mutex.Set(); }), "The request should be sent correctly (1)");
                Assert.IsTrue(mutex.Wait(1000), "Timeout on the get request");
                Assert.AreEqual(Status.InternalError, returnStatus, "The status of the request should be InternalError (1)");
                mutex.Reset();

                // Check the number of working transports and the node state
                Assert.AreEqual(nbOfTransportsPerNode, theNode.WorkingTransports, "The number of working transport should be the number of created transport (2)");
                Assert.IsFalse(theNode.IsDead, "The node should be alive (2)");
                Assert.AreEqual(1, memcacheClient.AliveNodes, "Number of alive nodes is incorrect (2)");
            }

            // Wait for the ServerMock to be fully disposed
            Thread.Sleep(100);

            // Attempt to send a request to take one of the transports out of the pool
            // After that the transport should be dead
            Assert.IsTrue(memcacheClient.Get("whatever", (Status s, byte[] o) => { returnStatus = s; mutex.Set(); }), "The request should be sent correctly (2)");
            Assert.IsTrue(mutex.Wait(1000), "Timeout on the get request");
            Assert.AreEqual(Status.InternalError, returnStatus, "The status of the request should be InternalError (2)");
            mutex.Reset();

            // Check the number of working transports and the node state
            Assert.AreEqual(nbOfTransportsPerNode - 1, theNode.WorkingTransports, "The number of working transport should be the number of created transport minus 1");
            if (nbOfTransportsPerNode == 1)
            {
                Assert.IsTrue(theNode.IsDead, "The node should be dead (3)");
                Assert.AreEqual(0, memcacheClient.AliveNodes, "Number of alive nodes is incorrect (3)");
            }
            else
            {
                Assert.IsFalse(theNode.IsDead, "The node should be alive (3)");
                Assert.AreEqual(1, memcacheClient.AliveNodes, "Number of alive nodes is incorrect (3)");
            }

            // A new transport has been allocated and is periodically trying to reconnect
            Assert.AreEqual(nbOfTransportsPerNode + 1, createdTransports, "Expected a new transport to be created to replace the disposed one");

            using (var serverMock2 = new ServerMock(rememberPort))
            {
                serverMock2.ResponseBody = new byte[24];

                // After some delay, the transport should connect
                Assert.That(() => theNode.WorkingTransports, new DelayedConstraint(new EqualConstraint(nbOfTransportsPerNode), 4000, 100), "After a while, the transport should manage to connect");
                Assert.IsFalse(theNode.IsDead, "The node should be alive (4)");
                Assert.AreEqual(1, memcacheClient.AliveNodes, "Number of alive nodes is incorrect (4)");

                // Attempt a get
                Assert.IsTrue(memcacheClient.Get("whatever", (Status s, byte[] o) => { returnStatus = s; mutex.Set(); }), "The request should be sent correctly (4)");
                Assert.IsTrue(mutex.Wait(1000), "Timeout on the get request");
                Assert.AreEqual(Status.InternalError, returnStatus, "The status of the request should be InternalError (4)");
                mutex.Reset();

                // Check the number of working transports and the node state
                Assert.AreEqual(nbOfTransportsPerNode, theNode.WorkingTransports, "The number of working transport should be the number of created transport (5)");
                Assert.IsFalse(theNode.IsDead, "The node should be alive (5)");
                Assert.AreEqual(1, memcacheClient.AliveNodes, "Number of alive nodes is incorrect (5)");

                // Dispose the client
                memcacheClient.Dispose();
            }
        }
Ejemplo n.º 8
0
        public void ReceiveFailConsistency([Values(true, false)] bool failInBody)
        {
            using (var serverMock = new ServerMock())
            {
                var endpoint = serverMock.ListenEndPoint;
                serverMock.ResponseBody = new byte[24];
                if (failInBody)
                {
                    // the Opaque vs RequestId is check in the body receiving callback
                    // I put 2 different values
                    new MemcacheResponseHeader
                    {
                        Cas = 8,
                        DataType = 12,
                        ExtraLength = 0,
                        KeyLength = 0,
                        Opaque = 0,
                        Status = Status.NoError,
                        Opcode = Opcode.Set,
                        TotalBodyLength = 0,
                    }.ToData(serverMock.ResponseHeader);
                }
                else
                {
                    // the magic number is checked in the header receiving callback
                    // I corrupt it
                    new MemcacheResponseHeader
                    {
                        Cas = 8,
                        DataType = 12,
                        ExtraLength = 0,
                        KeyLength = 0,
                        Opaque = 1,
                        Status = Status.NoError,
                        Opcode = Opcode.Set,
                        TotalBodyLength = 0,
                    }.ToData(serverMock.ResponseHeader);
                    serverMock.ResponseHeader.CopyFrom(0, (uint)42);
                }

                var config = new MemcacheClientConfiguration
                {
                    PoolSize = 1,
                };

                var node = new Memcache.Node.MemcacheNode(endpoint, config);
                var errorMutex = new ManualResetEventSlim(false);
                var callbackMutex = new ManualResetEventSlim(false);

                Exception expectedException = null;
                node.TransportError += e =>
                    {
                        Interlocked.Exchange<Exception>(ref expectedException, e);
                        errorMutex.Set();
                    };
                int nodeAliveCount = 0;
                node.NodeAlive += t => ++nodeAliveCount;
                int nodeDeadCount = 0;
                node.NodeDead += t => ++nodeDeadCount;

                Status receivedStatus = Status.NoError;
                node.TrySend(
                    new SetRequest
                    {
                        RequestOpcode = Opcode.Set,
                        RequestId = 1,
                        Expire = TimeSpan.FromSeconds(1),
                        Key = "Key".Select(c => (byte)c).ToArray(),
                        Message = new byte[] { 0, 1, 2, 3, 4 },
                        CallBack = s =>
                        {
                            receivedStatus = s;
                            callbackMutex.Set();
                        },
                    }, 1000);
                // must wait before the next test because the TransportError event is fired after the callback call

                // Expected result :
                // * The callback must have been called before 1 sec
                // * The failure callback must have been called, so the received status must be InternalError
                // * An MemcacheException should have been raised by the receive due to the bad response
                // * The node should have exaclty one transport in its pool
                //      more mean that we added it twice after a failure, less means we didn't putted it back in the pool
                // * The node should not be seen has dead yet

                Assert.IsTrue(callbackMutex.Wait(1000), @"The 1st callback has not been received after 1 second");
                Assert.IsTrue(errorMutex.Wait(1000), @"The 1st error has not been received after 1 second");
                Assert.AreEqual(Status.InternalError, receivedStatus, @"A bad response has not sent an InternalError to the request callback");
                Assert.IsInstanceOf<Memcache.Exceptions.MemcacheException>(expectedException, @"A bad response has not triggered a transport error. Expected a MemcacheException.");
                Assert.AreEqual(0, nodeDeadCount, @"The node has been detected has dead before a new send has been made");
                // After a short delay, the transport should be back in the transport pool (node.PoolSize == 1)
                Assert.That(() => node.PoolSize, new DelayedConstraint(new EqualConstraint(1), 2000, 100), "After a while, the transport should be back in the pool");

                new MemcacheResponseHeader
                {
                    Cas = 8,
                    DataType = 12,
                    ExtraLength = 0,
                    KeyLength = 0,
                    // must be the same or it will crash : TODO add a test to ensure we detect this fail
                    Opaque = 1,
                    Status = Status.NoError,
                    Opcode = Opcode.Set,
                    TotalBodyLength = 0,
                }.ToData(serverMock.ResponseHeader);

                serverMock.ResponseBody = null;
                expectedException = null;
                callbackMutex.Reset();
                receivedStatus = Status.NoError;

                var result = node.TrySend(
                    new SetRequest
                    {
                        RequestOpcode = Opcode.Set,
                        RequestId = 1,
                        Expire = TimeSpan.FromSeconds(1),
                        Key = "Key".Select(c => (byte)c).ToArray(),
                        Message = new byte[] { 0, 1, 2, 3, 4 },
                        CallBack = s =>
                        {
                            receivedStatus = s;
                            callbackMutex.Set();
                        },
                    }, 1000);

                // Expected result :
                // * An SocketException should have been raised by the send, since the previous receice has disconnected the socket
                // * The return must be true, because the request have been enqueued before the transport seen the socket died
                // * The failure callback must have been called, so the received status must be InternalError

                Assert.IsTrue(callbackMutex.Wait(1000), @"The 2nd callback has not been received after 1 second");
                Assert.IsTrue(result, @"The first failed request should not see a false return");
                Assert.AreEqual(Status.InternalError, receivedStatus, @"The send operation should have detected that the socket is dead");

                // After a short delay, the transport should connect
                Assert.That(() => node.PoolSize, new DelayedConstraint(new EqualConstraint(1), 2000, 100), "After a while, the transport should manage to connect");

                expectedException = null;
                callbackMutex.Reset();
                result = node.TrySend(
                    new SetRequest
                    {
                        RequestOpcode = Opcode.Set,
                        RequestId = 1,
                        Expire = TimeSpan.FromSeconds(1),
                        Key = "Key".Select(c => (byte)c).ToArray(),
                        Message = new byte[] { 0, 1, 2, 3, 4 },
                        CallBack = s =>
                        {
                            receivedStatus = s;
                            callbackMutex.Set();
                        },
                    }, 1000);

                // Expected result : everything should works fine now
                // * The return must be true, because the new transport should be available now
                // * No exception should have been raised
                // * The callback must have been called and with a NoError status

                Assert.IsTrue(result, @"The node has not been able to send a new request after a disconnection");
                Assert.IsTrue(callbackMutex.Wait(1000), @"The message has not been received after 1 second, case after reconnection");
                Assert.AreEqual(Status.NoError, receivedStatus, @"The response after a reconnection is still not NoError");
                Assert.IsNull(expectedException, "The request shouldn't have thrown an exception");
            }
        }