1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // (C) Copyright Ion Gaztanaga 2010-2010. Distributed under the Boost
4 // Software License, Version 1.0. (See accompanying file
5 // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // See http://www.boost.org/libs/interprocess for documentation.
8 //
9 //////////////////////////////////////////////////////////////////////////////
10 
11 #ifndef BOOST_INTERPROCESS_ROBUST_EMULATION_HPP
12 #define BOOST_INTERPROCESS_ROBUST_EMULATION_HPP
13 
14 #if defined(_MSC_VER)&&(_MSC_VER>=1200)
15 #pragma once
16 #endif
17 
18 #include <boost/interprocess/detail/config_begin.hpp>
19 #include <boost/interprocess/detail/workaround.hpp>
20 #include <boost/interprocess/sync/interprocess_mutex.hpp>
21 #include <boost/interprocess/sync/interprocess_recursive_mutex.hpp>
22 #include <boost/interprocess/detail/atomic.hpp>
23 #include <boost/interprocess/detail/os_file_functions.hpp>
24 #include <boost/interprocess/detail/tmp_dir_helpers.hpp>
25 #include <boost/interprocess/detail/intermodule_singleton.hpp>
26 #include <boost/interprocess/exceptions.hpp>
27 #include <string>
28 
29 namespace boost{
30 namespace interprocess{
31 namespace detail{
32 
33 namespace robust_emulation_helpers {
34 
35 template<class T>
36 class mutex_traits
37 {
38    public:
take_ownership(T & t)39    static void take_ownership(T &t)
40    {  t.take_ownership(); }
41 };
42 
remove_if_can_lock_file(const char * file_path)43 inline void remove_if_can_lock_file(const char *file_path)
44 {
45    file_handle_t fhnd = open_existing_file(file_path, read_write);
46 
47    if(fhnd != invalid_file()){
48       bool acquired;
49       if(try_acquire_file_lock(fhnd, acquired) && acquired){
50          delete_file(file_path);
51       }
52       close_file(fhnd);
53    }
54 }
55 
robust_lock_subdir_path()56 inline const char *robust_lock_subdir_path()
57 {  return "robust"; }
58 
robust_lock_prefix()59 inline const char *robust_lock_prefix()
60 {  return "lck"; }
61 
robust_lock_path(std::string & s)62 inline void robust_lock_path(std::string &s)
63 {
64    tmp_folder(s);
65    s += "/";
66    s += robust_lock_subdir_path();
67 }
68 
create_and_get_robust_lock_file_path(std::string & s,OS_process_id_t pid)69 inline void create_and_get_robust_lock_file_path(std::string &s, OS_process_id_t pid)
70 {
71    file_locking_helpers::create_tmp_subdir_and_get_pid_based_filepath
72       (robust_lock_subdir_path(), robust_lock_prefix(), pid, s);
73 }
74 
75 //This class will be a intermodule_singleton. The constructor will create
76 //a lock file, the destructor will erase it.
77 //
78 //We should take in care that another process might be erasing unlocked
79 //files while creating this one, so there are some race conditions we must
80 //take in care to guarantee some robustness.
81 class robust_mutex_lock_file
82 {
83    file_handle_t fd;
84    std::string fname;
85    public:
robust_mutex_lock_file()86    robust_mutex_lock_file()
87    {
88       permissions p;
89       p.set_unrestricted();
90       //Remove old lock files of other processes
91       remove_old_robust_lock_files();
92       //Create path and obtain lock file path for this process
93       create_and_get_robust_lock_file_path(fname, get_current_process_id());
94 
95       //Now try to open or create the lock file
96       fd = create_or_open_file(fname.c_str(), read_write, p);
97       //If we can't open or create it, then something unrecoverable has happened
98       if(fd == invalid_file()){
99          throw interprocess_exception(other_error, "Robust emulation robust_mutex_lock_file constructor failed: could not open or create file");
100       }
101 
102       //Now we must take in care a race condition with another process
103       //calling "remove_old_robust_lock_files()". No other threads from this
104       //process will be creating the lock file because intermodule_singleton
105       //guarantees this. So let's loop acquiring the lock and checking if we
106       //can't exclusively create the file (if the file is erased by another process
107       //then this exclusive open would fail). If the file can't be exclusively created
108       //then we have correctly open/create and lock the file. If the file can
109       //be exclusively created, then close previous locked file and try again.
110       while(1){
111          bool acquired;
112          if(!try_acquire_file_lock(fd, acquired) || !acquired ){
113             throw interprocess_exception(other_error, "Robust emulation robust_mutex_lock_file constructor failed: try_acquire_file_lock");
114          }
115          //Creating exclusively must fail with already_exists_error
116          //to make sure we've locked the file and no one has
117          //deleted it between creation and locking
118          file_handle_t fd2 = create_new_file(fname.c_str(), read_write, p);
119          if(fd2 != invalid_file()){
120             close_file(fd);
121             fd = fd2;
122             continue;
123          }
124          //If exclusive creation fails with expected error go ahead
125          else if(error_info(system_error_code()).get_error_code() == already_exists_error){ //must already exist
126             //Leak descriptor to mantain the file locked until the process dies
127             break;
128          }
129          //If exclusive creation fails with unexpected error throw an unrecoverable error
130          else{
131             close_file(fd);
132             throw interprocess_exception(other_error, "Robust emulation robust_mutex_lock_file constructor failed: create_file filed with unexpected error");
133          }
134       }
135    }
136 
~robust_mutex_lock_file()137    ~robust_mutex_lock_file()
138    {
139       //The destructor is guaranteed by intermodule_singleton to be
140       //executed serialized between all threads from current process,
141       //so we just need to close and unlink the file.
142       close_file(fd);
143       //If some other process deletes the file before us after
144       //closing it there should not be any problem.
145       delete_file(fname.c_str());
146    }
147 
148    private:
149    //This functor is execute for all files in the lock file directory
150    class other_process_lock_remover
151    {
152       public:
operator ()(const char * filepath,const char * filename)153       void operator()(const char *filepath, const char *filename)
154       {
155          std::string pid_str;
156          //If the lock file is not our own lock file, then try to do the cleanup
157          if(!file_locking_helpers::check_if_filename_complies_with_pid
158             (filename, robust_lock_prefix(), get_current_process_id(), pid_str)){
159             remove_if_can_lock_file(filepath);
160          }
161       }
162    };
163 
remove_old_robust_lock_files()164    bool remove_old_robust_lock_files()
165    {
166       std::string refcstrRootDirectory;
167       robust_lock_path(refcstrRootDirectory);
168       return for_each_file_in_dir(refcstrRootDirectory.c_str(), other_process_lock_remover());
169    }
170 };
171 
172 }  //namespace robust_emulation_helpers {
173 
174 //This is the mutex class. Mutex should follow mutex concept
175 //with an additonal "take_ownership()" function to take ownership of the
176 //mutex when robust_emulation_mutex determines the previous owner was dead.
177 template<class Mutex>
178 class robust_emulation_mutex
179 {
180    public:
181    static const boost::uint32_t correct_state = 0;
182    static const boost::uint32_t fixing_state  = 1;
183    static const boost::uint32_t broken_state  = 2;
184 
185    typedef robust_emulation_helpers::mutex_traits<Mutex> mutex_traits_t;
186 
187    robust_emulation_mutex();
188    void lock();
189    bool try_lock();
190    bool timed_lock(const boost::posix_time::ptime &abs_time);
191    void unlock();
192    void consistent();
193    bool previous_owner_dead();
194 
195    private:
196    static const unsigned int spin_threshold = 100u;
197    bool lock_own_unique_file();
198    bool robust_check();
199    bool check_if_owner_dead_and_take_ownership_atomically();
200    bool is_owner_dead(boost::uint32_t owner);
201    void owner_to_filename(boost::uint32_t owner, std::string &s);
202    //The real mutex
203    Mutex mtx;
204    //The pid of the owner
205    volatile boost::uint32_t owner;
206    //The state of the mutex (correct, fixing, broken)
207    volatile boost::uint32_t state;
208 };
209 
210 template<class Mutex>
robust_emulation_mutex()211 inline robust_emulation_mutex<Mutex>::robust_emulation_mutex()
212    : mtx(), owner(get_invalid_process_id()), state(correct_state)
213 {}
214 
215 template<class Mutex>
lock()216 inline void robust_emulation_mutex<Mutex>::lock()
217 {
218    //If the mutex is broken (recovery didn't call consistent()),
219    //then throw an exception
220    if(atomic_read32(&this->state) == broken_state){
221       throw interprocess_exception(lock_error, "Broken id");
222    }
223 
224    //This function provokes intermodule_singleton instantiation
225    if(!this->lock_own_unique_file()){
226       throw interprocess_exception(lock_error, "Broken id");
227    }
228 
229    //Now the logic. Try to lock, if successful mark the owner
230    //if it fails, start recovery logic
231    unsigned int spin_count = 0;
232    while(1){
233       if (mtx.try_lock()){
234          atomic_write32(&this->owner, get_current_process_id());
235          break;
236       }
237       else{
238          //Do the dead owner checking each spin_threshold lock tries
239          detail::thread_yield();
240          ++spin_count;
241          if(spin_count > spin_threshold){
242             //Check if owner dead and take ownership if possible
243             if(!this->robust_check()){
244                spin_count = 0;
245             }
246             else{
247                break;
248             }
249          }
250       }
251    }
252 }
253 
254 template<class Mutex>
try_lock()255 inline bool robust_emulation_mutex<Mutex>::try_lock()
256 {
257    //Same as lock() but without spinning
258    if(atomic_read32(&this->state) == broken_state){
259       throw interprocess_exception(lock_error, "Broken id");
260    }
261 
262    if(!this->lock_own_unique_file()){
263       throw interprocess_exception(lock_error, "Broken id");
264    }
265 
266    if (mtx.try_lock()){
267       atomic_write32(&this->owner, get_current_process_id());
268       return true;
269    }
270    else{
271       if(!this->robust_check()){
272          return false;
273       }
274       else{
275          return true;
276       }
277    }
278 }
279 
280 template<class Mutex>
timed_lock(const boost::posix_time::ptime & abs_time)281 inline bool robust_emulation_mutex<Mutex>::timed_lock
282    (const boost::posix_time::ptime &abs_time)
283 {
284    //Same as lock() but with an additional timeout
285    if(abs_time == boost::posix_time::pos_infin){
286       this->lock();
287       return true;
288    }
289    //Obtain current count and target time
290    boost::posix_time::ptime now = microsec_clock::universal_time();
291 
292    if(now >= abs_time)
293       return this->try_lock();
294 
295    do{
296       if(this->try_lock()){
297          break;
298       }
299       now = microsec_clock::universal_time();
300 
301       if(now >= abs_time){
302          return this->try_lock();
303       }
304       // relinquish current time slice
305       detail::thread_yield();
306    }while (true);
307 
308    return true;
309 }
310 
311 template<class Mutex>
owner_to_filename(boost::uint32_t owner,std::string & s)312 inline void robust_emulation_mutex<Mutex>::owner_to_filename(boost::uint32_t owner, std::string &s)
313 {
314    robust_emulation_helpers::create_and_get_robust_lock_file_path(s, owner);
315 }
316 
317 template<class Mutex>
robust_check()318 inline bool robust_emulation_mutex<Mutex>::robust_check()
319 {
320    //If the old owner was dead, and we've acquired ownership, mark
321    //the mutex as 'fixing'. This means that a "consistent()" is needed
322    //to avoid marking the mutex as "broken" when the mutex is unlocked.
323    if(!this->check_if_owner_dead_and_take_ownership_atomically()){
324       return false;
325    }
326    atomic_write32(&this->state, fixing_state);
327    return true;
328 }
329 
330 template<class Mutex>
check_if_owner_dead_and_take_ownership_atomically()331 inline bool robust_emulation_mutex<Mutex>::check_if_owner_dead_and_take_ownership_atomically()
332 {
333    boost::uint32_t cur_owner = get_current_process_id();
334    boost::uint32_t old_owner = atomic_read32(&this->owner), old_owner2;
335    //The cas loop guarantees that only one thread from this or another process
336    //will succeed taking ownership
337    do{
338       //Check if owner is dead
339       if(!this->is_owner_dead(old_owner)){
340          return false;
341       }
342       //If it's dead, try to mark this process as the owner in the owner field
343       old_owner2 = old_owner;
344       old_owner = atomic_cas32(&this->owner, cur_owner, old_owner);
345    }while(old_owner2 != old_owner);
346    //If success, we fix mutex internals to assure our ownership
347    mutex_traits_t::take_ownership(mtx);
348    return true;
349 }
350 
351 template<class Mutex>
is_owner_dead(boost::uint32_t owner)352 inline bool robust_emulation_mutex<Mutex>::is_owner_dead(boost::uint32_t owner)
353 {
354    //If owner is an invalid id, then it's clear it's dead
355    if(owner == (boost::uint32_t)get_invalid_process_id()){
356       return true;
357    }
358 
359    //Obtain the lock filename of the owner field
360    std::string file;
361    this->owner_to_filename(owner, file);
362 
363    //Now the logic is to open and lock it
364    file_handle_t fhnd = open_existing_file(file.c_str(), read_write);
365 
366    if(fhnd != invalid_file()){
367       //If we can open the file, lock it.
368       bool acquired;
369       if(try_acquire_file_lock(fhnd, acquired) && acquired){
370          //If locked, just delete the file
371          delete_file(file.c_str());
372          close_file(fhnd);
373          return true;
374       }
375       //If not locked, the owner is suppossed to be still alive
376       close_file(fhnd);
377    }
378    else{
379       //If the lock file does not exist then the owner is dead (a previous cleanup)
380       //function has deleted the file. If there is another reason, then this is
381       //an unrecoverable error
382       if(error_info(system_error_code()).get_error_code() == not_found_error){
383          return true;
384       }
385    }
386    return false;
387 }
388 
389 template<class Mutex>
consistent()390 inline void robust_emulation_mutex<Mutex>::consistent()
391 {
392    //This function supposes the previous state was "fixing"
393    //and the current process holds the mutex
394    if(atomic_read32(&this->state) != fixing_state &&
395       atomic_read32(&this->owner) != (boost::uint32_t)get_current_process_id()){
396       throw interprocess_exception(lock_error, "Broken id");
397    }
398    //If that's the case, just update mutex state
399    atomic_write32(&this->state, correct_state);
400 }
401 
402 template<class Mutex>
previous_owner_dead()403 inline bool robust_emulation_mutex<Mutex>::previous_owner_dead()
404 {
405    //Notifies if a owner recovery has been performed in the last lock()
406    return atomic_read32(&this->state) == fixing_state;
407 };
408 
409 template<class Mutex>
unlock()410 inline void robust_emulation_mutex<Mutex>::unlock()
411 {
412    //If in "fixing" state, unlock and mark the mutex as unrecoverable
413    //so next locks will fail and all threads will be notified that the
414    //data protected by the mutex was not recoverable.
415    if(atomic_read32(&this->state) == fixing_state){
416       atomic_write32(&this->state, broken_state);
417    }
418    //Write an invalid owner to minimize pid reuse possibility
419    atomic_write32(&this->owner, get_invalid_process_id());
420    mtx.unlock();
421 }
422 
423 template<class Mutex>
lock_own_unique_file()424 inline bool robust_emulation_mutex<Mutex>::lock_own_unique_file()
425 {
426    //This function forces instantiation of the singleton
427    robust_emulation_helpers::robust_mutex_lock_file* dummy =
428       &detail::intermodule_singleton
429          <robust_emulation_helpers::robust_mutex_lock_file>::get();
430    return dummy != 0;
431 }
432 
433 }  //namespace detail{
434 }  //namespace interprocess{
435 }  //namespace boost{
436 
437 #include <boost/interprocess/detail/config_end.hpp>
438 
439 #endif
440