Sharing objects between application instances using WCF
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 1 of 2 12 LastLast
Results 1 to 15 of 24

Thread: Sharing objects between application instances using WCF

  1. #1
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Lightbulb Sharing objects between application instances using WCF

    [This is the sequel to Sharing objects between application instances (using .NET Remoting)]

    Well, it took me some time to get back to this - there's a lot to read about WCF. And as I think this one will be mainly about WCF from now on, I've started a new thread about it.

    After having read lots of stuff about WCF and getting an idea of what it can all do and a vague idea of how I make it doing that, I decided to first translate the "greetings" sample from A Developer's Introduction to Windows Communication Foundation 4 from C# to C++/CLI to use it as an easy toy for initial experiments:

    Code:
    // WcfTest1Server.cpp: Hauptprojektdatei.
    
    // Translated from C# sample code at http://msdn.microsoft.com/en-us/library/ee354381.aspx
    
    #include "stdafx.h"
    
    #using "System.ServiceModel.dll"
    
    using namespace System;
    using namespace System::ServiceModel;
    using namespace System::ServiceModel::Description;
    
    [ServiceContract]
    public interface class IHello
    {
      [OperationContract]
      void SayHello(String ^name);
    };
    
    [ServiceContract]
    public interface class IGoodbye
    {
      [OperationContract]
      void SayGoodbye(String ^name);
    };
    
    public ref class GreetingService : public IHello, public IGoodbye  // Service implements both contracts
    {
    public:
      virtual void SayHello(String ^name)
      {
        Console::WriteLine("Hello {0}", name);
      }
    
      virtual void SayGoodbye(String ^name)
      {
        Console::WriteLine("Goodbye {0}", name);
      }
    };
    
    int main(array<System::String ^> ^args)
    {
      // Host is configured with two base addresses, one for HTTP and one for TCP
      ServiceHost ^host = gcnew ServiceHost(GreetingService::typeid,
        gcnew Uri("http://localhost:8080/greeting"),
        gcnew Uri("net.tcp://localhost:8081/greeting"));
      host->Open();
      for each (ServiceEndpoint ^se in host->Description->Endpoints)
        Console::WriteLine("A: {0}, B: {1}, C: {2}", se->Address, se->Binding->Name, se->Contract->Name);
      Console::WriteLine("Press <Enter> to stop the service");
      Console::ReadLine();
      host->Close();
      return 0;
    }
    That was easy so far.

    Difficulties started, however, when I tried to write a client for it, which I assumed not to be much more challenging either. And then it already took me some time to figure out that svcutil only accepts my server .exe as input if I compile it at least with the option /clr:pure instead of the mere /clr that gets set up by default by the IDE for a console app (unlike a Windows Forms app, BTW). (That does imply I can't use native code in a WPF service, doesn't it?)

    Then, after I had the .h file generated by svcutil, I tried to compile my minimalistic client with that header. (I don't post the client code itself for now since I'm sure it's irrelevant: The compiler already stalls while processing the .h file.) I get a bunch of C3766 errors, telling me I'd better implement the add/remove accessors of the five events inherited from ICommunicationObject, like void System::ServiceModel::ICommunicationObject::Closed::add(System::EventHandler ^). I get these errors for the two client classes HelloClient and GoodbyeClient which derive from ClientBase<TChannel> with the respective interface as the type parameter.

    However, according to MSDN, already ClientBase<TChannel> does implement them, yet these implementations are private. So what?

    I renamed the header file from its original name schemas.microsoft.com.2003.10.Serialization.h, which I found too long and somehow looked to me like svcutil would name all these files like that anyway, to WcfTest1Client.h, but I don't think that can have such consequences, can it?

    I'm confident I'll eventually figure this out, but there seems to be a considerable learning curve ahead. So, any help is appreciated.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  2. #2
    Join Date
    Jul 2002
    Posts
    2,494

    Re: Sharing objects between application instances using WCF

    Though WCF stuff can be implemented in C++/CLI, I think it is much better to make it in C# and connect to your existing application using class library. You can save a lot of efforts by using C#.
    BTW, according to my experience, WCF is one of W*F series that really works and has a good performance.

  3. #3
    Join Date
    Jan 2010
    Posts
    1,099

    Re: Sharing objects between application instances using WCF

    Quote Originally Posted by Eri523 View Post
    That does imply I can't use native code in a WPF service, doesn't it?
    Not sure... What if you isolate the native code, and create a .NET-only wrapper around it?

    Quote Originally Posted by Eri523 View Post
    I get a bunch of C3766 errors, telling me I'd better implement the add/remove accessors of the five events inherited from ICommunicationObject, like void System::ServiceModel::ICommunicationObject::Closed::add(System::EventHandler ^). I get these errors for the two client classes HelloClient and GoodbyeClient which derive from ClientBase<TChannel> with the respective interface as the type parameter.

    However, according to MSDN, already ClientBase<TChannel> does implement them, yet these implementations are private. So what?
    Those are events (on the MSDN page), not getters/setters - I'm a little rusty on C++/CLI equivalent of C# properties, but that's most likely what it wants. I know C++ .NET had some clumsy syntax for properties, and I think they changed it to something more intuitive in C++/CLI. Basically, the service contract is defined via the attributes (ServiceContract, OperationContract, DataContract, DataMember). The DataMember attribute is, in C# WCF model, applied to properties (which are syntactic sugar for get/set methods; nevertheless, note that functions are marked with the OperationContract attribute, but properties are a part of the data contract and have the DataMember attribute).

    As for svcutil, i don't think it's really required: it's purpose is (among other things I guess) to take a service, obtain the WSDL - which specifies the service contract, and use it to create a skeleton for a (potentially 3rd-party) client app.
    Something like this:

    But, since you created the service, you know the contract, so, I think that in effect you can skip that and create a test client manually. On the other hand, it would be nice to make it all work as it's supposed to... Because some other developer should be using the WSDL to create a client on his end.

    Anyway, I hope that makes sense, and I wish I could be of more help.

  4. #4
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Quote Originally Posted by Alex F View Post
    Though WCF stuff can be implemented in C++/CLI, I think it is much better to make it in C# [...]. You can save a lot of efforts by using C#.
    Mostly out of curiosity (you know, I'm somewhat neophobic about languages... ), what would I gain by making that transition? I suspect that workflow-style way of creating services, which sounds like one could click them together without writing a single line of code, isn't supported by C# Express anyway.

    [...] and connect to your existing application using class library.
    You mean I would use (or provide) a client-side DLL that exposes my services interface (or perhaps any application-specific adaption of it I want) and encapsulate interaction with the actual service? If yes, I think that DLL would, in most cases, just be an ultra-thin wrapper around the .h file generated by svcutil. As you'll see below, I got my test client, employing that .h file, to work in the meantime. The client .cpp file actually is just 26 code lines, and essentially there's nothing else in the client app besides that and the mentioned .h file. (I must admit, however, that of course this particular client is quite primitive, as is the service.)

    BTW, according to my experience, WCF is one of W*F series that really works and has a good performance.
    Good news, glad to hear!

    Quote Originally Posted by TheGreatCthulhu View Post
    Not sure... What if you isolate the native code, and create a .NET-only wrapper around it?
    Actually an idea I alredy thought about myself. It would certainly not be enough to isolate it in a separate module. As it's the assembly (i.e. .exe file) svcutil operates on and complains about, it'd need to be isolated in a separate DLL. So bye-bye to the monolithic one-.exe app I like so much. But then again, if the app is accompanied by its own server it's not that monolithic anymore anyway...

    At any rate, I probably won't need any native code in the project this is eventually meant to be used in at the moment. So these considerations are rather hypothetic...

    Those are events (on the MSDN page), not getters/setters[.]
    Actually, I was already concerned about using the term "accessors" in this context when I was writing the post (I somehow anticipated a reaction like yours from anyone... ) but I couldn't think of a better one.

    I'm a little rusty on C++/CLI equivalent of C# properties, but that's most likely what it wants. I know C++ .NET had some clumsy syntax for properties, and I think they changed it to something more intuitive in C++/CLI.
    add() is mapped to operator+=, remove() to operator-= and Invoke() to operator(). Not too clumsy I'd say. (And they are no propertes... )

    Basically, the service contract is defined via the attributes (ServiceContract, OperationContract, DataContract, DataMember). The DataMember attribute is, in C# WCF model, applied to properties (which are syntactic sugar for get/set methods; nevertheless, note that functions are marked with the OperationContract attribute, but properties are a part of the data contract and have the DataMember attribute).
    Though I wouldn't have sworn on it in my last post, it turned out that neiter the service contract nor the operation contract was the problem. As to the data contract: There's none involved here (or is there an implicit one regarding the String parameter?). And though it's encouraged all the way to use properties in .NET, I still mostly prefer explicit method calls over properties when an operation beyond a simple data transfer is involved, which obviously is the case when communicating with a service. And if it is a plain data transfer, I'll likely use plain public data members (perhaps due to the lacking notion of trivial properties in C++/CLI).

    As for svcutil, i don't think it's really required: it's purpose is (among other things I guess) to take a service, obtain the WSDL - which specifies the service contract, and use it to create a skeleton for a (potentially 3rd-party) client app.

    [...]

    But, since you created the service, you know the contract, so, I think that in effect you can skip that and create a test client manually.
    Actually, my main reason to use svcutil here was to create a starting point, since I didn't yet come across any relevant samples of how to create a client.

    On the other hand, it would be nice to make it all work as it's supposed to... Because some other developer should be using the WSDL to create a client on his end.
    Actually, I do have a WSDL that was produced as an intermediate step. I created the WSDL from the .exe and then the .h from the WSDL, both steps using svcutil. As I understood the svcutil documentation, there's no direct way from an .exe to a .h. However, I only had a brief look into the WSDL since I knew I wouldn't need to work with it directly.

    Anyway, I hope that makes sense, and I wish I could be of more help.
    Oh, it certainly did. And of course, as we all know, it's usually more productive to discuss things rather than exchanging ready-to-use solutions.

    So here's the client I have at this stage. Actually, it's just the same one I had as of my last post. It was only the .h file (and the app-config file, but I'd find that out later) that caused problems.

    Code:
    // WcfTest1.cpp: Hauptprojektdatei.
    
    #include "stdafx.h"
    
    #include "WcfTest1Client.h"
    
    using namespace System;
    
    int main(array<System::String ^> ^args)
    {
      String ^strName;
      Console::Write("Please enter your name: ");
      strName = Console::ReadLine();
      HelloClient ^helloclient = gcnew HelloClient;
      helloclient->SayHello(strName);
      helloclient->Close();
      GoodbyeClient ^goodbyeclient = gcnew GoodbyeClient;
      goodbyeclient->SayGoodbye(strName);
      goodbyeclient->Close();
    #ifdef _DEBUG
      Console::WriteLine("Hit <Enter> to continue...");
      Console::ReadLine();
    #endif
      return 0;
    }
    I mentioned in my last post that I had problems with the compiler demanding implementation of these five events. The solution I found for that feels pretty hackish but at least it works for now: I simply added dummy events to the client classes. Here's, exemplarily, HelloClient:

    Code:
    [System::Diagnostics::DebuggerStepThroughAttribute, 
    System::CodeDom::Compiler::GeneratedCodeAttribute(L"System.ServiceModel", L"4.0.0.0")]
    public ref class HelloClient : public System::ServiceModel::ClientBase<IHello^ >, public IHello
    {
    public:
      HelloClient();
      HelloClient(System::String^  endpointConfigurationName);
      HelloClient(System::String^  endpointConfigurationName, System::String^  remoteAddress);
      HelloClient(System::String^  endpointConfigurationName, System::ServiceModel::EndpointAddress^  remoteAddress);
      HelloClient(System::ServiceModel::Channels::Binding^  binding, System::ServiceModel::EndpointAddress^  remoteAddress);
      virtual System::Void SayHello(System::String^  name) sealed;
    
      // It insists on having implementations of the events, so I give it five dummies
    
    public:
      virtual event EventHandler ^Closed;
      virtual event EventHandler ^Closing;
      virtual event EventHandler ^Faulted;
      virtual event EventHandler ^Opened;
      virtual event EventHandler ^Opening;
    };
    Adding the events and a bit of reformatting to the two client classes was all I changed about the .h file. I just hope I didn't cut off something important in the private event implementations inside ClientBase<TChannel> with my dummy events.

    I'm uncertain about what the IHelloChannel and IGoodbyeChannel declared in the .h file are for. I couldn't find any references to them in the entire project code, and they're empty anyway except for the two interfaces they inherit. Could they be referenced implicitly from somewhere?

    Also important, of course was the app.config file created by svcutil, stored under the default name output.config. I could leave it uncanged except for adding the address attributes to the two endpoint nodes:

    Code:
            <client>
                <endpoint binding="basicHttpBinding" bindingConfiguration="DefaultBinding_IHello"
                    contract="IHello" name="DefaultBinding_IHello_IHello" address="http://localhost:8080/greeting" />
                <endpoint binding="basicHttpBinding" bindingConfiguration="DefaultBinding_IGoodbye"
                    contract="IGoodbye" name="DefaultBinding_IGoodbye_IGoodbye" address="http://localhost:8080/greeting" />
            </client>
    I have attached a ZIP file containing the client files (.cpp, .h and app.config) as well as the original .h and app.config files generated by svcutil for reference.

    One thing I don't really understand yet is tempuri.org. Right from the start I regarded it a mere placeholder for a real address (which got confirmed by the three-line Wikipedia article), so I replaced the four instances of it in the .h file with localhost:8080, but it turned out that the placeholder is referenced by default at some points inside the server which made connection attempts fail. I could make it work by simply reverting back to tempuri.org in the .h file, but of course I need to find out how to correctly set up the server instead.

    Next step I'll take will probably be implementing the data sharing scenario from the Remoting thread with WCF.
    Attached Files Attached Files
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  5. #5
    Join Date
    Jul 2002
    Posts
    2,494

    Re: Sharing objects between application instances using WCF

    If you need interprocess communication on the same computer, the best way is to use pipe and not HTTP. I don't remember details, take a look at NetNamedPipeBinding class. The change is transparent and affects only application configuration file.

  6. #6
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Thanks. That confirms thoughts I already had. For the first experiment I just wanted to connect to the given server, but I'll certainly look into NetNamedPipeBinding and aim to implement the sharing test scenario based on that.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  7. #7
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Ok, as an intermediate step I decided to first modify the greeting sample to use the netNamedPipeBinding which turned out to be easier than expected. The only thing I needed to change about the C++/CLI code was the statement in the server that instantiates the service host:

    Code:
      ServiceHost ^host = gcnew ServiceHost(GreetingService::typeid,
        gcnew Uri("net.pipe://localhost/greeting"));
    The rest could actually be done by modifying the app.config files. I removed anything or at least most of which isn't really needed and now have WCF use default values. This is the server's app.config (it didn't have one before):

    Code:
    <configuration>
      <system.serviceModel>
        <services>
          <service name="TestServer.GreetingService">
            <endpoint address="net.pipe://localhost/greeting"
                      binding="netNamedPipeBinding"
                      contract="IHello" />
            <endpoint address="net.pipe://localhost/greeting"
                      binding="netNamedPipeBinding"
                      contract="IGoodbye" />
          </service>
        </services>
      </system.serviceModel>
    </configuration>
    And this is the client's one which is considerably shorter now:

    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.serviceModel>
            <client>
                <endpoint binding="netNamedPipeBinding"
                    contract="IHello" name="DefaultBinding_IHello_IHello" address="net.pipe://localhost/greeting" />
                <endpoint binding="netNamedPipeBinding"
                    contract="IGoodbye" name="DefaultBinding_IGoodbye_IGoodbye" address="net.pipe://localhost/greeting" />
            </client>
        </system.serviceModel>
    </configuration>
    However, now I'm facing some uncertainties about how to implement the test scenario form the Remoting thread using WCF. The central question is: How would I return a live object (the TestServer::Server instance in the Remoting model code) using WCF? I found it was pretty straightforward using Remoting by deriving the class in question from MarsharByRefObject but I didn't yet come across anything equivalent in WCF. Does WCF have something like that at all, and if yes, where?

    In the meantime I'm considering an alternative approach that simply avoids returning a live object to the client: Drop the factory class and make the server class a singleton by specifying [ServiceBehavior(InstanceContextMode=InstanceContextMode::Single)] (what AFAIK isn't possible at all when using Remoting which led to the introduction of the factory class in the first place). The main role of the original Server class is now assumed by a new class internal to the server of which the new singular exposed server instance is able to manage multiple instances. (Remember the original scenario this is meant to be used for in the real-life app.) The clients now simply submit requests to send mails by calling a server method, the rest happens invisibly inside the server app.

    This alterative approach feels a bit less OO to me, but perhaps it's more client/server style and certainly it provides a higher level of encapsulation. Any comments?
    Last edited by Eri523; September 22nd, 2011 at 02:38 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  8. #8
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    10,958

    Re: Sharing objects between application instances using WCF

    Eri, check out my CG Tray Notify article series.

    It uses a WCF service hosted from a Windows Service application to communicate with a Tray Icon app running by different logged on users.

    It also uses two-way binding to send events to the tray icon clients.

  9. #9
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Arjay, thanks for pointing me to those articles. They were quite instructive, in particular part III of course. And your design with the singular exposed server object holding a number of Folder instances resembles the design I plan to use in my real-life app to a surprising extent.

    Some things pointed out in your articles clarified things I've read on MSDN, while others made me question some things I thought I had understood about WCF. In particular this one: Does setting the InstanceContextMode of my service contract implementation to Single really extend its lifetime to the entire time the hosting ServiceHost remains open? IOW is the lifetime of an instance of the service class not subject to be limited by any of the WCF standard timeouts under these circumstances? In that case I'd need to implement my own "self destruction mechanism" since I want my service instance and in turn the server app instance to auto-shutdown a certain time after the last client instance has detached. And I'm currently uncertain how to do that.

    I see, however, two differenes between our two scenarios that don't let it look reasonable to make my server a Windows Service: My service instance is neither meant to be shared between distinct users, nor to outlive the last client instance by a really significant timespan.

    And, mainly out of curiosity: Are all the project and class templates you use in the articles, in particular the one for a Windows Service, actually supported by the C# Express Edition as well?

    Right now I'm working on the WCF implementation of my test scenario and I think it'll be ready for posting about tomorrow. And it'll probably be a bit more refined than the one from the Remoting thread, i.e. more inclined towards the real-life scenario.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  10. #10
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Quote Originally Posted by Eri523 View Post
    Right now I'm working on the WCF implementation of my test scenario and I think it'll be ready for posting about tomorrow. And it'll probably be a bit more refined than the one from the Remoting thread, i.e. more inclined towards the real-life scenario.
    Ok, it took a bit longer, but here it is. And it is at least one step further from the Remoting implementation. It's about 30&#37; more code than that, but that's not due to increased complexity in handling WCF, rather a result of much more functionality being implemented. In particular it now has its own timeout mechanism, since I now think what I need can't be implemented based on WCF's own timeout mechanism with reasonable effort. Actually, from a framework design perspective this is already pretty close to how I plan it for the real-life app, however, of course it doesn't have any noticable business logic.

    Here's the header and implementation files of the core server class and a few helper classes:

    Code:
    // Server.h (Test Server)
    
    #pragma once
    
    #using "System.ServiceModel.dll"
    
    namespace TestServer
    {
    using namespace System;
    using namespace System::Threading;
    using namespace System::ServiceModel;
    using namespace System::Collections::Generic;
    
    private enum class ClientMirrorStatus { Active, Detached, TimedOut };
    
    private ref class ClientMirrorImage
    {
    public:
      ClientMirrorImage(int nInstanceNumber, Timers::Timer ^tmrHeartbeat);
    
      void AssertActive(bool bResetTimeout);
      void Detach();
      bool IsActive();
    
      virtual String ^ToString() override;
    
    private:
      void HeartbeatHandler(Object ^sender, Timers::ElapsedEventArgs ^e);
      void DetachFromHeartbeat();
    
    private:
      initonly int m_nInstanceNumber;
      Timers::Timer ^m_tmrHeartbeat;
      int m_nTimeout;
      ClientMirrorStatus m_status;
    };
    
    [ServiceContract(SessionMode=SessionMode::Required)]
    public interface class ITestServer
    {
      [OperationContract]
      int GetClientInstanceNumber();
    
      [OperationContract]
      String ^GetSharedData(int nInstanceNumber);
    
      [OperationContract]
      void SetSharedData(int nInstanceNumber, String ^strData);
    
      [OperationContract]
      void Detach(int nInstanceNumber);
    };
    
    [ServiceBehavior(InstanceContextMode=InstanceContextMode::Single)]
    public ref class Server : public ITestServer
    {
    public:
      Server();
      ~Server();
    
      // Service contract interface method implementation prototypes
    
      virtual int GetClientInstanceNumber();
      virtual String ^GetSharedData(int nInstanceNumber);
      virtual void SetSharedData(int nInstanceNumber, String ^strData);
      virtual void Detach(int nInstanceNumber);
    
    internal:
      static bool Startup(Action ^dltShutdownRequestCallback, Action<int> ^dltStatusReportCallback);
      static void Shutdown();
      static int GetServerObjectCount();
      static array<String ^> ^GetInstanceInfo();
      static bool IsPrimaryServerInstanceIdle();
    
    private:
      bool IsIdle();
      array<String ^> ^GetInstanceInfoCore();
      void HeartbeatHandler(Object ^sender, Timers::ElapsedEventArgs ^e);
    
    private:
      static EventWaitHandle ^s_evtServerLaunched = nullptr;
      static Action ^s_dltShutdownRequestCallback = nullptr;
      static Action<int> ^s_dltStatusReportCallback = nullptr;
      static ServiceHost ^s_host = nullptr;
      static int s_nServerObjectCount = 0;
      static Server ^s_svrPrimaryServerInstance = nullptr;
    
      int m_nClientInstance;
      String ^m_strSharedData;
      List<ClientMirrorImage ^> ^m_lstClientRegistry;
      Timers::Timer ^m_tmrHeartbeat;
      int m_nTimeout;
    };
    
    }
    Code:
    // Server.cpp (Test Server)
    
    #include "StdAfx.h"
    
    #include "Server.h"
    
    using namespace System::Runtime::CompilerServices;
    
    using namespace TestServer;
    
    // ClientMirrorImage methods
    
    ClientMirrorImage::ClientMirrorImage(int nInstanceNumber, Timers::Timer ^tmrHeartbeat) :
      m_nInstanceNumber(nInstanceNumber), m_tmrHeartbeat(tmrHeartbeat), m_nTimeout(60000), m_status(ClientMirrorStatus::Active)
    {
      tmrHeartbeat->Elapsed += gcnew Timers::ElapsedEventHandler(this, &ClientMirrorImage::HeartbeatHandler);
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    String ^ClientMirrorImage::ToString()
    {
      String ^str = String::Format("Client #{0}\t{1}", m_nInstanceNumber, m_status);
      if (m_status == ClientMirrorStatus::Active)
        str += String::Format(", timeout {0} seconds", m_nTimeout / 1000);
      return str;
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    void ClientMirrorImage::HeartbeatHandler(Object ^sender, Timers::ElapsedEventArgs ^e)
    {
      if (m_status == ClientMirrorStatus::Active && m_nTimeout > 0) {
        m_nTimeout -= m_tmrHeartbeat->Interval;
        if (m_nTimeout <= 0) {
          m_status = ClientMirrorStatus::TimedOut;
          DetachFromHeartbeat();
        }
      }
    }
    
    void ClientMirrorImage::DetachFromHeartbeat()
    {
      if (m_tmrHeartbeat) {
        m_tmrHeartbeat->Elapsed -= gcnew Timers::ElapsedEventHandler(this, &ClientMirrorImage::HeartbeatHandler);
        m_tmrHeartbeat = nullptr;
      }
    }
    
    // Asserts that the object is active and optionally resets the timeout
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    void ClientMirrorImage::AssertActive(bool bResetTimeout)
    {
      if (!IsActive())
        throw gcnew InvalidOperationException(String::Format("Attempted to access inactive ClientMirrorImage object (#{0})",
          m_nInstanceNumber));
      if (bResetTimeout)
        m_nTimeout = 60000;
    }
    
    void ClientMirrorImage::Detach()
    {
      DetachFromHeartbeat();
      if (IsActive())
        m_status = ClientMirrorStatus::Detached;
    }
    
    bool ClientMirrorImage::IsActive()
    {
      return m_status == ClientMirrorStatus::Active;
    }
    
    // Server methods
    
    Server::Server() : m_nClientInstance(0), m_strSharedData(String::Empty),
      m_lstClientRegistry(gcnew List<ClientMirrorImage ^>), m_tmrHeartbeat(gcnew Timers::Timer(1000)), m_nTimeout(60000)
    {
      if (!s_svrPrimaryServerInstance) s_svrPrimaryServerInstance = this;
      Interlocked::Increment(s_nServerObjectCount);
      m_tmrHeartbeat->Elapsed += gcnew Timers::ElapsedEventHandler(this, &Server::HeartbeatHandler);
      m_tmrHeartbeat->Start();
    }
    
    Server::~Server()
    {
      Interlocked::Decrement(s_nServerObjectCount);
    }
    
    array<String ^> ^Server::GetInstanceInfo()
    {
      return s_svrPrimaryServerInstance ? s_svrPrimaryServerInstance->GetInstanceInfoCore() :
        gcnew array<String ^>{"No server object instantiated yet"};
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    array<String ^> ^Server::GetInstanceInfoCore()
    {
      List<String ^> ^lst = gcnew List<String ^>;
      String ^str = "Server\t";
      if (IsIdle()) {
        if (m_nTimeout > 0)
          str += String::Format("Idle, timeout {0} seconds", m_nTimeout / 1000);
        else {
          str += "TimedOut";
          if (s_dltShutdownRequestCallback) str += ", shutting down...";
        }
      } else str += "Active";
      lst->Add(str);
      for each (ClientMirrorImage ^cmi in m_lstClientRegistry)
        lst->Add(cmi->ToString());
      return lst->ToArray();
    }
    
    bool Server::IsIdle()
    {
      if (!m_lstClientRegistry->Count) return true;
      int i = 0;
      while (i < m_lstClientRegistry->Count && !m_lstClientRegistry[i]->IsActive()) ++i;
      return i >= m_lstClientRegistry->Count;
    }
    
    bool Server::IsPrimaryServerInstanceIdle()
    {
      return !s_svrPrimaryServerInstance || s_svrPrimaryServerInstance->IsIdle();
    }
    
    [MethodImpl(MethodImplOptions::Synchronized)]
    void Server::HeartbeatHandler(Object ^sender, Timers::ElapsedEventArgs ^e)
    {
      if (IsIdle()) {
        if (m_nTimeout > 0) {
          if ((m_nTimeout -= m_tmrHeartbeat->Interval) <= 0 && s_dltShutdownRequestCallback)
            s_dltShutdownRequestCallback->BeginInvoke(nullptr, nullptr);
        }
      } else m_nTimeout = 60000;
    }
    
    int Server::GetServerObjectCount()
    {
      return s_nServerObjectCount;
    }
    
    // Service contract interface method implementations
    
    int Server::GetClientInstanceNumber()
    {
      m_lstClientRegistry->Add(gcnew ClientMirrorImage(++m_nClientInstance, m_tmrHeartbeat));
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      return m_nClientInstance;
    }
    
    String ^Server::GetSharedData(int nInstanceNumber)
    {
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      m_lstClientRegistry[nInstanceNumber - 1]->AssertActive(true);
      return m_strSharedData;
    }
    
    void Server::SetSharedData(int nInstanceNumber, String ^strData)
    {
      if (s_dltStatusReportCallback) s_dltStatusReportCallback(Thread::CurrentThread->ManagedThreadId);
      m_lstClientRegistry[nInstanceNumber - 1]->AssertActive(true);
      m_strSharedData = strData;
    }
    
    void Server::Detach(int nInstanceNumber)
    {
      m_lstClientRegistry[nInstanceNumber - 1]->Detach();
    }
    
    // To be called upon start-up of the supposedly singular primary server .exe instance,
    // returns true if no other instance is running yet, i.e. the "launched" event was not set:
    
    bool Server::Startup(Action ^dltShutdownRequestCallback, Action<int> ^dltStatusReportCallback)
    {
      EventWaitHandle ^evt = gcnew EventWaitHandle(false, EventResetMode::ManualReset, "WcfShareTestServerLaunchedEvent");
      if (evt->WaitOne(0)) {
        evt->Close();
        return false;  // Start-up failed: primary instance already running
      }
      s_evtServerLaunched = evt;
      s_dltShutdownRequestCallback = dltShutdownRequestCallback;
      s_dltStatusReportCallback = dltStatusReportCallback;
      s_host = gcnew ServiceHost(Server::typeid, gcnew Uri("net.pipe://localhost/TestServer"));
      s_host->Open();
      evt->Set();
      return true;
    }
    
    // To be called upon shutdown of the singular server .exe instance:
    
    void Server::Shutdown()
    {
      if (s_evtServerLaunched) {
        s_evtServerLaunched->Reset();
        s_evtServerLaunched->Close();
      }
      if (s_host) s_host->Close();
    }
    I have kept the basic design with the server start-up and shutdown methods, but there's a bunch more things happening between these two invocations now. As before, everything regarding the server begins in the server app's main form class' constructor and ends in its destructor:

    Code:
      public:
        Form1(void)  // Test Server
        {
          InitializeComponent();
          //
          //TODO: Konstruktorcode hier hinzuf&#252;gen.
          //
    
          if (Server::Startup(gcnew Action(this, &Form1::ServerShutdownRequestCallback),
            gcnew Action<int>(this, &Form1::ServerStatusReportCallback)))
          {
            timer1->Start();
          } else {
            Text += " (non-primary)";
            MessageBox::Show("There's already an instance of the server running. Please use that.",
              Text, MessageBoxButtons::OK, MessageBoxIcon::Exclamation);
          }
          lblGuiThreadId->Text = String::Format("GUI thread ID: {0}", Thread::CurrentThread->ManagedThreadId);
        }
    
      protected:
        /// <summary>
        /// Verwendete Ressourcen bereinigen.
        /// </summary>
        ~Form1()  // Test Server
        {
          timer1->Stop();
          Server::Shutdown();
    
          if (components)
          {
            delete components;
          }
        }
    I'd say the implementation file of that form is rather unspectacular, so I leave it out here. But of course it's contained in the attached project.

    The two main differences to the Remoting implementation, from the client point of view, are that the client now handles starting up the server app if needed mostly independent of the server itself, and that the client now keeps the client instance number obtained from the server and uses it as a handle to identify itself to the server when invoking its methods later. These are c'tor and d'tor of the client's main form:

    Code:
      public:
        Form1(void)  // Test Client
        {
          InitializeComponent();
          //
          //TODO: Konstruktorcode hier hinzuf&#252;gen.
          //
    
          lblGuiThreadId->Text = String::Format("GUI thread ID: {0}", Thread::CurrentThread->ManagedThreadId);
          bool bClientObjectValid = false;
          try {
            EventWaitHandle ^evt = gcnew EventWaitHandle(false, EventResetMode::ManualReset, "WcfShareTestServerLaunchedEvent");
            if (!evt->WaitOne(0)) {  // This means the server isn't running yet, so start it
              Process ^proc = Process::Start(Path::Combine(Application::StartupPath, "TestServer.exe"));
              Trace::Assert(proc != nullptr, "WCF Share Test: Failed to launch TestServer");
              Trace::Assert(evt->WaitOne(10000), "WCF Share Test: Wait for TestServer start-up timed out");
              proc->Close();
            }
            evt->Close();
            m_client = gcnew TestServer::TestServerClient;
            m_nInstanceNumber = m_client->GetClientInstanceNumber();
            Text += String::Format(" #{0}", m_nInstanceNumber);
          }
          catch (Exception ^e) {
            MessageBox::Show(String::Format("Client object instantiation or server connection failed: {0}", e),
              Text, MessageBoxButtons::OK, MessageBoxIcon::Error);
            groupBox1->Enabled = false;
          }
        }
    
      protected:
        /// <summary>
        /// Verwendete Ressourcen bereinigen.
        /// </summary>
        ~Form1()  // Test Client
        {
          if (m_client && m_client->State != CommunicationState::Faulted) {
            m_client->Detach(m_nInstanceNumber);
            m_client->Close();
          }
    
          if (components)
          {
            delete components;
          }
        }
    The implementation file of this form class is big-time boring so I omit that as well.

    The two app.config files aren't really thrilling either, but they're essential to the operation of the client/server pair and they're pretty short, so here's TestServer.exe.config:

    Code:
    <configuration>
      <system.serviceModel>
        <services>
          <service name="TestServer.Server" behaviorConfiguration="debug">
            <endpoint address="net.pipe://localhost/TestServer"
                      binding="netNamedPipeBinding"
                      contract="TestServer.ITestServer" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="debug">
              <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    </configuration>
    ... and WcfShareTest.exe.config:

    Code:
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.serviceModel>
            <client>
                <endpoint binding="netNamedPipeBinding"
                    contract="TestServer.ITestServer" name="DefaultBinding_ITestServer_ITestServer" address="net.pipe://localhost/TestServer" />
            </client>
        </system.serviceModel>
    </configuration>
    I have attached a zipped project to this post as well. I have cleaned the project before zipping it, still it contains a non-empty Debug output directory. The files in there are the two app.config files, along with the original output files generated by svcutil for reference. As for the Remoting implementation, I've also attached a screen shot of the server with two attached client instances.

    As usual, I'll appreciate any comments on the design and implementation. Of particular interest IMO is the synchronization aspect here. I think the inter-thread syncronization looks uncritical: The server's hearbeat handler is invoked on a different thread, so I synchronized any method accessing the timeout counters (members m_nTimeout of both the server and client mirror image class), in particular the heartbeat handlers themselves, using the "quick and dirty" MethodImplAttribute approach. Or did I overlook something here?

    Regarding the inter-process synchronization I'm still concerned about potential race conditions during server start-up and shutdown. While I have always started the client instances manually during my tests so far, allowing for a comfortable time span between two instance starts, the real-life app will often be started on schedule, making close-to-simultaneous client instance starts far more likely, so I think the server start-up should be of particular focus.
    Attached Images Attached Images  
    Attached Files Attached Files
    Last edited by Eri523; October 2nd, 2011 at 09:25 PM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  11. #11
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Almost one week later and no objections? Looks like my code isn't too bad... At any rate, here's another version to which I added extensive logging. I initially did that to be myself able to inspect what the code does closer to realtime, but I decided to also post it here for test and discussion.

    Actually, there's only one change to the Server class that's not related to logging: Two new lines in the d'tor that stop the heartbeat timer and detatch the object's handler from the timer's Elapsed event. Obviously, not having them in the original code was a mistake which was unveiled during early tests with the logging version of the code: One time the heartbeat tick handler got called 170 ms after Server::Shutdown() returned, i.e. at a point in time when the server object already has been long time destroyed. And perhaps that would've even got through unrecognized if it hadn't given rise to an assertion failure in the logger module, thereby, as a side effect, reveiling a bug in there as well...

    To be able to get timestamps that are half-way consistent between the distinct processes involved, the logger has a feature I called "hi-res synchronization" that gets set up in the TimestampSource c'tor, is activated by explicitly passing a set LogFlags::HiResSync to the logger instance getter when calling it for the first time, and IMO is somewhat hackish...

    The log entries aren't written to the log file in a proper chronological sequence by the logger module. Properly sorting the log by timestamp, which of course is important in the scenario here, is done by the external tool logsortts that I have attached as well. It's a simple cosole tool and the respective ZIP only contains the single .cpp file.

    The third attached file, SampleLog.zip, contains two logs taken from client/server sessions. Both sessions were quite similar to what the screen shot in the last post was taken from and lasted less than one minute. Yet they both contain 1k+ lines of log, from which clearly can be concluded that the log currently generated by the test scenario can pretty quickly mount up to some 100 kB. I'll certainly have much less logging later in the real-life app, and probably will reduce the amount of logging done in the test setup as well at some point.

    While the log in the sample WcfShareTest.log was generated by manually starting the server and then two clients, SimulStart.log was created by quasi-simultaneously starting two client instances from a trivial batch file, without the server already running at that time. This was intended to stress-test the server start-up scenario. The test exposed exactly the desired behaviour: Eventually there was one server instance running with two clients attached. What pretty much puzzles me, though, is how that happened. According to the log both clients enter the "Starting server app..." part of the code within a time span of 17 ms from each other, start to wait for server start-up with a distance of 15 ms and eventually report a successful server start within 4 ms. Yet there's just one server instance running. I don't think there was a second server instance for a short time that then died silently. Not only that the log doesn't report anything like that, the server also has intentionally been written to produce noticable noise when there already is an instance of it running. This is a sub-scenario I was much concerned about and I'm glad it works, but I would also love to know why and how it works...

    Note that the enclosed MiniLog module actually is a "light version" of a bigger logger module (about twice as much code lines) I commonly use in my apps. The test scenario code is prepared to optionally use the full-fledged logger module, but that's not included. Also, the two sample logs actually have been created by the big logger and I just removed some info I didn't intend to post publicly, but those produced by MiniLog are practically identical in layout.
    Attached Files Attached Files
    Last edited by Eri523; October 1st, 2011 at 05:52 PM. Reason: Re-added attachments. No idea where they ended up the first time.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  12. #12
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    10,958

    Re: Sharing objects between application instances using WCF

    Quote Originally Posted by Eri523 View Post
    While the log in the sample WcfShareTest.log was generated by manually starting the server and then two clients, SimulStart.log was created by quasi-simultaneously starting two client instances from a trivial batch file, without the server already running at that time. This was intended to stress-test the server start-up scenario. The test exposed exactly the desired behaviour: Eventually there was one server instance running with two clients attached. What pretty much puzzles me, though, is how that happened. According to the log both clients enter the "Starting server app..." part of the code within a time span of 17 ms from each other, start to wait for server start-up with a distance of 15 ms and eventually report a successful server start within 4 ms. Yet there's just one server instance running. I don't think there was a second server instance for a short time that then died silently.
    I think you have some issues with regard to the use of the manualset event.

    I see it's a manual reset named event, but I don't see where you set it or reset it. If you don't ever set it, then won't WaitOne(0) always false?

    At any rate, you might consider using a named mutex to protect the server initialization code.

    Code:
    bool mutexWasCreated;
    
    Mutex^ m = gcnew Mutex( false, "WcfShareTestServerLaunchedEvent", mutexWasCreated );
    
    // Block other processes
    m->WaitOne( );
    
    // Start up the server
    if( mutexWasCreated )
    {
      // Startup up WCF server
    }
    
    // Allow access to other processes
    m->ReleaseMutex( );
    The code above should block additional processes from attempting to access the server while its initializing.

  13. #13
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Thanks for your opinion.

    Quote Originally Posted by Arjay View Post
    I think you have some issues with regard to the use of the manualset event.
    I'd say the prevalent issue here is that (and why) I didn't encounter any issue in the SimulStart run documented in the log.

    I see it's a manual reset named event, but I don't see where you set it or reset it. If you don't ever set it, then won't WaitOne(0) always false?
    It gets set and reset in these two static Server methods that are called from the server form class' c'tor and d'tor (lines highlighted in red):

    Code:
    // To be called upon start-up of the supposedly singular primary server .exe instance,
    // returns true if no other instance is running yet, i.e. the "launched" event was not set:
    
    bool Server::Startup(Action ^dltShutdownRequestCallback, Action<int> ^dltStatusReportCallback)
    {
      LOG_FUNCTION_ENTRY
      EventWaitHandle ^evt = gcnew EventWaitHandle(false, EventResetMode::ManualReset, "WcfShareTestServerLaunchedEvent");
      if (evt->WaitOne(0)) {
        evt->Close();
        Log::GetInstance()->WriteLine("Leaving " __FUNCSIG__ " (failure)");
        return false;  // Start-up failed: primary instance already running
      }
      s_evtServerLaunched = evt;
      s_dltShutdownRequestCallback = dltShutdownRequestCallback;
      s_dltStatusReportCallback = dltStatusReportCallback;
      s_host = gcnew ServiceHost(Server::typeid, gcnew Uri("net.pipe://localhost/TestServer"));
      s_host->Open();
      evt->Set();
      Log::GetInstance()->WriteLine("Leaving " __FUNCSIG__ " (success)");
      return true;
    }
    
    // To be called upon shutdown of the singular server .exe instance:
    
    void Server::Shutdown()
    {
      LOG_FUNCTION_ENTRY
      if (s_evtServerLaunched) {
        s_evtServerLaunched->Reset();
        s_evtServerLaunched->Close();
      }
      if (s_host) s_host->Close();
      LOG_FUNCTION_EXIT
    }
    At any rate, you might consider using a named mutex to protect the server initialization code.

    Code:
    bool mutexWasCreated;
    
    Mutex^ m = gcnew Mutex( false, "WcfShareTestServerLaunchedEvent", mutexWasCreated );
    
    // Block other processes
    m->WaitOne( );
    
    // Start up the server
    if( mutexWasCreated )
    {
      // Startup up WCF server
    }
    
    // Allow access to other processes
    m->ReleaseMutex( );
    The code above should block additional processes from attempting to access the server while its initializing.
    I'd say that's functionally equivalent or at least quite similar to what I actually used in the Remoting test scenario (and almost literally identical to what I currently use in the real-life app to protect the SMTP server/user name pair). However, I always found the way I test for a server instance already running during client start-up (in Server::GetInstance()) a bit awkward.

    For the dual use it has here, i.e. both protecting the singular server instance and indicating/signaling that the server runs on the client side, I found the solution using an event instead of the mutex more straightforward and elegant. One particular advantage of the event here is that, unlike a mutex, it gets signaled when it is set, not when it is released. And that allowed me to use the following code in the client start-up:

    Code:
            EventWaitHandle ^evt = gcnew EventWaitHandle(false, EventResetMode::ManualReset, "WcfShareTestServerLaunchedEvent");
            if (!evt->WaitOne(0)) {  // This means the server isn't running yet, so start it
              log->WriteLine("Starting server app...");
              Process ^proc = Process::Start(Path::Combine(Application::StartupPath, "TestServer.exe"));
              Trace::Assert(proc != nullptr, "WCF Share Test: Failed to launch TestServer");
              log->WriteLine("Waiting for server start-up...");
              Trace::Assert(evt->WaitOne(10000), "WCF Share Test: Wait for TestServer start-up timed out");
              proc->Close();
              log->WriteLine("Server app started");
            }
            evt->Close();
    The main reason why handling it that way became necessary was that, unlike Remoting, WCF doesn't tolerate an attempt to connect to the server when that hasn't finished start-up yet, so I was in need of a way to wait for server start-up to finish.

    One additional thing your post and the thoughts I had while writing this post made me consider is, though, whether it might be a good idea to protect the phase from the creation of the event object to the first highlighted line in the first code snippet above by an additional global mutex.

    BTW, can't lines 3 to 6 of your code snippet be contracted to:

    Code:
    Mutex^ m = gcnew Mutex( true, "WcfShareTestServerLaunchedEvent", mutexWasCreated );
    Last edited by Eri523; October 2nd, 2011 at 06:21 AM.
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

  14. #14
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    10,958

    Re: Sharing objects between application instances using WCF

    Quote Originally Posted by Eri523 View Post
    BTW, can't lines 3 to 6 of your code snippet be contracted to:
    Code:
    Mutex^ m = gcnew Mutex( true, "WcfShareTestServerLaunchedEvent", mutexWasCreated );
    No. The explicit WaitOne() and ReleaseMutex() calls are needed to synchronize additional clients while the server is starting up.

    Consider the following code where a 10 second sleep is used to simulate the server startup.

    Code:
    using namespace System;
    using namespace System::Threading;
    using namespace System::Diagnostics;
    
    int main(array<System::String ^> ^args)
    {
    	bool mutexWasCreated;
    
    	Console::WriteLine( System::String::Format( L"Starting client: {0}", DateTime::Now ) );
    
    	Mutex^ m = gcnew Mutex( false, "WcfShareTestServerLaunchedEvent", mutexWasCreated );
    
    	// Block other processes until startup has completed
    	m->WaitOne( );
    
    	// Start up the server
    	if( mutexWasCreated )
    	{
    		Console::WriteLine( System::String::Format( L"Starting server - pid: {0}", Process::GetCurrentProcess( )->Id ) );
    
    		// Simulate startup up of WCF server (pause 10 seconds)
    		Thread::Sleep( 10000 );
    	}
    
    	// Allow access to other processes after startup has finished
    	m->ReleaseMutex( );
    
    	Console::WriteLine( System::String::Format( L"Using server - pid: {0}", Process::GetCurrentProcess( )->Id ) );
    	Console::WriteLine( System::String::Format( L"Client has access: {0}", DateTime::Now ) );
    
    
    	Console::ReadLine( );
    
        return 0;
    }
    In the Visual Studio IDE, open multiple instances by clicking "Debug\Start without debugging" (or Ctrl+F5).

    Notice the timings of "Client has access". You'll see that clients other than the first instance have to wait 10 seconds before getting access.

    If you set the change the TakeOwnership to true during the mutex creation and remove the calls to WaitOne and ReleaseMutex, you'll see the "Client has access" appear immediately, which proves the clients aren't waiting for the server startup.

    Output (with WaitOne and ReleaseMutex calls):
    Attached Images Attached Images  

  15. #15
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,571

    Re: Sharing objects between application instances using WCF

    Quote Originally Posted by Arjay View Post
    No. The explicit WaitOne() and ReleaseMutex() calls are needed to synchronize additional clients while the server is starting up.
    OIC, I think I got the point now. Guess I was misled by this comment line in your code:

    Code:
    	// Block other processes until startup has completed
    	m->WaitOne( );
    I'd say to block other processes requesting initial ownership during construction of the mutex is enough, yet not to block this process.

    Thank you not only for the explanation, but in particular for that illustrative demo code that allowed me to instantly and easily check out some variations.

    Also, the simple structure of your sample inspired me how to protect server start-up in my test code with a mutex. Now it looks like this:

    Code:
    // To be called upon start-up of the supposedly singular primary server .exe instance,
    // returns true if no other instance is running yet, i.e. the "launched" event was not set:
    
    bool Server::Startup(Action ^dltShutdownRequestCallback, Action<int> ^dltStatusReportCallback)
    {
      LOG_FUNCTION_ENTRY
      Log ^log = Log::GetInstance();
      bool bIMTheOne;
      log->WriteLine("Acquiring server start-up mutex...");
      Mutex ^mtxStartup = gcnew Mutex(true, "WcfShareTestServerStartupMutex", bIMTheOne);
      if (!bIMTheOne) {
        log->WriteLine("Mutex acquisition failed");
        mtxStartup->Close();
        log->WriteLine("Leaving " __FUNCSIG__ " (failure)");
        return false;  // Start-up failed: another instance is already starting up
      }
      log->WriteLine("Mutex acquired");
      EventWaitHandle ^evt = gcnew EventWaitHandle(false, EventResetMode::ManualReset, "WcfShareTestServerLaunchedEvent");
      if (evt->WaitOne(0)) {
        evt->Close();
        mtxStartup->ReleaseMutex();
        mtxStartup->Close();
        log->WriteLine("Leaving " __FUNCSIG__ " (failure)");
        return false;  // Start-up failed: primary instance already running
      }
      s_evtServerLaunched = evt;
      s_dltShutdownRequestCallback = dltShutdownRequestCallback;
      s_dltStatusReportCallback = dltStatusReportCallback;
      s_host = gcnew ServiceHost(Server::typeid, gcnew Uri("net.pipe://localhost/TestServer"));
      s_host->Open();
      evt->Set();
      mtxStartup->ReleaseMutex();
      mtxStartup->Close();
      log->WriteLine("Leaving " __FUNCSIG__ " (success)");
      return true;
    }
    However, I'm using the initial ownership approach here, since I instantly fail start-up if the mutex can't be acquired: no need to wait. I think now it's really fool-proof, though, according to my observations from the SimulStart run (of which I still don't know how exactly that happened), perhaps the extra protection isn't even necessary. OTOH it doesn't really hurt to have it in the code either...
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Page 1 of 2 12 LastLast

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Azure Activities Information Page

Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center