Scheduler: Daemon class: pidfile creation
g0dil [Fri, 9 Nov 2007 13:46:24 +0000 (13:46 +0000)]
Scheduler: Daemon class: Fix DaemonWatcher to not drop info from stdout/stderr when the child terminates
Scheduler: Daemon class: Replace all ::exit() calls in the daemon watcher with ::_exit()

git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@502 270642c3-0616-0410-b53a-bc976706d245

Scheduler/Daemon.cc
Scheduler/Daemon.hh
Scheduler/Daemon.ih
Scheduler/Daemon.test.cc

index 5252dcf..5c94256 100644 (file)
@@ -34,6 +34,7 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <signal.h>
+#include <sstream>
 #include "../Utils/Exception.hh"
 #include "../Utils/membind.hh"
 
 // senf::Daemon
 
 prefix_ senf::Daemon::~Daemon()
-{}
+{
+    if (! pidfile_.empty())
+        LIBC_CALL( ::unlink, (pidfile_.c_str()) );
+}
 
 prefix_ void senf::Daemon::daemonize(bool v)
 {
@@ -82,18 +86,19 @@ prefix_ void senf::Daemon::consoleLog(std::string path, StdStream which)
     }
 }
 
-prefix_ void senf::Daemon::pidFile(std::string f, bool unique)
+prefix_ void senf::Daemon::pidFile(std::string f)
 {
     pidfile_ = f;
-    unique_ = unique;
 }
 
 prefix_ void senf::Daemon::detach()
 {
-    LIBC_CALL_RV( nul, ::open, ("/dev/null", O_WRONLY) );
-    LIBC_CALL( ::dup2, (nul, 1) );
-    LIBC_CALL( ::dup2, (nul, 2) );
-    LIBC_CALL( ::close, (nul) );
+    if (daemonize_) {
+        LIBC_CALL_RV( nul, ::open, ("/dev/null", O_WRONLY) );
+        LIBC_CALL( ::dup2, (nul, 1) );
+        LIBC_CALL( ::dup2, (nul, 2) );
+        LIBC_CALL( ::close, (nul) );
+    }
 }
 
 prefix_ int senf::Daemon::start(int argc, char const ** argv)
@@ -105,12 +110,17 @@ prefix_ int senf::Daemon::start(int argc, char const ** argv)
     try {
 #   endif
 
-    configure();
-    if (daemonize_)
-        fork();
-    if (! pidfile_.empty())
-        pidfileCreate();
-    main();
+        configure();
+
+        if (daemonize_)
+            fork();
+        if (! pidfile_.empty() && ! pidfileCreate()) {
+            std::cerr << "\n*** PID file '" << pidfile_ << "' creation failed. Daemon running ?" 
+                      << std::endl;
+            return 1;
+        }
+
+        main();
 
 #   ifdef NDEBUG
     }
@@ -131,7 +141,7 @@ prefix_ int senf::Daemon::start(int argc, char const ** argv)
 // protected members
 
 prefix_ senf::Daemon::Daemon()
-    : argc_(0), argv_(0), daemonize_(true), stdout_(-1), stderr_(-1), pidfile_(""), unique_(true),
+    : argc_(0), argv_(0), daemonize_(true), stdout_(-1), stderr_(-1), pidfile_(""),
       detached_(false)
 {}
 
@@ -189,46 +199,139 @@ prefix_ void senf::Daemon::fork()
         return;
     }
 
+    // Ouch ... ensure, the daemon watcher does not remove the pidfile ...
+    pidfile_ = "";
+    
     LIBC_CALL( ::close, (coutpipe[1]) );
     LIBC_CALL( ::close, (cerrpipe[1]) );
 
     detail::DaemonWatcher watcher (pid, coutpipe[0], cerrpipe[0]);
     watcher.run();
 
-    ::exit(0);
-
+    ::_exit(0);
 }
 
-prefix_ void senf::Daemon::pidfileCreate()
-{}
+prefix_ bool senf::Daemon::pidfileCreate()
+{
+    // Create temporary file pidfile_.hostname.pid and hard-link it to pidfile_ If the hardlink
+    // fails, the pidfile exists. If the link count of the temporary file is not 2 after this, there
+    // was some race condition, probably over NFS.
+
+    std::string tempname;
+
+    {
+        char hostname[HOST_NAME_MAX+1];
+        LIBC_CALL( ::gethostname, (hostname, HOST_NAME_MAX+1) );
+        hostname[HOST_NAME_MAX] = 0;
+        std::stringstream tempname_s;
+        tempname_s << pidfile_ << "." << hostname << "." << ::getpid();
+        tempname = tempname_s.str();
+    }
+
+    while (1) {
+        {
+            std::ofstream pidf (tempname.c_str());
+            pidf << ::getpid() << std::endl;
+        }
+
+        if (::link(tempname.c_str(), pidfile_.c_str()) < 0) {
+            if (errno != EEXIST) 
+                throwErrno("::link()");
+        }
+        else {
+            struct ::stat s;
+            LIBC_CALL( ::stat, (tempname.c_str(), &s) );
+            LIBC_CALL( ::unlink, (tempname.c_str()) );
+            return s.st_nlink == 2;
+        }
+
+        // pidfile exists. Check, whether the pid in the pidfile still exists.
+        {
+            int old_pid (-1);
+            std::ifstream pidf (pidfile_.c_str());
+            if ( ! (pidf >> old_pid)
+                 || old_pid < 0 
+                 || ::kill(old_pid, 0) >= 0 
+                 || errno == EPERM )
+                return false;
+        }
+
+        // If we reach this point, the pid file exists but the process mentioned within the
+        // pid file does *not* exists. We assume, the pid file to be stale.
+
+        // I hope, the following procedure is without race condition: We remove our generated
+        // temporary pid file and recreate it as hard-link to the old pid file. Now we check, that
+        // the hard-link count of this file is 2. If it is not, we terminate, since someone else
+        // must have already created his hardlink. We then truncate the file and write our pid.
+
+        LIBC_CALL( ::unlink, (tempname.c_str() ));
+        if (::link(pidfile_.c_str(), tempname.c_str()) < 0) {
+            if (errno != ENOENT) throwErrno("::link()");
+            // Hmm ... the pidfile mysteriously disappeared ... try again.
+            continue;
+        }
+
+        {
+            struct ::stat s;
+            LIBC_CALL( ::stat, (tempname.c_str(), &s) );
+            if (s.st_nlink != 2) {
+                LIBC_CALL( ::unlink, (tempname.c_str()) );
+                return false;
+            }
+        }
+        
+        {
+            std::ofstream pidf (tempname.c_str());
+            pidf << ::getpid() << std::endl;
+        }
+
+        LIBC_CALL( ::unlink, (tempname.c_str()) );
+        break;
+    }
+    return true;
+}
 
 ///////////////////////////////////////////////////////////////////////////
 // senf::detail::DaemonWatcher
 
 prefix_ senf::detail::DaemonWatcher::DaemonWatcher(int pid, int coutpipe, int cerrpipe)
-    : childPid_(pid), coutpipe_(coutpipe), cerrpipe_(cerrpipe), 
-      coutForwarder_(coutpipe_, 1, senf::membind(&DaemonWatcher::pipeClosed, this)),
-      cerrForwarder_(cerrpipe_, 2, senf::membind(&DaemonWatcher::pipeClosed, this))
+    : childPid_(pid), coutpipe_(coutpipe), cerrpipe_(cerrpipe), sigChld_(false),
+      coutForwarder_(coutpipe_, 1, boost::bind(&DaemonWatcher::pipeClosed, this, 1)),
+      cerrForwarder_(cerrpipe_, 2, boost::bind(&DaemonWatcher::pipeClosed, this, 2))
 {}
 
 prefix_ void senf::detail::DaemonWatcher::run()
 {
-    Scheduler::instance().registerSignal(SIGCHLD, senf::membind(&DaemonWatcher::childDied, this));
+    Scheduler::instance().registerSignal(SIGCHLD, senf::membind(&DaemonWatcher::sigChld, this));
     Scheduler::instance().process();
 }
 
 ////////////////////////////////////////
 // private members
 
-prefix_ void senf::detail::DaemonWatcher::pipeClosed()
+prefix_ void senf::detail::DaemonWatcher::pipeClosed(int id)
 {
-    if (! timerRunning_) {
-        Scheduler::instance().timeout(Scheduler::instance().eventTime() + ClockService::seconds(1),
-                                      senf::membind(&DaemonWatcher::childOk, this));
-        timerRunning_ = true;
+    switch (id) {
+    case 1 : coutpipe_ = -1; break;
+    case 2 : cerrpipe_ = -1; break;
+    }
+
+    if (coutpipe_ == -1 && cerrpipe_ == -1) {
+        if (sigChld_)
+            childDied(); // does not return
+        Scheduler::instance().timeout(
+            Scheduler::instance().eventTime() + ClockService::seconds(1),
+            senf::membind(&DaemonWatcher::childOk, this));
     }
 }
 
+prefix_ void senf::detail::DaemonWatcher::sigChld()
+{
+    sigChld_ = true;
+    if (coutpipe_ == -1 && cerrpipe_ == -1)
+        childDied(); // does not return
+}
+
 prefix_ void senf::detail::DaemonWatcher::childDied()
 {
     int status (0);
@@ -237,11 +340,11 @@ prefix_ void senf::detail::DaemonWatcher::childDied()
         ::signal(WTERMSIG(status),SIG_DFL);
         ::kill(::getpid(), WTERMSIG(status));
         // should not be reached
-        ::exit(1);
+        ::_exit(1);
     }
     if (WEXITSTATUS(status) == 0)
-        ::exit(1);
-    ::exit(WEXITSTATUS(status));
+        ::_exit(1);
+    ::_exit(WEXITSTATUS(status));
 }
 
 prefix_ void senf::detail::DaemonWatcher::childOk()
index 205dcad..38aed48 100644 (file)
@@ -106,7 +106,7 @@ namespace senf {
                                              When running in the foreground, the log files will be
                                              ignored. */
 
-        void pidFile(std::string, bool unique = true); ///< Configure pid file
+        void pidFile(std::string);      ///< Configure pid file
 
         ///\}
         ///\name Auxiliary helpers
@@ -161,7 +161,7 @@ namespace senf {
     private:
 
         void fork();
-        void pidfileCreate();
+        bool pidfileCreate();
 
         int argc_;
         char const ** argv_;
@@ -170,7 +170,6 @@ namespace senf {
         int stdout_;
         int stderr_;
         std::string pidfile_;
-        bool unique_;
 
         bool detached_;
     };
index 23af6ce..6f49232 100644 (file)
@@ -69,18 +69,18 @@ namespace detail {
             Callback cb_;
         };
         
-        void pipeClosed();
+        void pipeClosed(int id);
+        void sigChld();
         void childDied();
         void childOk();
 
         int childPid_;
         int coutpipe_;
         int cerrpipe_;
+        bool sigChld_;
 
         Forwarder coutForwarder_;
         Forwarder cerrForwarder_;
-
-        bool timerRunning_;
     };
 
 }}
index 418ea9f..9c6b22f 100644 (file)
@@ -31,6 +31,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <iostream>
+#include <boost/filesystem/operations.hpp>
 #include "Daemon.hh"
 #include "../Utils/Exception.hh"
 
@@ -52,11 +53,18 @@ namespace {
 
     class MyDaemon : public senf::Daemon
     {
-        void configure() { std::cout << "Running configure()" << std::endl; }
-        void init() { std::cout << "Running init()" << std::endl; }
+        void configure() { 
+            std::cout << "Running configure()" << std::endl; 
+            pidFile("testDaemon.pid");
+        }
+
+        void init() { 
+            std::cout << "Running init()" << std::endl; 
+        }
+
         void run() {
-            delay(2000);
             std::cout << "Running run()" << std::endl; 
+            delay(1500);
         }
     };
 
@@ -84,6 +92,10 @@ BOOST_AUTO_UNIT_TEST(testDaemon)
 {
     char const * args[] = { "run", 0 };
     BOOST_CHECK_EQUAL( run(1,args), 0 );
+
+    BOOST_CHECK( boost::filesystem::exists("testDaemon.pid") );
+    delay(1000);
+    BOOST_CHECK( ! boost::filesystem::exists("testDaemon.pid") );
 }
 
 ///////////////////////////////cc.e////////////////////////////////////////