////////////////////////////////////////////////////////////////////////////// | |
// | |
// (C) Copyright Ion Gaztanaga 2009-2009. 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> | |
#include <boost/interprocess/managed_shared_memory.hpp> | |
#ifdef BOOST_INTERPROCESS_WINDOWS | |
#include <boost/interprocess/managed_windows_shared_memory.hpp> | |
#endif | |
#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/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 detail{ | |
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<managed_windows_shared_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<managed_windows_shared_memory> | |
{ | |
lock_file_logic(managed_windows_shared_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 *>(&shm_mem)); | |
} | |
enum { MemSize = ((sizeof(ManagedShMem)-1)/sizeof(max_align))+1u }; | |
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 max_align shm_mem[MemSize]; | |
}; | |
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> | |
max_align intermodule_singleton_common<ManagedShMem>::shm_mem[intermodule_singleton_common<ManagedShMem>::MemSize]; | |
template<class ManagedShMem> | |
void intermodule_singleton_common<ManagedShMem>::initialize_shm() | |
{ | |
//Obtain unique shm name and size | |
std::string s; | |
intermodule_singleton_helpers::get_shm_name(s); | |
const char *ShmName = s.c_str(); | |
const std::size_t ShmSize = intermodule_singleton_helpers::get_shm_size();; | |
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(); | |
//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<managed_windows_shared_memory> | |
{ | |
unlink_shmlogic(managed_windows_shared_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){ | |
detail::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; | |
//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; | |
template <typename C, bool L, class ManagedShMem> | |
volatile boost::uint32_t intermodule_singleton_impl<C, L, ManagedShMem>::this_module_singleton_initialized; | |
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_shared_memory> | |
{}; | |
#ifdef BOOST_INTERPROCESS_WINDOWS | |
template<typename C, bool LazyInit = false> | |
class windows_intermodule_singleton | |
: public intermodule_singleton_impl<C, LazyInit, managed_windows_shared_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> | |
// : public portable_intermodule_singleton<C, LazyInit> | |
#else | |
: public portable_intermodule_singleton<C, LazyInit> | |
#endif | |
{}; | |
} //namespace detail{ | |
} //namespace interprocess{ | |
} //namespace boost{ | |
#include <boost/interprocess/detail/config_end.hpp> | |
#endif |