views:

341

answers:

2

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>
+1  A: 

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&amp;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.

Craig Vermeer
Our URL's can not be auto-generated, as they are explicitly defined. We use our URL's as part of our versioning strategy. Our services are also used by multiple platforms, not just .NET, so we don't always have control over the clients. :(
jrista
A: 

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.

jrista