11

I have the following scenario:

My main Application (APP1) starts a Process (SERVER1). SERVER1 hosts a WCF service via named pipe. I want to connect to this service (from APP1), but sometimes it is not yet ready.

I create the ChannelFactory, open it and let it generate a client. If I now call a method on the generated Client I receive an excpetion whitch tells me that the Enpoint was not found:

var factory = new ChannelFactory<T>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe//localhost/myservice");
factory.Open()

var Client = factory.CreateChannel();
Client.Foo();

If I wait a little bit before calling the service, everything is fine;

var Client = factory.CreateChannel();
Thread.Sleep(2000);
Client.Foo();

How can I ensure, that the Service is ready without having to wait a random amount of time?

Jaster
  • 8,255
  • 3
  • 34
  • 60
  • 2
    Really: you just call the service and be prepared to handle an exception. Anything else doesn't really give you a good, solid feedback - yes, you could "ping" the service, but even if that "ping" succeeds, there's **no guarantee** that a few milliseconds later, your next call will indeed succeed. So just be prepared for exceptions and gracefully handle them. – marc_s Mar 22 '11 at 13:29

5 Answers5

6

If the general case is that you are just waiting for this other service to start up, then you may as well use the approach of having a "Ping" method on your interface that does nothing, and retrying until this starts responding.

We do a similar thing: we try and call a ping method in a loop at startup (1 second between retries), recording in our logs (but ultimately ignoring) any TargetInvocationException that occur trying to reach our service. Once we get the first proper response, we proceed onwards.

Naturally this only covers the startup warmup case - the service could go down after a successfull ping, or it we could get a TargetInvocationException for a reason other than "the service is not ready".

Rob Levine
  • 40,328
  • 13
  • 85
  • 111
  • I understand what you are doing, but this is just the opposite of gracefull code - I do that so far tbh, but I was looking for a better way. Did MS really fail to cover this very common scenario? – Jaster Mar 22 '11 at 14:18
  • 2
    They didn't "fail to cover" the scenario; "is ready" is a behavior that up to you to define. – Mike Atlas Mar 22 '11 at 15:32
  • @Jaster - This isn't a "failure" IMHO. WCF is just a communication library. The reality is that if your client tries to connect to a service that isn't up yet, it should rightly receive an exception that reflects this. WCF shouldn't presume to know *why* it isn't up, nor should it wait a few seconds and try again. The idea that "I know the service should be up in a moment" is not a general use case here. If your use case is that the server will be up shortly after the client starts - I would just code it as a ping in a loop. – Rob Levine Mar 22 '11 at 16:04
  • 1
    I guess this depends on the point of View. If you want to read a File, you can either try to read the file and check if an IOException is thrown. The more gracefull way is to check if the file exists and if you are able to open it. Sure both works fine, but if there was a lack of the possibility to check if the file exists it would be a failure. From my point of view, check if a service is up and ready is about the same category. Just my oppinion, you don't need to agree ;) As already mentioned my solution IS the ping, but I was/am pretty unhappy with that. – Jaster Mar 22 '11 at 16:07
5

You could have the service signal an event [Edited-see note] once the service host is fully open and the Opened event of the channel listener has fired. The Application would wait on the event before using its proxy.


Note: Using a named event is easy because the .NET type EventWaitHandle gives you everything you need. Using an anonymous event is preferable but a bit more work, since the .NET event wrapper types don't give you an inheritable event handle. But it's still possible if you P/Invoke the Windows DuplicateHandle API yourself to get an inheritable handle, then pass the duplicated handle's value to the child process in its command line arguments.

Chris Dickson
  • 11,964
  • 1
  • 39
  • 60
  • You suggest to use a global resource for this? Are you serious? – Jaster Mar 22 '11 at 16:04
  • Or an anonymous event if you prefer - but I'm not sure how easy it is in managed code to get a child process to inherit an event handle. Either way the resource can be released once communication via WCF is under way. Also, if named, it could be Local to session rather than Global, if I've understood correctly what your child process is. – Chris Dickson Mar 22 '11 at 16:25
  • It's still not gracefull and there is not really any differance to the other suggested solutions. – Jaster Mar 23 '11 at 07:50
  • @Jaster: Waiting for a kernel object to be signalled is a standard, efficient approach to inter-process synchronisation. I would prefer it to rolling your own signal mechanism in user code using signal files or MSMQ as per the other suggestions. I believe that having the service signal in some way that ServiceHost.Open has completed is exactly the solution to your problem, if you want to avoid polling until there is no exception. – Chris Dickson Mar 23 '11 at 09:55
  • I've now looked into what is required for the child process to inherit an anonymous event handle. See my edit to the answer. – Chris Dickson Mar 23 '11 at 14:26
  • Chris... its not the solution in detail, its the approach that I don't like at all. It is simmilar to all other suggested solutions in here. – Jaster Mar 24 '11 at 14:05
  • @Jaster: Fine, your choice... though I find it difficult to envisage what a "graceful" solution would look like, if not this. – Chris Dickson Mar 24 '11 at 16:04
2
  1. If you're using .Net 4.0 you could use WS-Discovery to make the service announce its presence via Broadcast IP.

  2. The service could also send a message to a queue (MSMQ binding) with a short lifespan, say a few seconds, which your client can monitor.

  3. Have the service create a signal file, then use a FileSystemWatcher in the client to detect when it gets created.

  4. Just while (!alive) try { alive = client.IsAlive(); } catch { ...reconnect here... } (in your service contract, you just have IsAlive() return true)

Chris Wenham
  • 23,679
  • 13
  • 59
  • 69
  • Its not a WebService, so WS-Discovery isn't really covering my needs here. Neither FileWatch, nor MSMQ can tell me when the Service is up, they can only tell me when the Process has started. There a gap - a huge gap - between those two steps... – Jaster Mar 22 '11 at 14:16
  • You would queue the message/create the file after you've called ServiceHost.Open(), assuming you're using ServiceHost. – Chris Wenham Mar 22 '11 at 14:19
  • Are you sure? I don't know what you're doing in your service, but after `ServiceHost.Open()` is called then clients should be able to connect. – Chris Wenham Mar 22 '11 at 14:28
  • no they can't, there is a tiny gap there ;) Nevermind setting an "external flag" is eitherway not the solution I was looking for. – Jaster Mar 22 '11 at 14:55
  • The only other thing I can think of is to try the [Auto-Start Feature](http://msdn.microsoft.com/en-us/library/ee677260.aspx) of AppFabric with IIS or WAS, which the documentation says can handle Named Pipe requests. – Chris Wenham Mar 22 '11 at 15:05
  • @Jaster: I'm very interested in the "tiny gap" you say exists. Can you tell us more? – Chris Dickson Mar 22 '11 at 16:06
  • Just give it a try, by adding a Process.Start() and Process.WaitForIdle() in my given example. The gap will be visable on machines with low resources (high load). – Jaster Mar 22 '11 at 16:09
  • @Jaster: I don't doubt that the client can create and try to use a proxy before the WCF service is fully up and running, even if it waits on the child process handle and calls WaitForInputIdle beforehand. However, creation of the WCF service-side channel stack will be independent of any Windows message pump in the child process, so WaitForInputIdle doesn't help you. I'm sure that once ServiceHost.Open returns, the entire channel stack is in place, and clients can connect. That's why the best approach is to wait on a signal from the service that Open has completed. – Chris Dickson Mar 23 '11 at 09:47
1

I have had the same issue and when using net.pipe*://localhost/serviceName*, I solved it by looking at the process of the self-hosted application.

the way i did that was with a utility class, here is the code.

public static class ServiceLocator
{
    public static bool IsWcfStarted()
    {
        Process[] ProcessList = Process.GetProcesses();
        return ProcessList.Any(a => a.ProcessName.StartsWith("MyApplication.Service.Host", StringComparison.Ordinal));
    }


    public static void StartWcfHost()
    {

        string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

        var Process2 = new Process();
        var Start2 = new ProcessStartInfo();
        Start2.FileName = Path.Combine(path, "Service", "MyApplication.Service.Host.exe");            
        Process2.StartInfo = Start2;
        Process2.Start();
    }
}

now, my application isn't called MyApplication but you get my point... now in my client Apps that use the host i have this call:

if (!ServiceLocator.IsWcfStarted())
{
    WriteEventlog("First instance of WCF Client... starting WCF host.")
    ServiceLocator.StartWcfHost();
    int timeout=0;
    while (!ServiceLocator.IsWcfStarted()) 
    {
      timeout++;
      if(timeout> MAX_RETRY)
      {
       //show message that probably wcf host is not available, end the client
       ....
      }

    }
}

This solved 2 issues, 1. The code errors I had wend away because of the race condition, and 2 2. I know in a controlled manner if the Host crashed due to some issue or misconfiguration.

Hope it helps.

Walter

0

I attached an event handler to client.InnerChannel.faulted, then reduced the reliableSession to 20 seconds. Within the event handler I removed the existing handler then ran an async method to attempt to connect again and attached the event handler again. Seems to work.

Dmytro
  • 16,668
  • 27
  • 80
  • 130
Gary
  • 1