| File: ServerInitiatedSessionShutdownTests.cs | Web Access |
| Project: ..\..\..\src\System.Private.ServiceModel\tests\Scenarios\Client\ChannelLayer\Client.ChannelLayer.IntegrationTests.csproj (Client.ChannelLayer.IntegrationTests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.ServiceModel; using System.ServiceModel.Channels; using System.Threading; using Infrastructure.Common; using Xunit; // Regression coverage for dotnet/wcf#5803. // // When a server closes its session while the client is idle between calls, // the client's duplex receive pump processes the EndRecord and the // ServiceChannel's auto-close path must transition the outer channel from // Opened to Closed. Prior to the fix, only the inner session was half-closed // and the outer channel remained Opened indefinitely. public class ServerInitiatedSessionShutdownTests : ConditionalWcfTest { [CallbackBehavior(UseSynchronizationContext = false)] private class NoOpCallback : IServerInitiatedShutdownCallback { public void OnShutdownNotification() { } } [WcfFact] [OuterLoop] public static void ServerInitiatedShutdown_ClientChannelTransitionsToClosed() { DuplexChannelFactory<IServerInitiatedShutdownService> factory = null; IServerInitiatedShutdownService proxy = null; ICommunicationObject commObj = null; ManualResetEventSlim closingSignal = new ManualResetEventSlim(false); ManualResetEventSlim closedSignal = new ManualResetEventSlim(false); bool faulted = false; try { // *** SETUP *** \\ NetTcpBinding binding = new NetTcpBinding(SecurityMode.None); binding.CloseTimeout = TimeSpan.FromSeconds(10); binding.OpenTimeout = TimeSpan.FromSeconds(10); binding.SendTimeout = TimeSpan.FromSeconds(10); InstanceContext context = new InstanceContext(new NoOpCallback()); factory = new DuplexChannelFactory<IServerInitiatedShutdownService>( context, binding, new EndpointAddress(Endpoints.Tcp_NoSecurity_ServerInitiatedShutdown_Address)); proxy = factory.CreateChannel(); commObj = (ICommunicationObject)proxy; commObj.Closing += (s, e) => closingSignal.Set(); commObj.Closed += (s, e) => closedSignal.Set(); commObj.Faulted += (s, e) => { faulted = true; closedSignal.Set(); }; commObj.Open(); // *** EXECUTE *** \\ // Warm-up call so the duplex receive pump is active. string echoed = proxy.Echo("hello"); Assert.Equal("hello", echoed); // Ask the service to close its session after replying. string shutdownReply = proxy.RequestServerShutdown(); Assert.Equal("Server shutting down", shutdownReply); // *** VALIDATE *** \\ // Wait up to 10s for the client to react to the server's EndRecord. bool signaled = closedSignal.Wait(binding.CloseTimeout + TimeSpan.FromSeconds(5)); Assert.True(signaled, $"Client ServiceChannel did not transition out of {CommunicationState.Opened} after the " + $"server closed its session. Current state: {commObj.State}. " + "This indicates issue dotnet/wcf#5803 has regressed."); Assert.True(closingSignal.IsSet, "Client ServiceChannel did not raise Closing in response to a graceful server-initiated session shutdown."); Assert.False(faulted, "Client ServiceChannel transitioned to Faulted instead of Closed in response to a graceful " + "server-initiated session shutdown."); Assert.Equal(CommunicationState.Closed, commObj.State); } finally { // *** ENSURE CLEANUP *** \\ ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)proxy, factory); } } }