362 lines
12 KiB
C++
362 lines
12 KiB
C++
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// (C) Copyright Ion Gaztanaga 2009-2012. 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_PORTABLE_INTERMODULE_SINGLETON_HPP
|
|
#define BOOST_INTERPROCESS_PORTABLE_INTERMODULE_SINGLETON_HPP
|
|
|
|
#ifndef BOOST_CONFIG_HPP
|
|
# include <boost/config.hpp>
|
|
#endif
|
|
#
|
|
#if defined(BOOST_HAS_PRAGMA_ONCE)
|
|
#pragma once
|
|
#endif
|
|
|
|
#include <boost/interprocess/detail/config_begin.hpp>
|
|
#include <boost/interprocess/detail/workaround.hpp>
|
|
|
|
#include <boost/interprocess/detail/managed_global_memory.hpp>
|
|
#include <boost/interprocess/detail/intermodule_singleton_common.hpp>
|
|
#include <boost/interprocess/shared_memory_object.hpp>
|
|
#include <boost/interprocess/detail/atomic.hpp>
|
|
#include <boost/interprocess/detail/os_thread_functions.hpp>
|
|
#include <boost/interprocess/detail/shared_dir_helpers.hpp>
|
|
#include <boost/interprocess/detail/os_file_functions.hpp>
|
|
#include <boost/interprocess/detail/file_locking_helpers.hpp>
|
|
#include <boost/assert.hpp>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
namespace boost{
|
|
namespace interprocess{
|
|
namespace ipcdetail{
|
|
|
|
typedef basic_managed_global_memory<shared_memory_object, true> managed_global_memory;
|
|
|
|
namespace intermodule_singleton_helpers {
|
|
|
|
static 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_shared_dir_and_clean_old(s);
|
|
s += "/";
|
|
s += subdir_name;
|
|
if(!open_or_create_shared_directory(s.c_str())){
|
|
error_info err = system_error_code();
|
|
throw interprocess_exception(err);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
}
|
|
|
|
template<>
|
|
struct thread_safe_global_map_dependant<managed_global_memory>
|
|
{
|
|
private:
|
|
static const int GMemMarkToBeRemoved = -1;
|
|
static const int GMemNotPresent = -2;
|
|
|
|
static const char *get_lock_file_subdir_name()
|
|
{ return "gmem"; }
|
|
|
|
static const char *get_lock_file_base_name()
|
|
{ return "lck"; }
|
|
|
|
static void create_and_get_singleton_lock_file_path(std::string &s)
|
|
{
|
|
create_tmp_subdir_and_get_pid_based_filepath
|
|
(get_lock_file_subdir_name(), get_lock_file_base_name(), get_current_process_id(), s, true);
|
|
}
|
|
|
|
struct gmem_erase_func
|
|
{
|
|
gmem_erase_func(const char *shm_name, const char *singleton_lock_file_path, managed_global_memory & shm)
|
|
:shm_name_(shm_name), singleton_lock_file_path_(singleton_lock_file_path), shm_(shm)
|
|
{}
|
|
|
|
void operator()()
|
|
{
|
|
locking_file_serial_id *pserial_id = shm_.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_;
|
|
managed_global_memory & shm_;
|
|
};
|
|
|
|
//This function applies shared memory erasure logic based on the passed lock file.
|
|
static void apply_gmem_erase_logic(const char *filepath, const char *filename)
|
|
{
|
|
int fd = GMemMarkToBeRemoved;
|
|
BOOST_TRY{
|
|
std::string str;
|
|
//If the filename is current process lock file, then avoid it
|
|
if(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_map_base_name());
|
|
BOOST_TRY{
|
|
managed_global_memory shm(open_only, str.c_str());
|
|
gmem_erase_func func(str.c_str(), filepath, shm);
|
|
shm.try_atomic_func(func);
|
|
}
|
|
BOOST_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);
|
|
}
|
|
} BOOST_CATCH_END
|
|
}
|
|
BOOST_CATCH(...){
|
|
|
|
} BOOST_CATCH_END
|
|
if(fd >= 0){
|
|
close_lock_file(fd);
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
static bool remove_old_gmem()
|
|
{
|
|
std::string refcstrRootDirectory;
|
|
get_shared_dir(refcstrRootDirectory);
|
|
refcstrRootDirectory += "/";
|
|
refcstrRootDirectory += get_lock_file_subdir_name();
|
|
return for_each_file_in_dir(refcstrRootDirectory.c_str(), apply_gmem_erase_logic);
|
|
}
|
|
|
|
struct lock_file_logic
|
|
{
|
|
lock_file_logic(managed_global_memory &shm)
|
|
: mshm(shm)
|
|
{ shm.atomic_func(*this); }
|
|
|
|
void operator()(void)
|
|
{
|
|
retry_with_new_map = false;
|
|
|
|
//First find the file locking descriptor id
|
|
locking_file_serial_id *pserial_id =
|
|
mshm.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_lockfile = 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_lockfile < 0){
|
|
this->register_lock_file(GMemMarkToBeRemoved);
|
|
std::string s;
|
|
get_map_name(s);
|
|
shared_memory_object::remove(s.c_str());
|
|
retry_with_new_map = true;
|
|
}
|
|
//If successful, register the file descriptor
|
|
else{
|
|
this->register_lock_file(fd_lockfile);
|
|
}
|
|
}
|
|
//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_map = 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_map_name(s);
|
|
shared_memory_object::remove(s.c_str());
|
|
retry_with_new_map = 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);
|
|
}
|
|
}
|
|
|
|
bool retry() const { return retry_with_new_map; }
|
|
|
|
private:
|
|
locking_file_serial_id * register_lock_file(int fd)
|
|
{
|
|
locking_file_serial_id *pinfo = mshm.construct<locking_file_serial_id>("lock_file_fd")();
|
|
fill_file_serial_id(fd, *pinfo);
|
|
return pinfo;
|
|
}
|
|
|
|
managed_global_memory &mshm;
|
|
bool retry_with_new_map;
|
|
};
|
|
|
|
static void construct_map(void *addr)
|
|
{
|
|
std::string s;
|
|
intermodule_singleton_helpers::get_map_name(s);
|
|
const char *MapName = s.c_str();
|
|
const std::size_t MapSize = intermodule_singleton_helpers::get_map_size();;
|
|
::new (addr)managed_global_memory(open_or_create, MapName, MapSize);
|
|
}
|
|
|
|
struct unlink_map_logic
|
|
{
|
|
unlink_map_logic(managed_global_memory &mshm)
|
|
: mshm_(mshm)
|
|
{ mshm.atomic_func(*this); }
|
|
|
|
void operator()()
|
|
{
|
|
locking_file_serial_id *pserial_id =
|
|
mshm_.find<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 = GMemMarkToBeRemoved;
|
|
std::string s;
|
|
create_and_get_singleton_lock_file_path(s);
|
|
delete_file(s.c_str());
|
|
close_lock_file(fd);
|
|
intermodule_singleton_helpers::get_map_name(s);
|
|
shared_memory_object::remove(s.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
managed_global_memory &mshm_;
|
|
};
|
|
|
|
static ref_count_ptr *find(managed_global_memory &map, const char *name)
|
|
{
|
|
return map.find<ref_count_ptr>(name).first;
|
|
}
|
|
|
|
static ref_count_ptr *insert(managed_global_memory &map, const char *name, const ref_count_ptr &ref)
|
|
{
|
|
return map.construct<ref_count_ptr>(name)(ref);
|
|
}
|
|
|
|
static bool erase(managed_global_memory &map, const char *name)
|
|
{
|
|
return map.destroy<ref_count_ptr>(name);
|
|
}
|
|
|
|
template<class F>
|
|
static void atomic_func(managed_global_memory &map, F &f)
|
|
{
|
|
map.atomic_func(f);
|
|
}
|
|
};
|
|
|
|
} //namespace intermodule_singleton_helpers {
|
|
|
|
template<typename C, bool LazyInit = true, bool Phoenix = false>
|
|
class portable_intermodule_singleton
|
|
: public intermodule_singleton_impl<C, LazyInit, Phoenix, managed_global_memory>
|
|
{};
|
|
|
|
} //namespace ipcdetail{
|
|
} //namespace interprocess{
|
|
} //namespace boost{
|
|
|
|
#include <boost/interprocess/detail/config_end.hpp>
|
|
|
|
#endif //#ifndef BOOST_INTERPROCESS_PORTABLE_INTERMODULE_SINGLETON_HPP
|