backup prototype of granular locking. Dont look at it yet
authorDuncan Mac-Vicar P <dmacvicar@suse.de>
Thu, 20 Mar 2008 17:49:50 +0000 (17:49 +0000)
committerDuncan Mac-Vicar P <dmacvicar@suse.de>
Thu, 20 Mar 2008 17:49:50 +0000 (17:49 +0000)
tests/zypp/base/CMakeLists.txt
tests/zypp/base/InterProcessMutex2_test.cc [new file with mode: 0644]
tests/zypp/base/InterProcessMutex_test.cc [new file with mode: 0644]
zypp/CMakeLists.txt
zypp/base/InterProcessMutex.cc [new file with mode: 0644]
zypp/base/InterProcessMutex.h [new file with mode: 0644]

index 22863f6..248a22b 100644 (file)
@@ -1,2 +1,2 @@
 
-ADD_TESTS(Sysconfig)
\ No newline at end of file
+ADD_TESTS(Sysconfig InterProcessMutex InterProcessMutex2)
diff --git a/tests/zypp/base/InterProcessMutex2_test.cc b/tests/zypp/base/InterProcessMutex2_test.cc
new file mode 100644 (file)
index 0000000..69eb978
--- /dev/null
@@ -0,0 +1,68 @@
+
+#include <sys/wait.h>
+
+#include <iostream>
+#include <fstream>
+#include <map>
+#include <string>
+#include <cstdio>
+
+#include <boost/test/auto_unit_test.hpp>
+
+#include "zypp/base/Logger.h"
+#include "zypp/base/Exception.h"
+#include "zypp/TmpPath.h"
+#include "zypp/PathInfo.h"
+
+#include "zypp/base/Sysconfig.h"
+#include "zypp/base/InterProcessMutex.h"
+
+
+using boost::unit_test::test_suite;
+using boost::unit_test::test_case;
+using namespace boost::unit_test;
+
+using namespace std;
+using namespace zypp;
+using namespace zypp::base;
+
+
+BOOST_AUTO_TEST_CASE(Abort)
+{
+    int r = 0;
+    int status = 0;
+    { 
+        MIL << "ready to fork" << endl;
+    
+        r = fork();
+  
+        if ( r < 0 )
+        {
+            BOOST_ERROR("Can't fork process");
+            return;
+        }
+        else if ( r == 0 )
+        {
+            MIL << "child, PID: " << getpid() << endl;
+            // child
+            sleep(3);
+            BOOST_REQUIRE_THROW( InterProcessMutex("testcase", 0), ZYppLockedException);
+            //InterProcessMutex mutex2("testcase");
+        }
+        else
+        {
+            MIL << "parent: " << getpid() << endl;
+            InterProcessMutex mutex("testcase");
+            // parent
+            sleep(6);
+            waitpid(r, &status, 0);
+            MIL << "first lock will go out of scope" << endl;
+            
+        }        
+    }
+    //if ( r > 0 )
+        
+}
+
+
+
diff --git a/tests/zypp/base/InterProcessMutex_test.cc b/tests/zypp/base/InterProcessMutex_test.cc
new file mode 100644 (file)
index 0000000..8aa2eb0
--- /dev/null
@@ -0,0 +1,70 @@
+
+#include <sys/wait.h>
+
+#include <iostream>
+#include <fstream>
+#include <map>
+#include <string>
+#include <cstdio>
+
+#include <boost/test/auto_unit_test.hpp>
+
+#include "zypp/base/Logger.h"
+#include "zypp/base/Exception.h"
+#include "zypp/TmpPath.h"
+#include "zypp/PathInfo.h"
+
+#include "zypp/base/Sysconfig.h"
+#include "zypp/base/InterProcessMutex.h"
+
+
+using boost::unit_test::test_suite;
+using boost::unit_test::test_case;
+using namespace boost::unit_test;
+
+using namespace std;
+using namespace zypp;
+using namespace zypp::base;
+
+#define DATADIR (Pathname(TESTS_SRC_DIR) + "/zypp/base/data/Sysconfig")
+
+BOOST_AUTO_TEST_CASE(WaitForTheOther)
+{
+    int r = 0;
+    int status = 0;
+    { 
+        MIL << "ready to fork" << endl;
+    
+        r = fork();
+  
+        if ( r < 0 )
+        {
+            BOOST_ERROR("Can't fork process");
+            return;
+        }
+        else if ( r == 0 )
+        {
+            MIL << "child, PID: " << getpid() << endl;
+            // child
+            //BOOST_REQUIRE_THROW( InterProcessMutex("testcase"), ZYppLockedException);
+            sleep(3);
+            InterProcessMutex mutex2("testcase");
+        }
+        else
+        {
+            MIL << "parent: " << getpid() << endl;
+            InterProcessMutex mutex("testcase");
+            // parent
+            sleep(3);
+            
+            MIL << "first lock will go out of scope" << endl;
+            
+        }        
+    }
+    //if ( r > 0 )
+    //    waitpid(r, &status, 0);
+}
+
+
+
+
index d88fb4a..1a05660 100644 (file)
@@ -182,6 +182,7 @@ SET( zypp_HEADERS
 INSTALL(  FILES ${zypp_HEADERS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include/zypp" )
 
 SET( zypp_base_SRCS
+  base/InterProcessMutex.cc
   base/SerialNumber.cc
   base/Random.cc
   base/Measure.cc
@@ -203,6 +204,7 @@ SET( zypp_base_SRCS
 )
 
 SET( zypp_base_HEADERS
+  base/InterProcessMutex.h
   base/Collector.h
   base/SerialNumber.h
   base/Easy.h
diff --git a/zypp/base/InterProcessMutex.cc b/zypp/base/InterProcessMutex.cc
new file mode 100644 (file)
index 0000000..64748cf
--- /dev/null
@@ -0,0 +1,302 @@
+
+extern "C"
+{
+#include <sys/file.h>
+}
+#include <iostream>
+#include <fstream>
+
+#include "zypp/base/Logger.h"
+#include "zypp/base/Gettext.h"
+#include "zypp/base/IOStream.h"
+#include "zypp/base/InterProcessMutex.h"
+#include "zypp/base/String.h"
+
+#include "zypp/Pathname.h"
+#include "zypp/PathInfo.h"
+
+#define ZYPP_LOCK_FILE "/var/run/zypp.pid"
+#define LMIL MIL << "LOCK [" << _name << "] "
+
+using namespace std;
+
+namespace zypp
+{
+namespace base
+{
+
+ ZYppLockedException::ZYppLockedException( const std::string & msg_r,
+                                           const std::string &name,
+                                           pid_t locker_pid )
+    : Exception(msg_r)
+    , _locker_pid (locker_pid)
+    , _name(name)
+{}
+
+ZYppLockedException::~ZYppLockedException() throw()
+{}
+    
+
+InterProcessMutex::InterProcessMutex( const std::string &name,
+                                      int timeout )
+  : _clean_lock(false)
+  , _zypp_lockfile(0)
+  , _locker_pid(0)
+  , _name(name)
+  , _timeout(timeout)
+{
+
+    // get the current pid
+    pid_t curr_pid = getpid();
+    Pathname lock_file = lockFilePath();
+    int totalslept = 0;
+    
+    while (1)
+    {
+        if ( PathInfo(lock_file).isExist() )
+        {
+            LMIL << "found lockfile " << lock_file << std::endl;
+            openLockFile("r");
+            shLockFile();
+
+            pid_t locker_pid = lockerPid();
+            pid_t curr_pid = getpid();
+            LMIL << "Locker pid is " << locker_pid << " (our pid: " << curr_pid << ") "<< std::endl;
+            _locker_pid = locker_pid;
+            // if the same pid has the lock it means we are trying from a new
+            // thread and we have to wait.
+            // if it is a different one and it is running, then we have to wait
+            // too, otherwise we can just take ownership of the lock
+            bool locker_running = isProcessRunning(locker_pid);
+            LMIL << "locker program is " << (locker_running ? "" : " not") << " running." << endl;
+            
+            // if the locker process runs and we are not root
+            // we can give access without risk.
+            if ( locker_running && ( geteuid() != 0 ) )
+            {
+                LMIL << locker_pid << " is running and has a lock. Access as normal user allowed." << std::endl;
+                break;
+            }
+            // if the locker process is not running we have different cases
+            // if we are root, we can clean its lock file, and take ownership of
+            // it, which may work or not depending on result of unlink.
+            // if the process is not running, and we are not root, we are not able to
+            // do much, so we are allowed to continue.
+            else if ( ! locker_running )
+            {
+                if ( geteuid() == 0 )
+                {
+                    LMIL << locker_pid
+                         <<" has a lock, but process is not running. Cleaning lock file." 
+                         << std::endl;
+
+                    if ( filesystem::unlink(lock_file) == 0 )
+                    {
+                        createLockFile();
+                        // now open it for reading
+                        openLockFile("r");
+                        shLockFile();
+                        break;
+                    }
+                    else
+                    {
+                        ERR << "can't clean lockfile. Sorry, can't create a new lock. Lock [" 
+                            << _name << "]  still locked." 
+                            << std::endl;
+                        ZYPP_THROW(ZYppLockedException(
+                                       _("Can't clean lockfile. Sorry, can't create a new lock. Zypp still locked."),
+                                       _name, locker_pid));
+                    }
+                }
+                else
+                {
+                    LMIL << locker_pid 
+                         << " not running and has a lock. Access as normal user allowed." 
+                         << std::endl;
+                    break;
+                }                    
+            }
+            // if the locker process is running and we are root, we have to wait
+            // or go away
+            else /* if ( locker_running && ( geteuid() == 0 ) ) */
+            {
+                LMIL << locker_pid << " is running and has a lock." << std::endl;
+                
+                // abort if we have slept more or equal than the timeout, but
+                // not for the case where timeout is negative which means no
+                // timeout and therefore we never abort.
+                if ( (totalslept >= _timeout) && (_timeout >= 0 ) )
+                    ZYPP_THROW(ZYppLockedException(                                       
+                                   _("This action is being run by another program already."),
+                                   _name, locker_pid));
+                        
+                // if not, let sleep one second and count it
+                LMIL << "waiting 1 second..." << endl;
+                unLockFile();
+                closeLockFile();
+                sleep(1);
+                ++totalslept;
+                continue;
+            }
+        }
+        else
+        {
+            MIL << "no lockfile " << lock_file << " found" << std::endl;
+            if ( geteuid() == 0 )
+            {
+                MIL << "running as root. Will attempt to create " << lock_file << std::endl;
+                createLockFile();
+                // now open it for reading
+                openLockFile("r");
+                shLockFile();
+            }
+            else
+            {
+                MIL << "running as user. Skipping creating " << lock_file << std::endl;
+            }
+            break;
+        }
+        break;
+    }
+    MIL << "Finish constructor" << endl;
+    
+}
+
+InterProcessMutex::~InterProcessMutex()
+{
+    try
+    {
+        pid_t curr_pid = getpid();
+        if ( _zypp_lockfile )
+        {
+            Pathname lock_file = lockFilePath();
+            unLockFile();
+            closeLockFile();
+
+            if ( _clean_lock )
+            {
+                MIL << "LOCK [" << _name << "] : cleaning lock file. (" << curr_pid << ")" << std::endl;
+                if ( filesystem::unlink(lock_file) == 0 )
+                  MIL << "LOCK [" << _name << "] : lockfile cleaned. (" << curr_pid << ")" << std::endl;
+                else
+                  ERR << "LOCK [" << _name  << "] : cant clean lockfile. (" << curr_pid << ")" << std::endl;
+            }
+          }
+      }
+      catch(...) {} // let no exception escape.
+}
+
+
+Pathname InterProcessMutex::lockFilePath() const
+{
+    return Pathname("/var/run/zypp-" + _name + ".pid");
+}    
+
+pid_t
+InterProcessMutex::locker_pid() const
+{
+    return _locker_pid;
+}
+
+void InterProcessMutex::openLockFile(const char *mode)
+{
+    Pathname lock_file = lockFilePath();
+    _zypp_lockfile = fopen(lock_file.asString().c_str(), mode);
+    if (_zypp_lockfile == 0)
+        ZYPP_THROW (Exception( "Cant open " + lock_file.asString() + " in mode " + std::string(mode) ) );
+}
+
+void InterProcessMutex::closeLockFile()
+{
+    fclose(_zypp_lockfile);
+}
+
+void InterProcessMutex::shLockFile()
+{
+    int fd = fileno(_zypp_lockfile);
+    int lock_error = flock(fd, LOCK_SH);
+    if (lock_error != 0)
+        ZYPP_THROW (Exception( "Cant get shared lock"));
+    else
+        XXX << "locked (shared)" << std::endl;
+}
+
+void InterProcessMutex::exLockFile()
+{
+    int fd = fileno(_zypp_lockfile);
+    // lock access to the file
+    int lock_error = flock(fd, LOCK_EX);
+    if (lock_error != 0)
+        ZYPP_THROW (Exception( "Cant get exclusive lock" ));
+    else
+        XXX << "locked (exclusive)" << std::endl;
+}
+
+void InterProcessMutex::unLockFile()
+{
+    int fd = fileno(_zypp_lockfile);
+    // lock access to the file
+    int lock_error = flock(fd, LOCK_UN);
+    if (lock_error != 0)
+        ZYPP_THROW (Exception( "Cant release lock" ));
+    else
+        XXX << "unlocked" << std::endl;
+}
+
+void InterProcessMutex::createLockFile()
+{
+    pid_t curr_pid = getpid();
+    openLockFile("w");
+    exLockFile();
+    fprintf(_zypp_lockfile, "%ld\n", (long) curr_pid);
+    fflush(_zypp_lockfile);
+    unLockFile();
+    MIL << "written lockfile with pid " << curr_pid << std::endl;
+    closeLockFile();
+}
+
+bool InterProcessMutex::isProcessRunning(pid_t pid_r)
+{
+    // it is another program, not me, see if it is still running
+    Pathname procdir( Pathname("/proc") / str::numstring(pid_r) );
+
+    PathInfo status( procdir/"status" );
+    XXX << "Checking " <<  status << endl;
+    bool still_running = status.isExist();
+
+    if ( still_running )
+    {
+        Pathname p( procdir/"exe" );
+        XXX << p << " -> " << filesystem::readlink( p ) << endl;
+
+        p = procdir/"cmdline";
+        XXX << p << ": ";
+        std::ifstream infile( p.c_str() );
+        for( iostr::EachLine in( infile ); in; in.next() )
+        {
+          XXX << *in << endl;
+        }
+     }
+
+     return still_running;
+}
+
+pid_t InterProcessMutex::lockerPid()
+{
+    pid_t locker_pid = 0;
+    long readpid = 0;
+
+    fscanf(_zypp_lockfile, "%ld", &readpid);
+    locker_pid = (pid_t) readpid;
+    return locker_pid;
+}
+
+bool InterProcessMutex::locked()
+{
+    return true;
+}
+
+}
+}
+
+
diff --git a/zypp/base/InterProcessMutex.h b/zypp/base/InterProcessMutex.h
new file mode 100644 (file)
index 0000000..7e83d5a
--- /dev/null
@@ -0,0 +1,87 @@
+
+#ifndef ZYPP_BASE_INTER_PROCESS_MUTEX_H
+#define ZYPP_BASE_INTER_PROCESS_MUTEX_H
+
+#include <string>
+#include "zypp/base/Exception.h"
+#include "zypp/Pathname.h"
+
+namespace zypp
+{
+namespace base
+{
+
+class ZYppLockedException : public Exception
+{
+public:
+    ZYppLockedException( const std::string & msg_r,
+                         const std::string &name,
+                         pid_t locker_pid );
+    virtual ~ZYppLockedException() throw();
+    pid_t locker_pid() const { return _locker_pid; }
+    std::string name() const { return _name; }
+private:
+    pid_t _locker_pid;
+    std::string _name;
+};
+
+
+/**
+ * Inter process scoped lock implementation
+ *
+ * This mutex will allow only one process to
+ * reach a critical region protected by a mutex
+ * of the same name.
+ *
+ */
+class InterProcessMutex
+{
+public:
+   /**
+    * Creates a mutex with a name and a timeout.
+    *
+    * default timeout is -1 which means no timeout
+    * at all, and the mutex will wait forever if
+    * other process is accessing the critical region
+    * for a mutex in with the same name.
+    *
+    * If the timeout is 0, then if the lock is acquired
+    * an exception will be thrown inmediately.
+    *
+    * Otherwise, the timeout exception will come after
+    * the timeout is reached.
+    *
+    */
+    InterProcessMutex( const std::string &name = "zypp",
+                      int timeout = -1 );
+
+    ~InterProcessMutex();
+
+    pid_t locker_pid() const;
+
+private:
+    void openLockFile(const char *mode);
+    void closeLockFile();
+
+    void shLockFile();
+    void exLockFile();
+    void unLockFile();
+    void createLockFile();
+    bool isProcessRunning(pid_t pid_r);
+    pid_t lockerPid();
+    bool locked();
+    Pathname lockFilePath() const;
+private:
+    bool _clean_lock;
+    FILE *_zypp_lockfile;
+    pid_t _locker_pid;
+    std::string _name;
+    int _timeout;
+};
+
+
+} }
+
+
+#endif
+