diff options
Diffstat (limited to 'boost/interprocess/detail/intermodule_singleton.hpp')
-rw-r--r-- | boost/interprocess/detail/intermodule_singleton.hpp | 1184 |
1 files changed, 1184 insertions, 0 deletions
diff --git a/boost/interprocess/detail/intermodule_singleton.hpp b/boost/interprocess/detail/intermodule_singleton.hpp new file mode 100644 index 0000000000..4bffbe9d4e --- /dev/null +++ b/boost/interprocess/detail/intermodule_singleton.hpp @@ -0,0 +1,1184 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (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_HPP +#define BOOST_INTERPROCESS_INTERMODULE_SINGLETON_HPP + +#if defined(_MSC_VER)&&(_MSC_VER>=1200) +#pragma once +#endif + +#include <boost/interprocess/detail/config_begin.hpp> +#include <boost/interprocess/detail/workaround.hpp> + +#if defined(BOOST_INTERPROCESS_WINDOWS) +#include <boost/interprocess/windows_shared_memory.hpp> +#endif + +#include <boost/interprocess/shared_memory_object.hpp> + +#include <boost/interprocess/offset_ptr.hpp> +#include <boost/interprocess/sync/spin/mutex.hpp> +#include <boost/interprocess/sync/spin/recursive_mutex.hpp> +#include <boost/interprocess/detail/managed_memory_impl.hpp> +#include <boost/interprocess/detail/managed_open_or_create_impl.hpp> +#include <boost/interprocess/mem_algo/rbtree_best_fit.hpp> +#include <boost/interprocess/indexes/iset_index.hpp> +#include <boost/interprocess/creation_tags.hpp> +#include <boost/interprocess/permissions.hpp> + + +#include <boost/interprocess/detail/atomic.hpp> +#include <boost/interprocess/detail/os_thread_functions.hpp> +#include <boost/interprocess/detail/tmp_dir_helpers.hpp> +#include <boost/interprocess/detail/os_file_functions.hpp> +#include <boost/interprocess/detail/mpl.hpp> +#include <boost/type_traits/type_with_alignment.hpp> +#include <boost/assert.hpp> +#include <cstddef> +#include <cstdio> +#include <cstring> +#include <string> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> + +#if defined(BOOST_INTERPROCESS_WINDOWS) +#include <fcntl.h> +#include <io.h> + +#include <sys/locking.h> +#else +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> +#endif + +namespace boost{ +namespace interprocess{ +namespace ipcdetail{ + +struct intermodule_singleton_mutex_family +{ + typedef boost::interprocess::ipcdetail::spin_mutex mutex_type; + typedef boost::interprocess::ipcdetail::spin_recursive_mutex recursive_mutex_type; +}; + +struct intermodule_types +{ + //We must use offset_ptr since a loaded DLL can map the singleton holder shared memory + //at a different address than other DLLs/main executables + typedef rbtree_best_fit<intermodule_singleton_mutex_family, offset_ptr<void> > mem_algo; + template<class Device, bool FileBased> + struct open_or_create + { + typedef managed_open_or_create_impl + <Device, mem_algo::Alignment, FileBased> type; + }; +}; + +template<class Device, bool FileBased> +class basic_managed_global_memory + : public basic_managed_memory_impl + < char + , intermodule_types::mem_algo + , iset_index + , intermodule_types::open_or_create<Device, FileBased>::type::ManagedOpenOrCreateUserOffset + > + , private intermodule_types::open_or_create<Device, FileBased>::type +{ + /// @cond + typedef typename intermodule_types::template open_or_create<Device, FileBased>::type base2_t; + + typedef basic_managed_memory_impl + < char + , intermodule_types::mem_algo + , iset_index + , base2_t::ManagedOpenOrCreateUserOffset + > base_t; + + typedef create_open_func<base_t> create_open_func_t; + + basic_managed_global_memory *get_this_pointer() + { return this; } + + public: + typedef typename base_t::size_type size_type; + + private: + typedef typename base_t::char_ptr_holder_t char_ptr_holder_t; + BOOST_MOVABLE_BUT_NOT_COPYABLE(basic_managed_global_memory) + /// @endcond + + public: //functions +/* + basic_managed_global_memory() + {} + + basic_managed_global_memory(create_only_t create_only, const char *name, + size_type size, const void *addr = 0, const permissions& perm = permissions()) + : base_t() + , base2_t(create_only, name, size, read_write, addr, + create_open_func_t(get_this_pointer(), DoCreate), perm) + {} +*/ + basic_managed_global_memory (open_or_create_t open_or_create, + const char *name, size_type size, + const void *addr = 0, const permissions& perm = permissions()) + : base_t() + , base2_t(open_or_create, name, size, read_write, addr, + create_open_func_t(get_this_pointer(), + DoOpenOrCreate), perm) + {} + + basic_managed_global_memory (open_only_t open_only, const char* name, + const void *addr = 0) + : base_t() + , base2_t(open_only, name, read_write, addr, + create_open_func_t(get_this_pointer(), + DoOpen)) + {} + +/* + basic_managed_global_memory (open_copy_on_write_t, const char* name, + const void *addr = 0) + : base_t() + , base2_t(open_only, name, copy_on_write, addr, + create_open_func_t(get_this_pointer(), + DoOpen)) + {} + + //!Connects to a created shared memory and its segment manager. + //!in read-only mode. + //!This can throw. + basic_managed_global_memory (open_read_only_t, const char* name, + const void *addr = 0) + : base_t() + , base2_t(open_only, name, read_only, addr, + create_open_func_t(get_this_pointer(), + DoOpen)) + {} + + //!Moves the ownership of "moved"'s managed memory to *this. + //!Does not throw + basic_managed_global_memory(BOOST_RV_REF(basic_managed_global_memory) moved) + { + basic_managed_global_memory tmp; + this->swap(moved); + tmp.swap(moved); + } + + //!Moves the ownership of "moved"'s managed memory to *this. + //!Does not throw + basic_managed_global_memory &operator=(BOOST_RV_REF(basic_managed_global_memory) moved) + { + basic_managed_global_memory tmp(boost::move(moved)); + this->swap(tmp); + return *this; + }*/ +}; + +#if defined(BOOST_INTERPROCESS_WINDOWS) +typedef basic_managed_global_memory<windows_shared_memory, false> windows_managed_global_memory; +#endif + +typedef basic_managed_global_memory<shared_memory_object, true> managed_global_memory; + +namespace file_locking_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 void create_tmp_subdir_and_get_pid_based_filepath(const char *subdir_name, const char *file_prefix, OS_process_id_t pid, std::string &s, bool creation_time = false) +{ + //Let's create a lock file for each process gmem that will mark if + //the process is alive or not + create_tmp_and_clean_old(s); + s += "/"; + s += subdir_name; + if(!open_or_create_directory(s.c_str())){ + throw interprocess_exception(error_info(system_error_code())); + } + s += "/"; + s += file_prefix; + if(creation_time){ + std::string sstamp; + get_pid_creation_time_str(sstamp); + s += sstamp; + } + else{ + pid_str_t pid_str; + get_pid_str(pid_str, pid); + s += pid_str; + } +} + +inline bool check_if_filename_complies_with_pid + (const char *filename, const char *prefix, OS_process_id_t pid, std::string &file_suffix, bool creation_time = false) +{ + //Check if filename complies with lock file name pattern + std::string fname(filename); + std::string fprefix(prefix); + if(fname.size() <= fprefix.size()){ + return false; + } + fname.resize(fprefix.size()); + if(fname != fprefix){ + return false; + } + + //If not our lock file, delete it if we can lock it + fname = filename; + fname.erase(0, fprefix.size()); + pid_str_t pid_str; + get_pid_str(pid_str, pid); + file_suffix = pid_str; + if(creation_time){ + std::size_t p = fname.find('_'); + if (p == std::string::npos){ + return false; + } + std::string save_suffix(fname); + fname.erase(p); + fname.swap(file_suffix); + bool ret = (file_suffix == fname); + file_suffix.swap(save_suffix); + return ret; + } + else{ + fname.swap(file_suffix); + return (file_suffix == fname); + } +} + +} //file_locking_helpers + +namespace intermodule_singleton_helpers { + +const int GMemMarkToBeRemoved = -1; +const int GMemNotPresent = -2; + +inline const char *get_lock_file_subdir_name() +{ return "gmem"; } + +inline const char *get_lock_file_base_name() +{ return "lck"; } + +inline void create_and_get_singleton_lock_file_path(std::string &s) +{ + file_locking_helpers::create_tmp_subdir_and_get_pid_based_filepath + (get_lock_file_subdir_name(), get_lock_file_base_name(), get_current_process_id(), s, true); +} + +inline const char *get_shm_base_name() +{ return "bip.gmem.shm."; } + +inline void get_shm_name(std::string &shm_name) +{ + file_locking_helpers::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 ManagedShMem> +struct managed_sh_dependant +{ + static void apply_gmem_erase_logic(const char *filepath, const char *filename); + + static bool remove_old_gmem() + { + std::string refcstrRootDirectory; + tmp_folder(refcstrRootDirectory); + refcstrRootDirectory += "/"; + refcstrRootDirectory += get_lock_file_subdir_name(); + return for_each_file_in_dir(refcstrRootDirectory.c_str(), apply_gmem_erase_logic); + } +}; + +#if defined(BOOST_INTERPROCESS_WINDOWS) + +template<> +struct managed_sh_dependant<windows_managed_global_memory> +{ + static void apply_gmem_erase_logic(const char *, const char *){} + + static bool remove_old_gmem() + { return true; } +}; + + +struct locking_file_serial_id +{ + int fd; + unsigned long dwVolumeSerialNumber; + unsigned long nFileIndexHigh; + unsigned long nFileIndexLow; + //This reference count counts the number of modules attached + //to the shared memory and lock file. This serves to unlink + //the locking file and shared memory when all modules are + //done with the global memory (shared memory) + volatile boost::uint32_t modules_attached_to_gmem_count; +}; + +inline bool lock_locking_file(int fd) +{ + int ret = 0; + while(ret != 0 && errno == EDEADLK){ + ret = _locking(fd, _LK_LOCK, 1/*lock_file_contents_length()*/); + } + return 0 == ret; +} + +inline bool try_lock_locking_file(int fd) +{ + return 0 == _locking(fd, _LK_NBLCK , 1); +} + +inline int open_or_create_and_lock_file(const char *name) +{ + permissions p; + p.set_unrestricted(); + while(1){ + file_handle_t handle = create_or_open_file(name, read_write, p); + int fd = _open_osfhandle((intptr_t)handle, _O_TEXT); + if(fd < 0){ + close_file(handle); + return fd; + } + if(!try_lock_locking_file(fd)){ + _close(fd); + return -1; + } + struct _stat s; + if(0 == _stat(name, &s)){ + return fd; + } + else{ + _close(fd); + } + } +} + +inline int try_open_and_lock_file(const char *name) +{ + file_handle_t handle = open_existing_file(name, read_write); + int fd = _open_osfhandle((intptr_t)handle, _O_TEXT); + if(fd < 0){ + close_file(handle); + return fd; + } + if(!try_lock_locking_file(fd)){ + _close(fd); + return -1; + } + return fd; +} + +inline void close_lock_file(int fd) +{ _close(fd); } + +inline bool is_valid_fd(int fd) +{ + struct _stat s; + return EBADF != _fstat(fd, &s); +} + +inline bool is_normal_file(int fd) +{ + if(_isatty(fd)) + return false; + struct _stat s; + if(0 != _fstat(fd, &s)) + return false; + return 0 != (s.st_mode & _S_IFREG); +} + +inline std::size_t get_size(int fd) +{ + struct _stat s; + if(0 != _fstat(fd, &s)) + return 0u; + return (std::size_t)s.st_size; +} + +inline bool fill_file_serial_id(int fd, locking_file_serial_id &id) +{ + winapi::interprocess_by_handle_file_information info; + if(!winapi::get_file_information_by_handle((void*)_get_osfhandle(fd), &info)) + return false; + id.fd = fd; + id.dwVolumeSerialNumber = info.dwVolumeSerialNumber; + id.nFileIndexHigh = info.nFileIndexHigh; + id.nFileIndexLow = info.nFileIndexLow; + id.modules_attached_to_gmem_count = 1; //Initialize attached count + return true; +} + +inline bool compare_file_serial(int fd, const locking_file_serial_id &id) +{ + winapi::interprocess_by_handle_file_information info; + if(!winapi::get_file_information_by_handle((void*)_get_osfhandle(fd), &info)) + return false; + + return id.dwVolumeSerialNumber == info.dwVolumeSerialNumber && + id.nFileIndexHigh == info.nFileIndexHigh && + id.nFileIndexLow == info.nFileIndexLow; +} + +#else //UNIX + +struct locking_file_serial_id +{ + int fd; + dev_t st_dev; + ino_t st_ino; + //This reference count counts the number of modules attached + //to the shared memory and lock file. This serves to unlink + //the locking file and shared memory when all modules are + //done with the global memory (shared memory) + volatile boost::uint32_t modules_attached_to_gmem_count; +}; + +inline bool lock_locking_file(int fd) +{ + int ret = 0; + while(ret != 0 && errno != EINTR){ + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 1; + ret = fcntl (fd, F_SETLKW, &lock); + } + return 0 == ret; +} + +inline bool try_lock_locking_file(int fd) +{ + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 1; + return 0 == fcntl (fd, F_SETLK, &lock); +} + +inline int open_or_create_and_lock_file(const char *name) +{ + permissions p; + p.set_unrestricted(); + while(1){ + int fd = create_or_open_file(name, read_write, p); + if(fd < 0){ + return fd; + } + if(!try_lock_locking_file(fd)){ + close(fd); + return -1; + } + struct stat s; + if(0 == stat(name, &s)){ + return fd; + } + else{ + close(fd); + } + } +} + +inline int try_open_and_lock_file(const char *name) +{ + int fd = open_existing_file(name, read_write); + if(fd < 0){ + return fd; + } + if(!try_lock_locking_file(fd)){ + close(fd); + return -1; + } + return fd; +} + +inline void close_lock_file(int fd) +{ close(fd); } + +inline bool is_valid_fd(int fd) +{ + struct stat s; + return EBADF != fstat(fd, &s); +} + +inline bool is_normal_file(int fd) +{ + struct stat s; + if(0 != fstat(fd, &s)) + return false; + return 0 != (s.st_mode & S_IFREG); +} + +inline std::size_t get_size(int fd) +{ + struct stat s; + if(0 != fstat(fd, &s)) + return 0u; + return (std::size_t)s.st_size; +} + +inline bool fill_file_serial_id(int fd, locking_file_serial_id &id) +{ + struct stat s; + if(0 != fstat(fd, &s)) + return false; + id.fd = fd; + id.st_dev = s.st_dev; + id.st_ino = s.st_ino; + id.modules_attached_to_gmem_count = 1; //Initialize attached count + return true; +} + +inline bool compare_file_serial(int fd, const locking_file_serial_id &id) +{ + struct stat info; + if(0 != fstat(fd, &info)) + return false; + + return id.st_dev == info.st_dev && + id.st_ino == info.st_ino; +} + +#endif + +template<class ManagedShMem> +struct gmem_erase_func +{ + gmem_erase_func(const char *shm_name, const char *singleton_lock_file_path, ManagedShMem & shm) + :shm_name_(shm_name), singleton_lock_file_path_(singleton_lock_file_path), shm_(shm) + {} + + void operator()() + { + locking_file_serial_id *pserial_id = shm_.template find<locking_file_serial_id>("lock_file_fd").first; + if(pserial_id){ + pserial_id->fd = GMemMarkToBeRemoved; + } + delete_file(singleton_lock_file_path_); + shared_memory_object::remove(shm_name_); + } + + const char * const shm_name_; + const char * const singleton_lock_file_path_; + ManagedShMem & shm_; +}; + +//This function applies shared memory erasure logic based on the passed lock file. +template<class ManagedShMem> +void managed_sh_dependant<ManagedShMem>:: + apply_gmem_erase_logic(const char *filepath, const char *filename) +{ + int fd = GMemMarkToBeRemoved; + try{ + std::string str; + //If the filename is current process lock file, then avoid it + if(file_locking_helpers::check_if_filename_complies_with_pid + (filename, get_lock_file_base_name(), get_current_process_id(), str, true)){ + return; + } + //Open and lock the other process' lock file + fd = try_open_and_lock_file(filepath); + if(fd < 0){ + return; + } + //If done, then the process is dead so take global shared memory name + //(the name is based on the lock file name) and try to apply erasure logic + str.insert(0, get_shm_base_name()); + try{ + ManagedShMem shm(open_only, str.c_str()); + gmem_erase_func<ManagedShMem> func(str.c_str(), filepath, shm); + shm.try_atomic_func(func); + } + catch(interprocess_exception &e){ + //If shared memory is not found erase the lock file + if(e.get_error_code() == not_found_error){ + delete_file(filepath); + } + } + } + catch(...){ + + } + if(fd >= 0){ + close_lock_file(fd); + } +} + +} //namespace intermodule_singleton_helpers { + + + +namespace intermodule_singleton_helpers { + +//The lock file logic creates uses a unique instance to a file +template <class ManagedShMem> +struct lock_file_logic +{ + lock_file_logic(ManagedShMem &shm) + : mshm(shm) + { shm.atomic_func(*this); } + + void operator()(void) + { + retry_with_new_shm = false; + + //First find the file locking descriptor id + locking_file_serial_id *pserial_id = + mshm.template find<locking_file_serial_id>("lock_file_fd").first; + + int fd; + //If not found schedule a creation + if(!pserial_id){ + fd = GMemNotPresent; + } + //Else get it + else{ + fd = pserial_id->fd; + } + //If we need to create a new one, do it + if(fd == GMemNotPresent){ + std::string lck_str; + //Create a unique current pid based lock file path + create_and_get_singleton_lock_file_path(lck_str); + //Open or create and lock file + int fd = intermodule_singleton_helpers::open_or_create_and_lock_file(lck_str.c_str()); + //If failed, write a bad file descriptor to notify other modules that + //something was wrong and unlink shared memory. Mark the function object + //to tell caller to retry with another shared memory + if(fd < 0){ + this->register_lock_file(GMemMarkToBeRemoved); + std::string s; + get_shm_name(s); + shared_memory_object::remove(s.c_str()); + retry_with_new_shm = true; + } + //If successful, register the file descriptor + else{ + this->register_lock_file(fd); + } + } + //If the fd was invalid (maybe a previous try failed) notify caller that + //should retry creation logic, since this shm might have been already + //unlinked since the shm was removed + else if (fd == GMemMarkToBeRemoved){ + retry_with_new_shm = true; + } + //If the stored fd is not valid (a open fd, a normal file with the + //expected size, or does not have the same file id number, + //then it's an old shm from an old process with the same pid. + //If that's the case, mark it as invalid + else if(!is_valid_fd(fd) || + !is_normal_file(fd) || + 0 != get_size(fd) || + !compare_file_serial(fd, *pserial_id)){ + pserial_id->fd = GMemMarkToBeRemoved; + std::string s; + get_shm_name(s); + shared_memory_object::remove(s.c_str()); + retry_with_new_shm = true; + } + else{ + //If the lock file is ok, increment reference count of + //attached modules to shared memory + atomic_inc32(&pserial_id->modules_attached_to_gmem_count); + } + } + + private: + locking_file_serial_id * register_lock_file(int fd) + { + locking_file_serial_id *pinfo = mshm.template construct<locking_file_serial_id>("lock_file_fd")(); + fill_file_serial_id(fd, *pinfo); + return pinfo; + } + + public: + ManagedShMem &mshm; + bool retry_with_new_shm; +}; + +#if defined(BOOST_INTERPROCESS_WINDOWS) + +template<> +struct lock_file_logic<windows_managed_global_memory> +{ + lock_file_logic(windows_managed_global_memory &) + : retry_with_new_shm(false) + {} + + void operator()(void){} + const bool retry_with_new_shm; +}; + +#endif + +} //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 ManagedShMem> +class intermodule_singleton_common +{ + public: + typedef void*(singleton_constructor_t)(ManagedShMem &); + typedef void (singleton_destructor_t)(void *, ManagedShMem &); + + 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) + { + 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 ManagedShMem &get_shm() + { + return *static_cast<ManagedShMem *>(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(ManagedShMem)]; + } mem_holder; +}; + +template<class ManagedShMem> +volatile boost::uint32_t intermodule_singleton_common<ManagedShMem>::this_module_singleton_count; + +template<class ManagedShMem> +volatile boost::uint32_t intermodule_singleton_common<ManagedShMem>::this_module_shm_initialized; + +template<class ManagedShMem> +typename intermodule_singleton_common<ManagedShMem>::mem_holder_t + intermodule_singleton_common<ManagedShMem>::mem_holder; + +template<class ManagedShMem> +void intermodule_singleton_common<ManagedShMem>::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<ManagedShMem>::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())ManagedShMem(open_or_create, ShmName, ShmSize); + //Use shared memory internal lock to initialize the lock file + //that will mark this gmem as "in use". + intermodule_singleton_helpers::lock_file_logic<ManagedShMem> 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().~ManagedShMem(); + 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 ManagedShMem> +struct unlink_shmlogic +{ + unlink_shmlogic(ManagedShMem &mshm) + : mshm_(mshm) + { mshm.atomic_func(*this); } + void operator()() + { + intermodule_singleton_helpers::locking_file_serial_id *pserial_id = + mshm_.template find<intermodule_singleton_helpers::locking_file_serial_id> + ("lock_file_fd").first; + BOOST_ASSERT(0 != pserial_id); + if(1 == atomic_dec32(&pserial_id->modules_attached_to_gmem_count)){ + int fd = pserial_id->fd; + if(fd > 0){ + pserial_id->fd = intermodule_singleton_helpers::GMemMarkToBeRemoved; + std::string s; + intermodule_singleton_helpers::create_and_get_singleton_lock_file_path(s); + delete_file(s.c_str()); + intermodule_singleton_helpers::close_lock_file(fd); + intermodule_singleton_helpers::get_shm_name(s); + shared_memory_object::remove(s.c_str()); + } + } + } + ManagedShMem &mshm_; +}; + +#if defined(BOOST_INTERPROCESS_WINDOWS) + +template<> +struct unlink_shmlogic<windows_managed_global_memory> +{ + unlink_shmlogic(windows_managed_global_memory &) + {} + void operator()(){} +}; + +#endif + + +template<class ManagedShMem> +void intermodule_singleton_common<ManagedShMem>::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 + unlink_shmlogic<ManagedShMem> f(get_shm()); + (get_shm()).~ManagedShMem(); + atomic_write32(&this_module_shm_initialized, Uninitialized); + //Do some cleanup for other processes old gmem instances + intermodule_singleton_helpers::managed_sh_dependant<ManagedShMem>::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 ManagedShMem> +void intermodule_singleton_common<ManagedShMem>::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 ManagedShMem> +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<ManagedShMem>::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<ManagedShMem>::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<ManagedShMem>::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(ManagedShMem &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; + } + ManagedShMem &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(ManagedShMem &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); + } + } + ManagedShMem &mshm; + void *ret_ptr; + }; + + //A wrapper to execute init_atomic_func + static void *singleton_constructor(ManagedShMem &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, ManagedShMem &mshm) + { (void)p; + fini_atomic_func f(mshm); + mshm.atomic_func(f); + } +}; + +template <typename C, bool L, class ManagedShMem> +volatile int intermodule_singleton_impl<C, L, ManagedShMem>::lifetime_type_lazy::m_dummy = 0; + +//These will be zero-initialized by the loader +template <typename C, bool L, class ManagedShMem> +void *intermodule_singleton_impl<C, L, ManagedShMem>::this_module_singleton_ptr = 0; + +template <typename C, bool L, class ManagedShMem> +volatile boost::uint32_t intermodule_singleton_impl<C, L, ManagedShMem>::this_module_singleton_initialized = 0; + +template <typename C, bool L, class ManagedShMem> +typename intermodule_singleton_impl<C, L, ManagedShMem>::lifetime_type + intermodule_singleton_impl<C, L, ManagedShMem>::lifetime; + +template<typename C, bool LazyInit = false> +class portable_intermodule_singleton + : public intermodule_singleton_impl<C, LazyInit, managed_global_memory> +{}; + +#if defined(BOOST_INTERPROCESS_WINDOWS) + +template<typename C, bool LazyInit = false> +class windows_intermodule_singleton + : public intermodule_singleton_impl + < C + , LazyInit + , windows_managed_global_memory + > +{}; + +#endif + +//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 = false> +class intermodule_singleton + #ifdef BOOST_INTERPROCESS_WINDOWS + : public windows_intermodule_singleton<C, LazyInit> + #else + : public portable_intermodule_singleton<C, LazyInit> + #endif +{}; + + +} //namespace ipcdetail{ +} //namespace interprocess{ +} //namespace boost{ + +#include <boost/interprocess/detail/config_end.hpp> + +#endif |