Esempio n. 1
0
    public static void BasicHttp_Abort_ChannelFactory_Operations_Active()
    {
        // Test creates 2 channels from a single channel factory and
        // aborts the channel factory while both channels are executing
        // operations.  This verifies the operations are cancelled and
        // the channel factory is in the correct state.
        BasicHttpBinding             binding        = null;
        TimeSpan                     delayOperation = TimeSpan.FromSeconds(3);
        ChannelFactory <IWcfService> factory        = null;
        IWcfService                  serviceProxy1  = null;
        IWcfService                  serviceProxy2  = null;

        try
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
            binding.CloseTimeout = ScenarioTestHelpers.TestTimeout;
            factory       = new ChannelFactory <IWcfService>(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic));
            serviceProxy1 = factory.CreateChannel();
            serviceProxy2 = factory.CreateChannel();

            // *** EXECUTE *** \\
            Task <string> t1 = serviceProxy1.EchoWithTimeoutAsync("first", delayOperation);
            Task <string> t2 = serviceProxy2.EchoWithTimeoutAsync("second", delayOperation);
            factory.Abort();

            // *** VALIDATE *** \\
            Assert.True(factory.State == CommunicationState.Closed,
                        String.Format("Expected factory state 'Closed', actual was '{0}'", factory.State));

            Assert.Throws <TaskCanceledException>(() => t1.GetAwaiter().GetResult());
            Assert.Throws <TaskCanceledException>(() => t2.GetAwaiter().GetResult());

            Assert.True(((ICommunicationObject)serviceProxy1).State == CommunicationState.Closed,
                        String.Format("Expected channel 1 state 'Closed', actual was '{0}'", ((ICommunicationObject)serviceProxy1).State));
            Assert.True(((ICommunicationObject)serviceProxy2).State == CommunicationState.Closed,
                        String.Format("Expected channel 2 state 'Closed', actual was '{0}'", ((ICommunicationObject)serviceProxy2).State));

            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy1).Abort();
            ((ICommunicationObject)serviceProxy2).Abort();
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy1,
                                                          (ICommunicationObject)serviceProxy2,
                                                          factory);
        }
    }
Esempio n. 2
0
    public static void BasicHttp_Async_Close_ChannelFactory_Operations_Active()
    {
        // Test creates 2 channels from a single channel factory and
        // asynchronously closes the channel factory while both channels are
        // executing operations.  This verifies the operations are cancelled and
        // the channel factory is in the correct state.
        BasicHttpBinding             binding        = null;
        TimeSpan                     delayOperation = TimeSpan.FromSeconds(3);
        ChannelFactory <IWcfService> factory        = null;
        IWcfService                  serviceProxy1  = null;
        IWcfService                  serviceProxy2  = null;
        string expectedEcho1 = "first";
        string expectedEcho2 = "second";

        try
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
            binding.CloseTimeout = ScenarioTestHelpers.TestTimeout;
            binding.SendTimeout  = ScenarioTestHelpers.TestTimeout;
            factory       = new ChannelFactory <IWcfService>(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic));
            serviceProxy1 = factory.CreateChannel();
            serviceProxy2 = factory.CreateChannel();

            // *** EXECUTE *** \\
            Task <string> t1          = serviceProxy1.EchoWithTimeoutAsync(expectedEcho1, delayOperation);
            Task <string> t2          = serviceProxy2.EchoWithTimeoutAsync(expectedEcho2, delayOperation);
            Task          factoryTask = Task.Factory.FromAsync(factory.BeginClose, factory.EndClose, TaskCreationOptions.None);

            // *** VALIDATE *** \\
            factoryTask.GetAwaiter().GetResult();
            Assert.True(factory.State == CommunicationState.Closed,
                        String.Format("Expected factory state 'Closed', actual was '{0}'", factory.State));

            Exception exception1  = null;
            Exception exception2  = null;
            string    actualEcho1 = null;
            string    actualEcho2 = null;

            // Verification is slightly more complex for the close with active operations because
            // we don't know which might have completed first and whether the channel factory
            // was able to close and dispose either channel before it completed.  So we just
            // ensure the Tasks complete with an exception or a successful return and have
            // been closed by the factory.
            try
            {
                actualEcho1 = t1.GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                exception1 = e;
            }

            try
            {
                actualEcho2 = t2.GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                exception2 = e;
            }

            Assert.True(exception1 != null || actualEcho1 != null, "First operation should have thrown Exception or returned an echo");
            Assert.True(exception2 != null || actualEcho2 != null, "Second operation should have thrown Exception or returned an echo");

            Assert.True(actualEcho1 == null || String.Equals(expectedEcho1, actualEcho1),
                        String.Format("First operation returned '{0}' but expected '{1}'.", expectedEcho1, actualEcho1));

            Assert.True(actualEcho2 == null || String.Equals(expectedEcho2, actualEcho2),
                        String.Format("Second operation returned '{0}' but expected '{1}'.", expectedEcho2, actualEcho2));

            Assert.True(((ICommunicationObject)serviceProxy1).State == CommunicationState.Closed,
                        String.Format("Expected channel 1 state 'Closed', actual was '{0}'", ((ICommunicationObject)serviceProxy1).State));
            Assert.True(((ICommunicationObject)serviceProxy2).State == CommunicationState.Closed,
                        String.Format("Expected channel 2 state 'Closed', actual was '{0}'", ((ICommunicationObject)serviceProxy2).State));

            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy1).Abort();
            ((ICommunicationObject)serviceProxy2).Abort();
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy1,
                                                          (ICommunicationObject)serviceProxy2,
                                                          factory);
        }
    }
    public static void Abort_During_Implicit_Open_Closes_Async_Waiters()
    {
        // This test is a regression test of an issue with CallOnceManager.
        // When a single proxy is used to make several service calls without
        // explicitly opening it, the CallOnceManager queues up all the requests
        // that happen while it is opening the channel (or handling previously
        // queued service calls.  If the channel was closed or faulted during
        // the handling of any queued requests, it caused a pathological worst
        // case where every queued request waited for its complete SendTimeout
        // before failing.
        //
        // This test operates by making multiple concurrent asynchronous service
        // calls, but stalls the Opening event to allow them to be queued before
        // any of them are allowed to proceed.  It then closes the channel when
        // the first service operation is allowed to proceed.  This causes the
        // CallOnce manager to deal with all its queued operations and cause
        // them to complete other than by timing out.

        BasicHttpBinding             binding = null;
        ChannelFactory <IWcfService> factory = null;
        IWcfService serviceProxy             = null;
        int         timeoutMs        = 20000;
        long        operationsQueued = 0;
        int         operationCount   = 5;

        Task <string>[] tasks               = new Task <string> [operationCount];
        Exception[]     exceptions          = new Exception[operationCount];
        string[]        results             = new string[operationCount];
        bool            isClosed            = false;
        DateTime        endOfOpeningStall   = DateTime.Now;
        int             serverDelayMs       = 100;
        TimeSpan        serverDelayTimeSpan = TimeSpan.FromMilliseconds(serverDelayMs);
        string          testMessage         = "testMessage";

        try
        {
            // *** SETUP *** \\
            binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
            binding.TransferMode = TransferMode.Streamed;
            // SendTimeout is the timeout used for implicit opens
            binding.SendTimeout = TimeSpan.FromMilliseconds(timeoutMs);
            factory             = new ChannelFactory <IWcfService>(binding, new EndpointAddress(Endpoints.HttpBaseAddress_Basic_Text));
            serviceProxy        = factory.CreateChannel();

            // Force the implicit open to stall until we have multiple concurrent calls pending.
            // This forces the CallOnceManager to have a queue of waiters it will need to notify.
            ((ICommunicationObject)serviceProxy).Opening += (s, e) =>
            {
                // Wait until we see sync calls have been queued
                DateTime startOfOpeningStall = DateTime.Now;
                while (true)
                {
                    endOfOpeningStall = DateTime.Now;

                    // Don't wait forever -- if we stall longer than the SendTimeout, it means something
                    // is wrong other than what we are testing, so just fail early.
                    if ((endOfOpeningStall - startOfOpeningStall).TotalMilliseconds > timeoutMs)
                    {
                        Assert.True(false, "The Opening event timed out waiting for operations to queue, which was not expected for this test.");
                    }

                    // As soon as we have all our Tasks at least running, wait a little
                    // longer to allow them finish queuing up their waiters, then stop stalling the Opening
                    if (Interlocked.Read(ref operationsQueued) >= operationCount)
                    {
                        Task.Delay(500).Wait();
                        endOfOpeningStall = DateTime.Now;
                        return;
                    }

                    Task.Delay(100).Wait();
                }
            };

            // Each task will make a synchronous service call, which will cause all but the
            // first to be queued for the implicit open.  The first call to complete then closes
            // the channel so that it is forced to deal with queued waiters.
            Func <string> callFunc = () =>
            {
                // We increment the # ops queued before making the actual sync call, which is
                // technically a short race condition in the test.  But reversing the order would
                // timeout the implicit open and fault the channel.
                Interlocked.Increment(ref operationsQueued);

                // The call of the operation is what creates the entry in the CallOnceManager queue.
                // So as each Task below starts, it increments the count and adds a waiter to the
                // queue.  We ask for a small delay on the server side just to introduce a small
                // stall after the sync request has been made before it can complete.  Otherwise
                // fast machines can finish all the requests before the first one finishes the Close().
                Task <string> t = serviceProxy.EchoWithTimeoutAsync(testMessage, serverDelayTimeSpan);
                lock (tasks)
                {
                    if (!isClosed)
                    {
                        try
                        {
                            isClosed = true;
                            ((ICommunicationObject)serviceProxy).Abort();
                        }
                        catch { }
                    }
                }
                return(t.GetAwaiter().GetResult());
            };

            // *** EXECUTE *** \\

            DateTime startTime = DateTime.Now;
            for (int i = 0; i < operationCount; ++i)
            {
                tasks[i] = Task.Run(callFunc);
            }

            for (int i = 0; i < operationCount; ++i)
            {
                try
                {
                    results[i] = tasks[i].GetAwaiter().GetResult();
                }
                catch (Exception ex)
                {
                    exceptions[i] = ex;
                }
            }

            // *** VALIDATE *** \\
            double elapsedMs = (DateTime.Now - endOfOpeningStall).TotalMilliseconds;

            // Before validating that the issue was fixed, first validate that we received the exceptions or the
            // results we expected. This is to verify the fix did not introduce a behavioral change other than the
            // elimination of the long unnecessary timeouts after the channel was closed.
            int nFailures = 0;
            for (int i = 0; i < operationCount; ++i)
            {
                if (exceptions[i] == null)
                {
                    Assert.True((String.Equals("test", results[i])),
                                String.Format("Expected operation #{0} to return '{1}' but actual was '{2}'",
                                              i, testMessage, results[i]));
                }
                else
                {
                    ++nFailures;

                    TimeoutException toe = exceptions[i] as TimeoutException;
                    Assert.True(toe == null, String.Format("Task [{0}] should not have failed with TimeoutException", i));
                }
            }

            Assert.True(nFailures > 0,
                        String.Format("Expected at least one operation to throw an exception, but none did. Elapsed time = {0} ms.",
                                      elapsedMs));


            // --- Here is the test of the actual bug fix ---
            // The original issue was that sync waiters in the CallOnceManager were not notified when
            // the channel became unusable and therefore continued to time out for the full amount.
            // Additionally, because they were executed sequentially, it was also possible for each one
            // to time out for the full amount.  Given that we closed the channel, we expect all the queued
            // waiters to have been immediately waked up and detected failure.
            int expectedElapsedMs = (operationCount * serverDelayMs) + timeoutMs / 2;
            Assert.True(elapsedMs < expectedElapsedMs,
                        String.Format("The {0} operations took {1} ms to complete which exceeds the expected {2} ms",
                                      operationCount, elapsedMs, expectedElapsedMs));

            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy).Close();
            factory.Close();
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }