From 73dc8fd0938e078a46a728bb2edb7a68f7b79e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bere=C5=BCa=C5=84ski?= Date: Mon, 28 Aug 2017 23:10:28 +0200 Subject: [PATCH 1/2] Add test exposing issues with disposing faulted client channels Relates to GH-71 and GH-104. --- .../Client/WcfClientFixture.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Castle.Facilities.WcfIntegration.Tests/Client/WcfClientFixture.cs b/src/Castle.Facilities.WcfIntegration.Tests/Client/WcfClientFixture.cs index be2769f8ba..bc33b1b13d 100644 --- a/src/Castle.Facilities.WcfIntegration.Tests/Client/WcfClientFixture.cs +++ b/src/Castle.Facilities.WcfIntegration.Tests/Client/WcfClientFixture.cs @@ -2090,6 +2090,36 @@ public void CanDiscoverServiceEndpointInManagedMode() } } } + + [Test] + public void WillNotThrowUponClientContainerDisposalIfServerDies() + { + using (var serviceContainer = new WindsorContainer() + .AddFacility(f => f.CloseTimeout = TimeSpan.Zero) + .Register(Component.For() + .DependsOn(new { number = 28 }) + .AsWcfService(new DefaultServiceModel() + .AddEndpoints(WcfEndpoint.ForContract() + .BoundTo(new NetTcpBinding { PortSharingEnabled = true }) + .At("net.tcp://localhost/Operations2") + ) + ))) + { + using (var clientContainer = new WindsorContainer() + .AddFacility() + .AddFacility(f => f.CloseTimeout = TimeSpan.Zero)) + { + var factory = clientContainer.Resolve(); + + var client = factory.GetClient(new Uri("net.tcp://localhost/Operations2")); + + Assert.AreEqual(28, client.GetValueFromConstructor()); + serviceContainer.Dispose(); + factory.Release(client); + } + } + } + protected void RegisterLoggingFacility(IWindsorContainer container) { var logging = new LoggingFacility(LoggerImplementation.ExtendedLog4net); From 769a0d61a77fb1248bf120dfa10dd066a07b9ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bere=C5=BCa=C5=84ski?= Date: Mon, 28 Aug 2017 23:13:59 +0200 Subject: [PATCH 2/2] Fix disposal of faulted client channels The disposal order in WcfClientActivator matters: WcfChannelHolder knows how to properly close/abort broken channels, while the WCF IDisposable implementation will throw an exception. Therefore, WcfChannelHolder.Dispose() needs to be called first, before the cleanup logic in AbstractComponentActivator invokes the WCF IDisposable implementation (via the DisposalConcern). The invocation order was originally correct, but it got inverted in 0c30043d "Improved concurrency during WCF channel failover". Restore the proper cleanup order, so that WCF channels are appropriately closed/aborted before being disposed. Fixes GH-71 and GH-104. --- .../Client/WcfClientActivator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Castle.Facilities.WcfIntegration/Client/WcfClientActivator.cs b/src/Castle.Facilities.WcfIntegration/Client/WcfClientActivator.cs index 9c6a10d41c..f7b3850eb3 100644 --- a/src/Castle.Facilities.WcfIntegration/Client/WcfClientActivator.cs +++ b/src/Castle.Facilities.WcfIntegration/Client/WcfClientActivator.cs @@ -59,10 +59,12 @@ public WcfClientActivator(ComponentModel model, IKernelInternal kernel, protected override void ApplyDecommissionConcerns(object instance) { - base.ApplyDecommissionConcerns(instance); - + // WCF channels have a _peculiar_ IDisposable implementation, which will throw if the channel is not closed gracefully. + // IWcfChannelHolder knows how to close/abort the channel properly, so it needs to be invoked first. var channelHolder = (IWcfChannelHolder)instance; channelHolder.Dispose(); + + base.ApplyDecommissionConcerns(instance); } protected override object Instantiate(CreationContext context)