PIDFile pid_file(pid_file_);
try {
- if (pid_file.check()) {
+ // Acquire a lock for check and write operations.
+ PIDLock pid_lock(pid_file.getLockname());
+
+ if (!pid_lock.isLocked() || pid_file.check()) {
// Already running instance, bail out
LOG_FATAL(lfc_logger, LFC_RUNNING);
return;
lease_file = Memfile_LeaseMgr::getDefaultLeaseFilePath(u);
}
PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(lease_file, FILE_PID));
- return (pid_file.check());
+ PIDLock pid_lock(pid_file.getLockname());
+ return (!pid_lock.isLocked() || pid_file.check());
}
std::string
return ("");
}
+std::string
+Daemon::getPIDLockName() const {
+ if (pid_file_) {
+ return (pid_file_->getLockname());
+ }
+
+ return ("");
+}
+
void
Daemon::setPIDFileName(const std::string& pid_file_name) {
if (pid_file_) {
setPIDFileName(makePIDFileName());
}
+ // Acquire a lock for check and write operations.
+ util::PIDLock pid_lock(getPIDLockName());
+ if (!pid_lock.isLocked()) {
+ isc_throw(DaemonPIDExists, "Daemon::createPIDFile: can't lock, "
+ "PID lock file: " << getPIDLockName());
+ }
+
// If we find a pre-existing file containing a live PID we bail.
int chk_pid = pid_file_->check();
if (chk_pid > 0) {
/// @return text string
std::string getPIDFileName() const;
+ /// @brief Returns the current PID lock file name
+ /// @return text string
+ std::string getPIDLockName() const;
+
/// @brief Sets PID file name
///
/// If this method is called prior to calling createPIDFile,
/// @brief Creates the PID file
///
/// If the PID file name has not been previously set, the method
- /// uses manufacturePIDFileName() to set it. If the PID file
+ /// uses makePIDFileName() to set it. If the PID file
/// name refers to an existing file whose contents are a PID whose
/// process is still alive, the method will throw a DaemonPIDExists
/// exception. Otherwise, the file created (or truncated) and
/// the given pid (if not zero) is written to the file.
+ /// As there is a race condition between the check and write phases
+ /// a lock is taken.
///
/// @param pid PID to write to the file if not zero, otherwise the
/// PID of the current process is used.
#include <signal.h>
#include <unistd.h>
#include <cerrno>
+#include <sys/file.h>
namespace isc {
namespace util {
-PIDFile::PIDFile(const std::string& filename)
- : filename_(filename) {
-}
-
-PIDFile::~PIDFile() {
-}
-
int
PIDFile::check() const {
std::ifstream fs(filename_.c_str());
}
}
+PIDLock::PIDLock(const std::string& lockname)
+ : lockname_(lockname), fd_(-1), locked_(false) {
+ // Open the lock file.
+ fd_ = open(lockname_.c_str(), O_RDONLY | O_CREAT, 0600);
+ if (fd_ == -1) {
+ if (errno == ENOENT) {
+ // Ignoring missing component in the path.
+ locked_ = true;
+ return;
+ }
+ std::string errmsg = strerror(errno);
+ isc_throw(PIDFileError, "cannot create pid lockfile '"
+ << lockname_ << "': " << errmsg);
+ }
+ // Try to acquire the lock. If we can't somebody else is actively
+ // using it.
+ int ret = flock(fd_, LOCK_EX | LOCK_NB);
+ if (ret == 0) {
+ locked_ = true;
+ return;
+ }
+ if (errno != EWOULDBLOCK) {
+ std::string errmsg = strerror(errno);
+ isc_throw(PIDFileError, "cannot lock pid lockfile '"
+ << lockname_ << "': " << errmsg);
+ }
+}
+
+PIDLock::~PIDLock() {
+ if (fd_ != -1) {
+ if (locked_) {
+ // For symmetry as the close releases the lock...
+ static_cast<void>(flock(fd_, LOCK_UN));
+ }
+ static_cast<void>(close(fd_));
+ }
+ fd_ = -1;
+ locked_ = false;
+}
+
} // namespace isc::util
} // namespace isc
/// @brief Constructor
///
/// @param filename PID filename.
- PIDFile(const std::string& filename);
+ PIDFile(const std::string& filename)
+ : filename_(filename) {
+ }
/// @brief Destructor
- ~PIDFile();
+ virtual ~PIDFile() = default;
/// @brief Read the PID in from the file and check it.
///
/// If the file exists but can't be read or it doesn't have
/// the proper format treat it as the process existing.
///
+ /// The PID file should be locked to avoid a race condition.
+ ///
/// @return returns the PID if it is in, 0 otherwise.
///
/// @throw throws PIDCantReadPID if it was able to open the file
/// @brief Write the PID to the file.
///
+ /// The PID file must be locked to avoid a race condition.
+ ///
/// @param pid the pid to write to the file.
///
/// @throw throws PIDFileError if it can't open or write to the PID file.
/// @brief Get PID of the current process and write it to the file.
///
+ /// The PID file must be locked to avoid a race condition.
+ ///
/// @throw throws PIDFileError if it can't open or write to the PID file.
void write() const;
/// @brief Delete the PID file.
///
+ /// This is an atomic operation not subject to a race condition.
+ ///
/// @throw throws PIDFileError if it can't delete the PID file
void deleteFile() const;
return (filename_);
}
+ /// @brief Returns the path to the lock file.
+ std::string getLockname() const {
+ return (filename_ + ".lock");
+ }
+
private:
/// @brief PID filename
std::string filename_;
/// @brief Defines a shared pointer to a PIDFile
typedef boost::shared_ptr<PIDFile> PIDFilePtr;
+/// @brief RAII device to handle a lock file to avoid race conditions.
+class PIDLock {
+public:
+ /// @brief Constructor
+ ///
+ /// Try to get a lock.
+ ///
+ /// @param lockname Lock filename.
+ PIDLock(const std::string& lockname);
+
+ /// @brief Destructor
+ ///
+ /// Release the lock when taken and delete the lock file.
+ ~PIDLock();
+
+ /// @brief Return the lock status.
+ bool isLocked() {
+ return (locked_);
+ }
+
+private:
+ /// @brief Name of the Lock file.
+ std::string lockname_;
+
+ /// @brief File descriptor to the lock file.
+ int fd_;
+
+ /// @brief Lock status.
+ bool locked_;
+};
+
} // namespace isc::util
} // namespace isc
#include <util/pid_file.h>
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
#include <fstream>
#include <signal.h>
#include <stdint.h>
// Filenames used for testing.
const char* TESTNAME = "pid_file.test";
+const char* TESTLOCKNAME = "pid_file.test.lock";
class PIDFileTest : public ::testing::Test {
public:
protected:
/// @brief Removes any old test files before the test
virtual void SetUp() {
- removeTestFile();
+ removeTestFiles();
}
/// @brief Removes any remaining test files after the test
virtual void TearDown() {
- removeTestFile();
+ removeTestFiles();
}
private:
/// @brief Removes any remaining test files
- void removeTestFile() const {
+ void removeTestFiles() const {
static_cast<void>(remove(absolutePath(TESTNAME).c_str()));
+ static_cast<void>(remove(absolutePath(TESTLOCKNAME).c_str()));
}
};
// Delete a file we haven't created
pid_file.deleteFile();
}
+
+/// @brief Test getting a lock.
+TEST_F(PIDFileTest, lock) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ EXPECT_EQ(absolutePath(TESTLOCKNAME), pid_file.getLockname());
+
+ PIDLock lock(absolutePath(TESTLOCKNAME));
+ EXPECT_TRUE(lock.isLocked());
+
+ PIDLock lock2(absolutePath(TESTLOCKNAME));
+ EXPECT_FALSE(lock2.isLocked());
+}
+
+/// @brief Test getting and releasing a lock.
+TEST_F(PIDFileTest, lock2) {
+ {
+ PIDLock lock(absolutePath(TESTLOCKNAME));
+ EXPECT_TRUE(lock.isLocked());
+ }
+ {
+ PIDLock lock2(absolutePath(TESTLOCKNAME));
+ EXPECT_TRUE(lock2.isLocked());
+ }
+}
+
+/// @brief Test ignoring a path with a missing component.
+TEST_F(PIDFileTest, lockNoent) {
+ boost::scoped_ptr<PIDLock> lock;
+ ASSERT_NO_THROW(lock.reset(new PIDLock("/does/not/exist.lock")));
+ ASSERT_TRUE(lock);
+ EXPECT_TRUE(lock->isLocked());
+}
+
} // end of anonymous namespace