单元测试时ServiceHost地址冲突
我正在编写一个小型 WCF 托管引擎,它将根据 .config 文件动态创建 ServiceHost。总体思路是允许我们在运行时删除现有服务以及添加新服务,而无需使所有服务离线。
我遇到了一个单元测试问题,这表明这可能不像听起来那么容易。似乎任何给定端点只能存在一个 ServiceHost(即使单个 ServiceHost 中可能存在服务的多个不同端点)。这通常不是问题,但是当需要重新配置服务时,关闭原始 ServiceHost 实际上并不会终止该端点地址的注册。尝试为相同的服务(这意味着使用相同的端点)创建另一个 ServiceHost 失败,并出现以下异常:
System.InvalidOperationException: The ChannelDispatcher at 'net.pipe://localhost/' with contract(s) '"ITestService"' is unable to open its IChannelListener. --->
System.InvalidOperationException: A registration already exists for URI 'net.pipe://localhost/'.
我实际上在单元测试期间遇到了错误。这些测试将使用一个单元,该单元会尽可能地完全关闭 ServiceHosts 和托管引擎。然后创建托管引擎的另一个实例,该实例尝试为不同的测试再次重新创建相同的 ServiceHost。第二次测试遇到上面的错误。我猜测虽然 ServiceHost.Close() 被调用,但实际上并没有破坏服务主机......所以它仍然挂在内存中。我无法判断 GC 是否正在清理旧的服务主机...问题在最初发生后仍然存在并且没有消失(我已经能够确定的最好结果...到目前为止我已经等待了大约 30 分钟。 )
我的system.serviceModel的配置文件如下:
<system.serviceModel>
<services>
<service name="Campus.Core.ServiceModel.TestServiceStub">
<endpoint
address="net.pipe://localhost"
binding="netNamedPipeBinding"
contract="Campus.Core.ServiceModel.ITestService"
/>
</service>
</services>
</system.serviceModel>
I have a small WCF hosting engine that I am writing that will dynamically create ServiceHosts based on the .config file. The general idea is to allow us to remove existing services, as well as add new services, at runtime without having to bring all of our services offline.
I ran into a problem unit testing that indicates this may not be as easy as it sounds. It seems that only one ServiceHost may exist for any given endpoint (even though multiple different endpoints for a service may exist in a single ServiceHost). This is not a problem normally, however when a service needs to be reconfigured, bringing down the original ServiceHost does not actually kill the registration for that endpoint address. Trying to create another ServiceHost, for the same service (which means the same endpoints are used) fails with the following exception:
System.InvalidOperationException: The ChannelDispatcher at 'net.pipe://localhost/' with contract(s) '"ITestService"' is unable to open its IChannelListener. --->
System.InvalidOperationException: A registration already exists for URI 'net.pipe://localhost/'.
I am actually encountering the error during unit testing. The tests will exercise one unit, which fully closes down the ServiceHosts and hosting engine as much as is humanly possible. Then creates another instance of the hosting engine, which tries to recreate the same ServiceHosts again for a different test. The second test encounters the error above. I am guessing that while ServiceHost.Close() was called, that does not actually destroy the service host...so it is still hanging around in memory. I can not tell whether the GC is cleaning up the old service hosts or not...the problem persists without going away after it initially occurs (as best I have been able to determine...I have waited about 30 minutes so far.)
My configuration file for system.serviceModel is as follows:
<system.serviceModel>
<services>
<service name="Campus.Core.ServiceModel.TestServiceStub">
<endpoint
address="net.pipe://localhost"
binding="netNamedPipeBinding"
contract="Campus.Core.ServiceModel.ITestService"
/>
</service>
</services>
</system.serviceModel>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
为了提供这个问题的答案,以防其他人遇到这个问题。造成这个问题的原因其实有两个,如下:
1)单元测试时,如果遇到异常,通常会在ServiceHost关闭之前就从正在测试的代码中跳出来。这使得 ServiceHost 绑定到特定端点。这导致执行同一代码段的所有后续测试都失败。当我使用 SubSpec 和 xUnit 进行 BDD 时,单个测试用例(BDD 术语中的关注点)执行每个测试一个断言,并且单个测试用例可以包含多达十几个或更多断言。
2) 注意 MEX 端点。每个服务的 MEX 端点只能存在一次。最初,我创建了一个 http 和 net.tcp mex 端点。然而,这导致了一个问题,因为无论第二个启动的 MEX 端点实例都会引发异常。一般来说,如果您使用 MEX 端点,HTTP 是最有用的协议,除非存在某些物理基础设施问题阻止您这样做。
一般来说,在 ServiceHost 上调用 Close() 方法将完全解除它的绑定,从而允许以前绑定到其端点的任何地址再次可用。有时关闭可能需要一段时间,并且在极少数情况下,可能会引发异常。如果您使用 SubSpec 执行 BDD 并遵循每个测试单一断言的规则,则在一个测试中抛出阻止 ServiceHosts 关闭的异常将导致所有后续测试失败。
To provide an answer to this question, in case anyone else has run into the problem. There actually turned out to be two causes to this problem, as follows:
1) During unit testing, if an exception was encountered, it would usually break out of the code being tested before the ServiceHost could be closed. This left the ServiceHost bound to a particular endpoint. This caused ALL subsequent tests to fail that exercised the same piece of code. As I was doing BDD with SubSpec and xUnit, a single test case (concern in BDD terms) performed single-assertion-per-test, and a single test case could encompass a up to a dozen or more assertions.
2) Beware of the MEX endpoint. The MEX endpoint can only exist once per service. Initially, I had created an http and net.tcp mex endpoint. This caused a problem, however, as whichever instance of the MEX endpoint started up second threw an exception. Generally speaking, if you utilize the MEX endpoint, HTTP is the most useful protocol to use, unless there is some physical infrastructural issue preventing you from doing so.
Generally speaking, calling the Close() method on a ServiceHost will fully unbind it, allowing whatever addresses that were previously bound to its endpoints be usable again. Sometimes closure can take a while, and in rare cases, an exception may be thrown. If you are doing BDD with SubSpec and following the rule of single assertion per test, an exception thrown in one test that prevents a ServiceHosts closure will cause all subsequent tests to fail.
一种答案是每次启动服务主机时将 Guid 附加到服务主机的 URL,并使用工厂方法既启动 ServiceHost 实例又返回客户端通道,以便客户端知道要使用哪个 url 。
IDesign 的 InProcFactory 示例使用此方法,因此您可以按原样使用它:
http://www.idesign.net/idesign/DesktopDefault.aspx?tabindex=5&tabid=11
请注意,您必须在 IDesign 网站上注册才能下载示例,他们偶尔会向您发送有关培训等信息,但不会太多。
One answer is to append a Guid to the URL for the Service Host each time you spin one up, and use a factory approach that both spins up the ServiceHost instances and returns the Client-side channel, so that the client knows which url to use.
IDesign's InProcFactory sample uses this approach, so you may be able to use it as-is:
http://www.idesign.net/idesign/DesktopDefault.aspx?tabindex=5&tabid=11
Note that you'll have to register with IDesign's site in order to download the sample, and they'll send you the occasional announcement about training and such, but it's not too much.