You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
439 lines
14 KiB
439 lines
14 KiB
////////////////////////////////////////////////////////////////////////////// |
|
// |
|
// (C) Copyright Ion Gaztanaga 2010-2010. 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_ROBUST_EMULATION_HPP |
|
#define BOOST_INTERPROCESS_ROBUST_EMULATION_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/sync/interprocess_mutex.hpp> |
|
#include <boost/interprocess/sync/interprocess_recursive_mutex.hpp> |
|
#include <boost/interprocess/detail/atomic.hpp> |
|
#include <boost/interprocess/detail/os_file_functions.hpp> |
|
#include <boost/interprocess/detail/tmp_dir_helpers.hpp> |
|
#include <boost/interprocess/detail/intermodule_singleton.hpp> |
|
#include <boost/interprocess/exceptions.hpp> |
|
#include <string> |
|
|
|
namespace boost{ |
|
namespace interprocess{ |
|
namespace ipcdetail{ |
|
|
|
namespace robust_emulation_helpers { |
|
|
|
template<class T> |
|
class mutex_traits |
|
{ |
|
public: |
|
static void take_ownership(T &t) |
|
{ t.take_ownership(); } |
|
}; |
|
|
|
inline void remove_if_can_lock_file(const char *file_path) |
|
{ |
|
file_handle_t fhnd = open_existing_file(file_path, read_write); |
|
|
|
if(fhnd != invalid_file()){ |
|
bool acquired; |
|
if(try_acquire_file_lock(fhnd, acquired) && acquired){ |
|
delete_file(file_path); |
|
} |
|
close_file(fhnd); |
|
} |
|
} |
|
|
|
inline const char *robust_lock_subdir_path() |
|
{ return "robust"; } |
|
|
|
inline const char *robust_lock_prefix() |
|
{ return "lck"; } |
|
|
|
inline void robust_lock_path(std::string &s) |
|
{ |
|
tmp_folder(s); |
|
s += "/"; |
|
s += robust_lock_subdir_path(); |
|
} |
|
|
|
inline void create_and_get_robust_lock_file_path(std::string &s, OS_process_id_t pid) |
|
{ |
|
file_locking_helpers::create_tmp_subdir_and_get_pid_based_filepath |
|
(robust_lock_subdir_path(), robust_lock_prefix(), pid, s); |
|
} |
|
|
|
//This class will be a intermodule_singleton. The constructor will create |
|
//a lock file, the destructor will erase it. |
|
// |
|
//We should take in care that another process might be erasing unlocked |
|
//files while creating this one, so there are some race conditions we must |
|
//take in care to guarantee some robustness. |
|
class robust_mutex_lock_file |
|
{ |
|
file_handle_t fd; |
|
std::string fname; |
|
public: |
|
robust_mutex_lock_file() |
|
{ |
|
permissions p; |
|
p.set_unrestricted(); |
|
//Remove old lock files of other processes |
|
remove_old_robust_lock_files(); |
|
//Create path and obtain lock file path for this process |
|
create_and_get_robust_lock_file_path(fname, get_current_process_id()); |
|
|
|
//Now try to open or create the lock file |
|
fd = create_or_open_file(fname.c_str(), read_write, p); |
|
//If we can't open or create it, then something unrecoverable has happened |
|
if(fd == invalid_file()){ |
|
throw interprocess_exception(other_error, "Robust emulation robust_mutex_lock_file constructor failed: could not open or create file"); |
|
} |
|
|
|
//Now we must take in care a race condition with another process |
|
//calling "remove_old_robust_lock_files()". No other threads from this |
|
//process will be creating the lock file because intermodule_singleton |
|
//guarantees this. So let's loop acquiring the lock and checking if we |
|
//can't exclusively create the file (if the file is erased by another process |
|
//then this exclusive open would fail). If the file can't be exclusively created |
|
//then we have correctly open/create and lock the file. If the file can |
|
//be exclusively created, then close previous locked file and try again. |
|
while(1){ |
|
bool acquired; |
|
if(!try_acquire_file_lock(fd, acquired) || !acquired ){ |
|
throw interprocess_exception(other_error, "Robust emulation robust_mutex_lock_file constructor failed: try_acquire_file_lock"); |
|
} |
|
//Creating exclusively must fail with already_exists_error |
|
//to make sure we've locked the file and no one has |
|
//deleted it between creation and locking |
|
file_handle_t fd2 = create_new_file(fname.c_str(), read_write, p); |
|
if(fd2 != invalid_file()){ |
|
close_file(fd); |
|
fd = fd2; |
|
continue; |
|
} |
|
//If exclusive creation fails with expected error go ahead |
|
else if(error_info(system_error_code()).get_error_code() == already_exists_error){ //must already exist |
|
//Leak descriptor to mantain the file locked until the process dies |
|
break; |
|
} |
|
//If exclusive creation fails with unexpected error throw an unrecoverable error |
|
else{ |
|
close_file(fd); |
|
throw interprocess_exception(other_error, "Robust emulation robust_mutex_lock_file constructor failed: create_file filed with unexpected error"); |
|
} |
|
} |
|
} |
|
|
|
~robust_mutex_lock_file() |
|
{ |
|
//The destructor is guaranteed by intermodule_singleton to be |
|
//executed serialized between all threads from current process, |
|
//so we just need to close and unlink the file. |
|
close_file(fd); |
|
//If some other process deletes the file before us after |
|
//closing it there should not be any problem. |
|
delete_file(fname.c_str()); |
|
} |
|
|
|
private: |
|
//This functor is execute for all files in the lock file directory |
|
class other_process_lock_remover |
|
{ |
|
public: |
|
void operator()(const char *filepath, const char *filename) |
|
{ |
|
std::string pid_str; |
|
//If the lock file is not our own lock file, then try to do the cleanup |
|
if(!file_locking_helpers::check_if_filename_complies_with_pid |
|
(filename, robust_lock_prefix(), get_current_process_id(), pid_str)){ |
|
remove_if_can_lock_file(filepath); |
|
} |
|
} |
|
}; |
|
|
|
bool remove_old_robust_lock_files() |
|
{ |
|
std::string refcstrRootDirectory; |
|
robust_lock_path(refcstrRootDirectory); |
|
return for_each_file_in_dir(refcstrRootDirectory.c_str(), other_process_lock_remover()); |
|
} |
|
}; |
|
|
|
} //namespace robust_emulation_helpers { |
|
|
|
//This is the mutex class. Mutex should follow mutex concept |
|
//with an additonal "take_ownership()" function to take ownership of the |
|
//mutex when robust_emulation_mutex determines the previous owner was dead. |
|
template<class Mutex> |
|
class robust_emulation_mutex |
|
{ |
|
public: |
|
static const boost::uint32_t correct_state = 0; |
|
static const boost::uint32_t fixing_state = 1; |
|
static const boost::uint32_t broken_state = 2; |
|
|
|
typedef robust_emulation_helpers::mutex_traits<Mutex> mutex_traits_t; |
|
|
|
robust_emulation_mutex(); |
|
void lock(); |
|
bool try_lock(); |
|
bool timed_lock(const boost::posix_time::ptime &abs_time); |
|
void unlock(); |
|
void consistent(); |
|
bool previous_owner_dead(); |
|
|
|
private: |
|
static const unsigned int spin_threshold = 100u; |
|
bool lock_own_unique_file(); |
|
bool robust_check(); |
|
bool check_if_owner_dead_and_take_ownership_atomically(); |
|
bool is_owner_dead(boost::uint32_t owner); |
|
void owner_to_filename(boost::uint32_t owner, std::string &s); |
|
//The real mutex |
|
Mutex mtx; |
|
//The pid of the owner |
|
volatile boost::uint32_t owner; |
|
//The state of the mutex (correct, fixing, broken) |
|
volatile boost::uint32_t state; |
|
}; |
|
|
|
template<class Mutex> |
|
inline robust_emulation_mutex<Mutex>::robust_emulation_mutex() |
|
: mtx(), owner(get_invalid_process_id()), state(correct_state) |
|
{} |
|
|
|
template<class Mutex> |
|
inline void robust_emulation_mutex<Mutex>::lock() |
|
{ |
|
//If the mutex is broken (recovery didn't call consistent()), |
|
//then throw an exception |
|
if(atomic_read32(&this->state) == broken_state){ |
|
throw interprocess_exception(lock_error, "Broken id"); |
|
} |
|
|
|
//This function provokes intermodule_singleton instantiation |
|
if(!this->lock_own_unique_file()){ |
|
throw interprocess_exception(lock_error, "Broken id"); |
|
} |
|
|
|
//Now the logic. Try to lock, if successful mark the owner |
|
//if it fails, start recovery logic |
|
unsigned int spin_count = 0; |
|
while(1){ |
|
if (mtx.try_lock()){ |
|
atomic_write32(&this->owner, get_current_process_id()); |
|
break; |
|
} |
|
else{ |
|
//Do the dead owner checking each spin_threshold lock tries |
|
ipcdetail::thread_yield(); |
|
++spin_count; |
|
if(spin_count > spin_threshold){ |
|
//Check if owner dead and take ownership if possible |
|
if(!this->robust_check()){ |
|
spin_count = 0; |
|
} |
|
else{ |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::try_lock() |
|
{ |
|
//Same as lock() but without spinning |
|
if(atomic_read32(&this->state) == broken_state){ |
|
throw interprocess_exception(lock_error, "Broken id"); |
|
} |
|
|
|
if(!this->lock_own_unique_file()){ |
|
throw interprocess_exception(lock_error, "Broken id"); |
|
} |
|
|
|
if (mtx.try_lock()){ |
|
atomic_write32(&this->owner, get_current_process_id()); |
|
return true; |
|
} |
|
else{ |
|
if(!this->robust_check()){ |
|
return false; |
|
} |
|
else{ |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::timed_lock |
|
(const boost::posix_time::ptime &abs_time) |
|
{ |
|
//Same as lock() but with an additional timeout |
|
if(abs_time == boost::posix_time::pos_infin){ |
|
this->lock(); |
|
return true; |
|
} |
|
//Obtain current count and target time |
|
boost::posix_time::ptime now = microsec_clock::universal_time(); |
|
|
|
if(now >= abs_time) |
|
return this->try_lock(); |
|
|
|
do{ |
|
if(this->try_lock()){ |
|
break; |
|
} |
|
now = microsec_clock::universal_time(); |
|
|
|
if(now >= abs_time){ |
|
return this->try_lock(); |
|
} |
|
// relinquish current time slice |
|
ipcdetail::thread_yield(); |
|
}while (true); |
|
|
|
return true; |
|
} |
|
|
|
template<class Mutex> |
|
inline void robust_emulation_mutex<Mutex>::owner_to_filename(boost::uint32_t owner, std::string &s) |
|
{ |
|
robust_emulation_helpers::create_and_get_robust_lock_file_path(s, owner); |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::robust_check() |
|
{ |
|
//If the old owner was dead, and we've acquired ownership, mark |
|
//the mutex as 'fixing'. This means that a "consistent()" is needed |
|
//to avoid marking the mutex as "broken" when the mutex is unlocked. |
|
if(!this->check_if_owner_dead_and_take_ownership_atomically()){ |
|
return false; |
|
} |
|
atomic_write32(&this->state, fixing_state); |
|
return true; |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::check_if_owner_dead_and_take_ownership_atomically() |
|
{ |
|
boost::uint32_t cur_owner = get_current_process_id(); |
|
boost::uint32_t old_owner = atomic_read32(&this->owner), old_owner2; |
|
//The cas loop guarantees that only one thread from this or another process |
|
//will succeed taking ownership |
|
do{ |
|
//Check if owner is dead |
|
if(!this->is_owner_dead(old_owner)){ |
|
return false; |
|
} |
|
//If it's dead, try to mark this process as the owner in the owner field |
|
old_owner2 = old_owner; |
|
old_owner = atomic_cas32(&this->owner, cur_owner, old_owner); |
|
}while(old_owner2 != old_owner); |
|
//If success, we fix mutex internals to assure our ownership |
|
mutex_traits_t::take_ownership(mtx); |
|
return true; |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::is_owner_dead(boost::uint32_t owner) |
|
{ |
|
//If owner is an invalid id, then it's clear it's dead |
|
if(owner == (boost::uint32_t)get_invalid_process_id()){ |
|
return true; |
|
} |
|
|
|
//Obtain the lock filename of the owner field |
|
std::string file; |
|
this->owner_to_filename(owner, file); |
|
|
|
//Now the logic is to open and lock it |
|
file_handle_t fhnd = open_existing_file(file.c_str(), read_write); |
|
|
|
if(fhnd != invalid_file()){ |
|
//If we can open the file, lock it. |
|
bool acquired; |
|
if(try_acquire_file_lock(fhnd, acquired) && acquired){ |
|
//If locked, just delete the file |
|
delete_file(file.c_str()); |
|
close_file(fhnd); |
|
return true; |
|
} |
|
//If not locked, the owner is suppossed to be still alive |
|
close_file(fhnd); |
|
} |
|
else{ |
|
//If the lock file does not exist then the owner is dead (a previous cleanup) |
|
//function has deleted the file. If there is another reason, then this is |
|
//an unrecoverable error |
|
if(error_info(system_error_code()).get_error_code() == not_found_error){ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
template<class Mutex> |
|
inline void robust_emulation_mutex<Mutex>::consistent() |
|
{ |
|
//This function supposes the previous state was "fixing" |
|
//and the current process holds the mutex |
|
if(atomic_read32(&this->state) != fixing_state && |
|
atomic_read32(&this->owner) != (boost::uint32_t)get_current_process_id()){ |
|
throw interprocess_exception(lock_error, "Broken id"); |
|
} |
|
//If that's the case, just update mutex state |
|
atomic_write32(&this->state, correct_state); |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::previous_owner_dead() |
|
{ |
|
//Notifies if a owner recovery has been performed in the last lock() |
|
return atomic_read32(&this->state) == fixing_state; |
|
}; |
|
|
|
template<class Mutex> |
|
inline void robust_emulation_mutex<Mutex>::unlock() |
|
{ |
|
//If in "fixing" state, unlock and mark the mutex as unrecoverable |
|
//so next locks will fail and all threads will be notified that the |
|
//data protected by the mutex was not recoverable. |
|
if(atomic_read32(&this->state) == fixing_state){ |
|
atomic_write32(&this->state, broken_state); |
|
} |
|
//Write an invalid owner to minimize pid reuse possibility |
|
atomic_write32(&this->owner, get_invalid_process_id()); |
|
mtx.unlock(); |
|
} |
|
|
|
template<class Mutex> |
|
inline bool robust_emulation_mutex<Mutex>::lock_own_unique_file() |
|
{ |
|
//This function forces instantiation of the singleton |
|
robust_emulation_helpers::robust_mutex_lock_file* dummy = |
|
&ipcdetail::intermodule_singleton |
|
<robust_emulation_helpers::robust_mutex_lock_file>::get(); |
|
return dummy != 0; |
|
} |
|
|
|
} //namespace ipcdetail{ |
|
} //namespace interprocess{ |
|
} //namespace boost{ |
|
|
|
#include <boost/interprocess/detail/config_end.hpp> |
|
|
|
#endif
|
|
|