| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // (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 detail{ |
| |
| 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 |
| detail::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 |
| detail::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 = |
| &detail::intermodule_singleton |
| <robust_emulation_helpers::robust_mutex_lock_file>::get(); |
| return dummy != 0; |
| } |
| |
| } //namespace detail{ |
| } //namespace interprocess{ |
| } //namespace boost{ |
| |
| #include <boost/interprocess/detail/config_end.hpp> |
| |
| #endif |