1036 lines
32 KiB
C++
1036 lines
32 KiB
C++
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// (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/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{
|
|
|
|
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(::boost::detail::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 ::boost::detail::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>
|
|
::boost::detail::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){
|
|
ipcdetail::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 ipcdetail{
|
|
} //namespace interprocess{
|
|
} //namespace boost{
|
|
|
|
#include <boost/interprocess/detail/config_end.hpp>
|
|
|
|
#endif
|