//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2009-2011. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/interprocess for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef BOOST_INTERPROCESS_INTERMODULE_SINGLETON_COMMON_HPP
#define BOOST_INTERPROCESS_INTERMODULE_SINGLETON_COMMON_HPP

#if defined(_MSC_VER)&&(_MSC_VER>=1200)
#pragma once
#endif

#include <boost/interprocess/detail/config_begin.hpp>
#include <boost/interprocess/detail/workaround.hpp>

#include <boost/interprocess/detail/atomic.hpp>
#include <boost/interprocess/detail/os_thread_functions.hpp>
#include <boost/type_traits/type_with_alignment.hpp>
#include <boost/assert.hpp>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>
#include <sstream>

namespace boost{
namespace interprocess{
namespace ipcdetail{

namespace intermodule_singleton_helpers {

inline void get_pid_creation_time_str(std::string &s)
{
   std::stringstream stream;
   stream << get_current_process_id() << '_';
   stream.precision(6);
   stream << std::fixed << get_current_process_creation_time();
   s = stream.str();
}

inline const char *get_shm_base_name()
{  return "bip.gmem.shm.";  }

inline void get_shm_name(std::string &shm_name)
{
   get_pid_creation_time_str(shm_name);
   shm_name.insert(0, get_shm_base_name());
}

inline std::size_t get_shm_size()
{  return 65536;  }

template<class ManagedGlobalMemory>
struct managed_sh_dependant;

}  //namespace intermodule_singleton_helpers {

//This class contains common code for all singleton types, so that we instantiate this
//code just once per module. This class also holds a reference counted shared memory
//to be used by all instances

template<class ManagedGlobalMemory>
class intermodule_singleton_common
{
   public:
   typedef void*(singleton_constructor_t)(ManagedGlobalMemory &);
   typedef void (singleton_destructor_t)(void *, ManagedGlobalMemory &);

   static const ::boost::uint32_t Uninitialized       = 0u;
   static const ::boost::uint32_t Initializing        = 1u;
   static const ::boost::uint32_t Initialized         = 2u;
   static const ::boost::uint32_t Broken              = 3u;

   static void finalize_singleton_logic(void *ptr, singleton_destructor_t destructor)
   {
      //Protect destruction against lazy singletons not initialized in this execution
      if(ptr){
         destructor(ptr, get_shm());
         //If this is the last singleton of this module
         //apply shm destruction.
         //Note: singletons are destroyed when the module is unloaded
         //so no threads should be executing or holding references
         //to this module
         if(1 == atomic_dec32(&this_module_singleton_count)){
            destroy_shm();
         }
      }
   }

   static void initialize_singleton_logic
      (void *&ptr, volatile boost::uint32_t &this_module_singleton_initialized, singleton_constructor_t ini_func);

   private:
   static ManagedGlobalMemory &get_shm()
   {
      return *static_cast<ManagedGlobalMemory *>(static_cast<void *>(&mem_holder.shm_mem));
   }

   static void initialize_shm();
   static void destroy_shm();
   //Static data, zero-initalized without any dependencies
   //this_module_singleton_count is the number of singletons used by this module
   static volatile boost::uint32_t this_module_singleton_count;
   //this_module_shm_initialized is the state of this module's shm class object
   static volatile boost::uint32_t this_module_shm_initialized;
   static struct mem_holder_t
   {
      ::boost::detail::max_align aligner;
      char shm_mem [sizeof(ManagedGlobalMemory)];
   } mem_holder;
};

template<class ManagedGlobalMemory>
volatile boost::uint32_t intermodule_singleton_common<ManagedGlobalMemory>::this_module_singleton_count;

template<class ManagedGlobalMemory>
volatile boost::uint32_t intermodule_singleton_common<ManagedGlobalMemory>::this_module_shm_initialized;

template<class ManagedGlobalMemory>
typename intermodule_singleton_common<ManagedGlobalMemory>::mem_holder_t
   intermodule_singleton_common<ManagedGlobalMemory>::mem_holder;

template<class ManagedGlobalMemory>
void intermodule_singleton_common<ManagedGlobalMemory>::initialize_shm()
{
   //Obtain unique shm name and size
   std::string s;
   while(1){
      //Try to pass shm state to initializing
      ::boost::uint32_t tmp = atomic_cas32(&this_module_shm_initialized, Initializing, Uninitialized);
      if(tmp >= Initialized){
         break;
      }
      //If some other thread is doing the work wait
      else if(tmp == Initializing){
         thread_yield();
      }
      else{ //(tmp == Uninitialized)
         //If not initialized try it again?
         try{
            //Remove old shared memory from the system
            intermodule_singleton_helpers::managed_sh_dependant<ManagedGlobalMemory>::remove_old_gmem();
            //
            if(s.empty()){
               intermodule_singleton_helpers::get_shm_name(s);
            }
            const char *ShmName = s.c_str();
            const std::size_t ShmSize = intermodule_singleton_helpers::get_shm_size();;

            //in-place construction of the shared memory class
            ::new (&get_shm())ManagedGlobalMemory(open_or_create, ShmName, ShmSize);
            //Use shared memory internal lock to initialize the lock file
            //that will mark this gmem as "in use".
            typename intermodule_singleton_helpers::managed_sh_dependant<ManagedGlobalMemory>::
               lock_file_logic f(get_shm());
            //If function failed (maybe a competing process has erased the shared
            //memory between creation and file locking), retry with a new instance.
            if(f.retry_with_new_shm){
               get_shm().~ManagedGlobalMemory();
               atomic_write32(&this_module_shm_initialized, Uninitialized);
            }
            else{
               //Locking succeeded, so this shared memory module-instance is ready
               atomic_write32(&this_module_shm_initialized, Initialized);
               break;
            }
         }
         catch(...){
            //
            throw;
         }
      }
   }
}

template<class ManagedGlobalMemory>
void intermodule_singleton_common<ManagedGlobalMemory>::destroy_shm()
{
   if(!atomic_read32(&this_module_singleton_count)){
      //This module is being unloaded, so destroy
      //the shared memory object of this module
      //and unlink the shared memory if it's the last
      typename intermodule_singleton_helpers::managed_sh_dependant<ManagedGlobalMemory>::
         unlink_shmlogic f(get_shm());
      (get_shm()).~ManagedGlobalMemory();
      atomic_write32(&this_module_shm_initialized, Uninitialized);
      //Do some cleanup for other processes old gmem instances
      intermodule_singleton_helpers::managed_sh_dependant<ManagedGlobalMemory>::remove_old_gmem();
   }
}

//Initialize this_module_singleton_ptr, creates the shared memory if needed and also creates an unique
//opaque type in shared memory through a singleton_constructor_t function call,
//initializing the passed pointer to that unique instance.
//
//We have two concurrency types here. a)the shared memory/singleton creation must
//be safe between threads of this process but in different modules/dlls. b)
//the pointer to the singleton is per-module, so we have to protect this
//initization between threads of the same module.
//
//All static variables declared here are shared between inside a module
//so atomic operations will synchronize only threads of the same module.
template<class ManagedGlobalMemory>
void intermodule_singleton_common<ManagedGlobalMemory>::initialize_singleton_logic
   (void *&ptr, volatile boost::uint32_t &this_module_singleton_initialized, singleton_constructor_t constructor)
{
   //If current module is not initialized enter to lock free logic
   if(atomic_read32(&this_module_singleton_initialized) != Initialized){
      //Now a single thread of the module will succeed in this CAS.
      //trying to pass from Uninitialized to Initializing
      ::boost::uint32_t previous_module_singleton_initialized = atomic_cas32
         (&this_module_singleton_initialized, Initializing, Uninitialized);
      //If the thread succeeded the CAS (winner) it will compete with other 
      //winner threads from other modules to create the shared memory
      if(previous_module_singleton_initialized == Uninitialized){
         try{
            //Now initialize shm, this function solves concurrency issues
            //between threads of several modules
            initialize_shm();
            //Increment the module reference count that reflects how many
            //singletons this module holds, so that we can safely destroy
            //module shared memory object when no singleton is left
            atomic_inc32(&this_module_singleton_count);
            //Now try to create the singleton in shared memory.
            //This function solves concurrency issues
            //between threads of several modules
            void *tmp = constructor(get_shm());
            //Insert a barrier before assigning the pointer to
            //make sure this assignment comes after the initialization
            atomic_write32(&this_module_singleton_initialized, Initializing);
            //Assign the singleton address to the module-local pointer
            ptr = tmp;
            //Memory barrier inserted, all previous operations should complete
            //before this one. Now marked as initialized
            atomic_inc32(&this_module_singleton_initialized);
         }
         catch(...){
            //Mark singleton failed to initialize
            atomic_write32(&this_module_singleton_initialized, Broken);
            throw;
         }
      }
      //If previous state was initializing, this means that another winner thread is
      //trying to initialize the singleton. Just wait until completes its work.
      else if(previous_module_singleton_initialized == Initializing){
         while(1){
            previous_module_singleton_initialized = atomic_read32(&this_module_singleton_initialized);
            if(previous_module_singleton_initialized >= Initialized){
               //Already initialized, or exception thrown by initializer thread
               break;
            }
            else if(previous_module_singleton_initialized == Initializing){
               thread_yield();
            }
            else{
               //This can't be happening!
               BOOST_ASSERT(0);
            }
         }
      }
      else if(previous_module_singleton_initialized == Initialized){
         //Nothing to do here, the singleton is ready
      }
      //If previous state was greater than initialized, then memory is broken
      //trying to initialize the singleton.
      else{//(previous_module_singleton_initialized > Initialized)
         throw interprocess_exception("boost::interprocess::intermodule_singleton initialization failed");
      }
   }
   BOOST_ASSERT(ptr != 0);
}

//Now this class is a singleton, initializing the singleton in
//the first get() function call if LazyInit is false. If true
//then the singleton will be initialized when loading the module.
template<typename C, bool LazyInit, class ManagedGlobalMemory>
class intermodule_singleton_impl
{
   public:
   static C& get()   //Let's make inlining easy
   {
      if(!this_module_singleton_ptr){
         if(lifetime.dummy_function())  //This forces lifetime instantiation, for reference counted destruction
            intermodule_singleton_common<ManagedGlobalMemory>::initialize_singleton_logic
               (this_module_singleton_ptr, this_module_singleton_initialized, singleton_constructor);
      }
      return *static_cast<C*>(this_module_singleton_ptr);
   }

   private:

   struct ref_count_ptr
   {
      ref_count_ptr(C *p, boost::uint32_t count)
         : ptr(p), singleton_ref_count(count)
      {}
      C *ptr;
      //This reference count serves to count the number of attached
      //modules to this singleton
      volatile boost::uint32_t singleton_ref_count;
   };

   //These statics will be zero-initialized without any constructor call dependency
   //this_module_singleton_ptr will be a module-local pointer to the singleton
   static void*                      this_module_singleton_ptr;
   //this_module_singleton_count will be used to synchronize threads of the same module
   //for access to a singleton instance, and to flag the state of the
   //singleton.
   static volatile boost::uint32_t   this_module_singleton_initialized;

   //This class destructor will trigger singleton destruction
   struct lifetime_type_lazy
   {
      bool dummy_function()
      {  return m_dummy == 0; }

      ~lifetime_type_lazy()
      {
         intermodule_singleton_common<ManagedGlobalMemory>::finalize_singleton_logic
            (this_module_singleton_ptr, singleton_destructor);
      }
      //Dummy volatile so that the compiler can't resolve its value at compile-time
      //and can't avoid lifetime_type instantiation if dummy_function() is called.
      static volatile int m_dummy;
   };

   struct lifetime_type_static
      : public lifetime_type_lazy
   {
      lifetime_type_static()
      {
         intermodule_singleton_common<ManagedGlobalMemory>::initialize_singleton_logic
            (this_module_singleton_ptr, this_module_singleton_initialized, singleton_constructor);
      }
   };

   typedef typename if_c
      <LazyInit, lifetime_type_lazy, lifetime_type_static>::type lifetime_type;

   static lifetime_type lifetime;

   //A functor to be executed inside shared memory lock that just
   //searches for the singleton in shm and if not present creates a new one.
   //If singleton constructor throws, the exception is propagated
   struct init_atomic_func
   {
      init_atomic_func(ManagedGlobalMemory &m)
         : mshm(m)
      {}

      void operator()()
      {
         ref_count_ptr *rcount = mshm.template find<ref_count_ptr>(unique_instance).first;
         if(!rcount){
            C *p = new C();
            try{
               rcount = mshm.template construct<ref_count_ptr>(unique_instance)(p, 0u);
            }
            catch(...){
               delete p;
               throw;
            }
         }
         atomic_inc32(&rcount->singleton_ref_count);
         ret_ptr = rcount->ptr;
      }
      ManagedGlobalMemory &mshm;
      void *ret_ptr;
   };

   //A functor to be executed inside shared memory lock that just
   //deletes the singleton in shm if the attached count reaches to zero
   struct fini_atomic_func
   {
      fini_atomic_func(ManagedGlobalMemory &m)
         : mshm(m)
      {}

      void operator()()
      {
         ref_count_ptr *rcount = mshm.template find<ref_count_ptr>(unique_instance).first;
            //The object must exist
         BOOST_ASSERT(rcount);
         //Check if last reference
         if(atomic_dec32(&rcount->singleton_ref_count) == 1){
            //If last, destroy the object
            BOOST_ASSERT(rcount->ptr != 0);
            delete rcount->ptr;
            //Now destroy shm entry
            bool destroyed = mshm.template destroy<ref_count_ptr>(unique_instance);
            (void)destroyed;  BOOST_ASSERT(destroyed == true);
         }
      }
      ManagedGlobalMemory &mshm;
      void *ret_ptr;
   };

   //A wrapper to execute init_atomic_func
   static void *singleton_constructor(ManagedGlobalMemory &mshm)
   {
      init_atomic_func f(mshm);
      mshm.atomic_func(f);
      return f.ret_ptr;
   }

   //A wrapper to execute fini_atomic_func
   static void singleton_destructor(void *p, ManagedGlobalMemory &mshm)
   {  (void)p;
      fini_atomic_func f(mshm);
      mshm.atomic_func(f);
   }
};

template <typename C, bool L, class ManagedGlobalMemory>
volatile int intermodule_singleton_impl<C, L, ManagedGlobalMemory>::lifetime_type_lazy::m_dummy = 0;

//These will be zero-initialized by the loader
template <typename C, bool L, class ManagedGlobalMemory>
void *intermodule_singleton_impl<C, L, ManagedGlobalMemory>::this_module_singleton_ptr = 0;

template <typename C, bool L, class ManagedGlobalMemory>
volatile boost::uint32_t intermodule_singleton_impl<C, L, ManagedGlobalMemory>::this_module_singleton_initialized = 0;

template <typename C, bool L, class ManagedGlobalMemory>
typename intermodule_singleton_impl<C, L, ManagedGlobalMemory>::lifetime_type
   intermodule_singleton_impl<C, L, ManagedGlobalMemory>::lifetime;

}  //namespace ipcdetail{
}  //namespace interprocess{
}  //namespace boost{

#include <boost/interprocess/detail/config_end.hpp>

#endif   //#ifndef BOOST_INTERPROCESS_INTERMODULE_SINGLETON_COMMON_HPP
