aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoel Rosdahl <joel@rosdahl.net>2020-03-06 19:39:32 +0100
committerJoel Rosdahl <joel@rosdahl.net>2020-03-10 21:05:01 +0100
commitc181fdd983376014e6c27b6e0b64af9a7e0eb7e3 (patch)
tree9db44b2eb086d6b613ef88bba39efccd45c9fd53
parentc468acbf200a5352ac34f798107241d49500d533 (diff)
downloadccache-c181fdd983376014e6c27b6e0b64af9a7e0eb7e3.tar.gz
ccache-c181fdd983376014e6c27b6e0b64af9a7e0eb7e3.tar.bz2
ccache-c181fdd983376014e6c27b6e0b64af9a7e0eb7e3.zip
C++-ify lockfile routines
-rw-r--r--Makefile.in4
-rw-r--r--src/Lockfile.cpp216
-rw-r--r--src/Lockfile.hpp55
-rw-r--r--src/stats.cpp38
-rw-r--r--unittest/main.cpp2
-rw-r--r--unittest/test_Lockfile.cpp (renamed from src/lockfile.hpp)35
6 files changed, 324 insertions, 26 deletions
diff --git a/Makefile.in b/Makefile.in
index 6600dd3d..fb070555 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -42,6 +42,7 @@ non_third_party_sources = \
src/Context.cpp \
src/Counters.cpp \
src/Decompressor.cpp \
+ src/Lockfile.cpp \
src/NullCompressor.cpp \
src/NullDecompressor.cpp \
src/ProgressBar.cpp \
@@ -60,7 +61,6 @@ non_third_party_sources = \
src/hashutil.cpp \
src/language.cpp \
src/legacy_util.cpp \
- src/lockfile.cpp \
src/logging.cpp \
src/manifest.cpp \
src/result.cpp \
@@ -86,6 +86,7 @@ test_suites += unittest/test_Checksum.cpp
test_suites += unittest/test_Compression.cpp
test_suites += unittest/test_Config.cpp
test_suites += unittest/test_FormatNonstdStringView.cpp
+test_suites += unittest/test_Lockfile.cpp
test_suites += unittest/test_NullCompression.cpp
test_suites += unittest/test_ScopeGuard.cpp
test_suites += unittest/test_Stat.cpp
@@ -97,7 +98,6 @@ test_suites += unittest/test_compopt.cpp
test_suites += unittest/test_hash.cpp
test_suites += unittest/test_hashutil.cpp
test_suites += unittest/test_legacy_util.cpp
-test_suites += unittest/test_lockfile.cpp
test_sources += unittest/catch2_tests.cpp
test_sources += unittest/framework.cpp
diff --git a/src/Lockfile.cpp b/src/Lockfile.cpp
new file mode 100644
index 00000000..7aba595e
--- /dev/null
+++ b/src/Lockfile.cpp
@@ -0,0 +1,216 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "Lockfile.hpp"
+
+#include "Util.hpp"
+#include "legacy_util.hpp"
+#include "logging.hpp"
+
+#include "third_party/fmt/core.h"
+
+namespace {
+
+#ifndef _WIN32
+
+bool
+do_acquire_posix(const std::string& lockfile, uint32_t staleness_limit)
+{
+ const uint32_t max_to_sleep = 10000; // Microseconds.
+ uint32_t to_sleep = 1000; // Microseconds.
+ uint32_t slept = 0; // Microseconds.
+ std::string initial_content;
+
+ while (true) {
+ std::string my_content =
+ fmt::format("{}:{}:{}", get_hostname(), getpid(), time(nullptr));
+
+ if (symlink(my_content.c_str(), lockfile.c_str()) == 0) {
+ // We got the lock.
+ return true;
+ }
+
+ int saved_errno = errno;
+ cc_log("lockfile_acquire: symlink %s: %s",
+ lockfile.c_str(),
+ strerror(saved_errno));
+ if (saved_errno == ENOENT) {
+ // Directory doesn't exist?
+ if (Util::create_dir(Util::dir_name(lockfile))) {
+ // OK. Retry.
+ continue;
+ }
+ }
+
+ if (saved_errno == EPERM) {
+ // The file system does not support symbolic links. We have no choice but
+ // to grant the lock anyway.
+ return true;
+ }
+
+ if (saved_errno != EEXIST) {
+ // Directory doesn't exist or isn't writable?
+ return false;
+ }
+
+ std::string content = Util::read_link(lockfile);
+ if (content.empty()) {
+ if (errno == ENOENT) {
+ // The symlink was removed after the symlink() call above, so retry
+ // acquiring it.
+ continue;
+ } else {
+ cc_log("lockfile_acquire: readlink %s: %s",
+ lockfile.c_str(),
+ strerror(errno));
+ return false;
+ }
+ }
+
+ if (content == my_content) {
+ // Lost NFS reply?
+ cc_log("lockfile_acquire: symlink %s failed but we got the lock anyway",
+ lockfile.c_str());
+ return true;
+ }
+
+ // A possible improvement here would be to check if the process holding the
+ // lock is still alive and break the lock early if it isn't.
+ cc_log("lockfile_acquire: lock info for %s: %s",
+ lockfile.c_str(),
+ content.c_str());
+
+ if (initial_content.empty()) {
+ initial_content = content;
+ }
+
+ if (slept <= staleness_limit) {
+ cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
+ lockfile.c_str(),
+ to_sleep);
+ usleep(to_sleep);
+ slept += to_sleep;
+ to_sleep = std::min(max_to_sleep, 2 * to_sleep);
+ } else if (content != initial_content) {
+ cc_log("lockfile_acquire: gave up acquiring %s", lockfile.c_str());
+ return false;
+ } else {
+ // The lock seems to be stale -- break it and try again.
+ cc_log("lockfile_acquire: breaking %s", lockfile.c_str());
+ if (tmp_unlink(lockfile.c_str()) != 0) {
+ cc_log("Failed to unlink %s: %s", lockfile.c_str(), strerror(errno));
+ return false;
+ }
+ to_sleep = 1000;
+ slept = 0;
+ initial_content.clear();
+ }
+ }
+}
+
+#else // !_WIN32
+
+HANDLE
+do_acquire_win32(const std::string& lockfile, uint32_t staleness_limit)
+{
+ unsigned to_sleep = 1000; // Microseconds.
+ unsigned max_to_sleep = 10000; // Microseconds.
+ unsigned slept = 0; // Microseconds.
+ HANDLE handle;
+
+ while (true) {
+ DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE;
+ handle = CreateFile(lockfile.c_str(),
+ GENERIC_WRITE, // desired access
+ 0, // shared mode (0 = not shared)
+ NULL, // security attributes
+ CREATE_ALWAYS, // creation disposition
+ flags, // flags and attributes
+ NULL // template file
+ );
+ if (handle != INVALID_HANDLE_VALUE) {
+ break;
+ }
+
+ DWORD error = GetLastError();
+ cc_log("lockfile_acquire: CreateFile %s: error code %lu",
+ lockfile.c_str(),
+ error);
+ if (error == ERROR_PATH_NOT_FOUND) {
+ // Directory doesn't exist?
+ if (Util::create_dir(Util::dir_name(lockfile)) == 0) {
+ // OK. Retry.
+ continue;
+ }
+ }
+
+ // ERROR_SHARING_VIOLATION: lock already held.
+ // ERROR_ACCESS_DENIED: maybe pending delete.
+ if (error != ERROR_SHARING_VIOLATION && error != ERROR_ACCESS_DENIED) {
+ // Fatal error, give up.
+ break;
+ }
+
+ if (slept > staleness_limit) {
+ cc_log("lockfile_acquire: gave up acquiring %s", lockfile.c_str());
+ break;
+ }
+
+ cc_log("lockfile_acquire: failed to acquire %s; sleeping %u microseconds",
+ lockfile.c_str(),
+ to_sleep);
+ usleep(to_sleep);
+ slept += to_sleep;
+ to_sleep = std::min(max_to_sleep, 2 * to_sleep);
+ }
+
+ return handle;
+}
+
+#endif // !_WIN32
+
+} // namespace
+
+Lockfile::Lockfile(const std::string& path, uint32_t staleness_limit)
+ : m_lockfile(path + ".lock")
+{
+#ifndef _WIN32
+ m_acquired = do_acquire_posix(m_lockfile, staleness_limit);
+#else
+ m_handle = do_acquire_win32(m_lockfile, staleness_limit);
+#endif
+ if (acquired()) {
+ cc_log("Acquired lock %s", m_lockfile.c_str());
+ } else {
+ cc_log("Failed to acquire lock %s", m_lockfile.c_str());
+ }
+}
+
+Lockfile::~Lockfile()
+{
+ if (acquired()) {
+ cc_log("Releasing lock %s", m_lockfile.c_str());
+#ifndef _WIN32
+ if (tmp_unlink(m_lockfile.c_str()) != 0) {
+ cc_log("Failed to unlink %s: %s", m_lockfile.c_str(), strerror(errno));
+ }
+#else
+ CloseHandle(m_handle);
+#endif
+ }
+}
diff --git a/src/Lockfile.hpp b/src/Lockfile.hpp
new file mode 100644
index 00000000..78baed81
--- /dev/null
+++ b/src/Lockfile.hpp
@@ -0,0 +1,55 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#pragma once
+
+#include "system.hpp"
+
+#include <string>
+
+class Lockfile
+{
+public:
+ // Acquire a lock on `path`. Break the lock (or give up, depending on
+ // implementation) after `staleness_limit` Microseconds.
+ Lockfile(const std::string& path, uint32_t staleness_limit = 2000000);
+
+ // Release the lock if acquired.
+ ~Lockfile();
+
+ // Return whether the lockfile was acquired successfully.
+ bool acquired() const;
+
+private:
+ std::string m_lockfile;
+#ifndef _WIN32
+ bool m_acquired = false;
+#else
+ HANDLE m_handle = nullptr;
+#endif
+};
+
+inline bool
+Lockfile::acquired() const
+{
+#ifndef _WIN32
+ return m_acquired;
+#else
+ return m_handle != INVALID_HANDLE_VALUE;
+#endif
+}
diff --git a/src/stats.cpp b/src/stats.cpp
index 2008445c..72bd4564 100644
--- a/src/stats.cpp
+++ b/src/stats.cpp
@@ -25,9 +25,9 @@
#include "AtomicFile.hpp"
#include "Context.hpp"
#include "Counters.hpp"
+#include "Lockfile.hpp"
#include "cleanup.hpp"
#include "hashutil.hpp"
-#include "lockfile.hpp"
#include "logging.hpp"
#include "third_party/fmt/core.h"
@@ -41,9 +41,6 @@
#include <sys/types.h>
#include <unistd.h>
-// How long (in microseconds) to wait before breaking a stale lock.
-constexpr unsigned k_lock_staleness_limit = 2000000;
-
#define FLAG_NOZERO 1 // don't zero with the -z option
#define FLAG_ALWAYS 2 // always show, even if zero
#define FLAG_NEVER 4 // never show
@@ -349,17 +346,20 @@ stats_flush_to_file(const Config& config,
"{}/{:x}/stats", config.cache_dir(), hash_from_int(getpid()) % 16);
}
- if (!lockfile_acquire(sfile.c_str(), k_lock_staleness_limit)) {
- return;
- }
-
Counters counters;
- stats_read(sfile, counters);
- for (int i = 0; i < STATS_END; ++i) {
- counters[i] += updates[i];
+
+ {
+ Lockfile lock(sfile);
+ if (!lock.acquired()) {
+ return;
+ }
+
+ stats_read(sfile, counters);
+ for (int i = 0; i < STATS_END; ++i) {
+ counters[i] += updates[i];
+ }
+ stats_write(sfile, counters);
}
- stats_write(sfile, counters);
- lockfile_release(sfile.c_str());
std::string subdir(Util::dir_name(sfile));
bool need_cleanup = false;
@@ -499,7 +499,8 @@ stats_zero(const Config& config)
free(fname);
continue;
}
- if (lockfile_acquire(fname, k_lock_staleness_limit)) {
+ Lockfile lock(fname);
+ if (lock.acquired()) {
stats_read(fname, counters);
for (unsigned i = 0; stats_info[i].message; i++) {
if (!(stats_info[i].flags & FLAG_NOZERO)) {
@@ -508,7 +509,6 @@ stats_zero(const Config& config)
}
counters[STATS_ZEROTIMESTAMP] = timestamp;
stats_write(fname, counters);
- lockfile_release(fname);
}
free(fname);
}
@@ -534,12 +534,12 @@ stats_set_sizes(const char* dir, unsigned num_files, uint64_t total_size)
{
Counters counters;
char* statsfile = format("%s/stats", dir);
- if (lockfile_acquire(statsfile, k_lock_staleness_limit)) {
+ Lockfile lock(statsfile);
+ if (lock.acquired()) {
stats_read(statsfile, counters);
counters[STATS_NUMFILES] = num_files;
counters[STATS_TOTALSIZE] = total_size / 1024;
stats_write(statsfile, counters);
- lockfile_release(statsfile);
}
free(statsfile);
}
@@ -550,11 +550,11 @@ stats_add_cleanup(const char* dir, unsigned count)
{
Counters counters;
char* statsfile = format("%s/stats", dir);
- if (lockfile_acquire(statsfile, k_lock_staleness_limit)) {
+ Lockfile lock(statsfile);
+ if (lock.acquired()) {
stats_read(statsfile, counters);
counters[STATS_NUMCLEANUPS] += count;
stats_write(statsfile, counters);
- lockfile_release(statsfile);
}
free(statsfile);
}
diff --git a/unittest/main.cpp b/unittest/main.cpp
index eb16c762..d6ddb2d6 100644
--- a/unittest/main.cpp
+++ b/unittest/main.cpp
@@ -28,7 +28,6 @@ unsigned suite_conf(unsigned);
unsigned suite_hash(unsigned);
unsigned suite_hashutil(unsigned);
unsigned suite_legacy_util(unsigned);
-unsigned suite_lockfile(unsigned);
const suite_fn k_legacy_suites[] = {
&suite_args,
@@ -37,7 +36,6 @@ const suite_fn k_legacy_suites[] = {
&suite_hash,
&suite_hashutil,
&suite_legacy_util,
- &suite_lockfile,
nullptr,
};
diff --git a/src/lockfile.hpp b/unittest/test_Lockfile.cpp
index 275c7a0c..cbd61b78 100644
--- a/src/lockfile.hpp
+++ b/unittest/test_Lockfile.cpp
@@ -16,7 +16,36 @@
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-#pragma once
+// This file contains tests for functions in lockfile.c.
-bool lockfile_acquire(const char* path, unsigned staleness_limit);
-void lockfile_release(const char* path);
+#include "../src/Lockfile.hpp"
+#include "../src/Stat.hpp"
+
+#include "third_party/catch.hpp"
+
+TEST_CASE("Lockfile acquire and release")
+{
+ {
+ Lockfile lock("test", 1000);
+ CHECK(lock.acquired());
+ auto st = Stat::lstat("test.lock");
+ CHECK(st);
+#ifndef _WIN32
+ CHECK(st.is_symlink());
+#else
+ CHECK(st.is_regular());
+#endif
+ }
+
+ CHECK(!Stat::lstat("test.lock"));
+}
+
+#ifndef _WIN32
+TEST_CASE("Lockfile breaking")
+{
+ CHECK(symlink("foo", "test.lock") == 0);
+
+ Lockfile lock("test", 1000);
+ CHECK(lock.acquired());
+}
+#endif // !_WIN32