diff options
author | Joel Rosdahl <joel@rosdahl.net> | 2020-03-06 19:39:32 +0100 |
---|---|---|
committer | Joel Rosdahl <joel@rosdahl.net> | 2020-03-10 21:05:01 +0100 |
commit | c181fdd983376014e6c27b6e0b64af9a7e0eb7e3 (patch) | |
tree | 9db44b2eb086d6b613ef88bba39efccd45c9fd53 | |
parent | c468acbf200a5352ac34f798107241d49500d533 (diff) | |
download | ccache-c181fdd983376014e6c27b6e0b64af9a7e0eb7e3.tar.gz ccache-c181fdd983376014e6c27b6e0b64af9a7e0eb7e3.tar.bz2 ccache-c181fdd983376014e6c27b6e0b64af9a7e0eb7e3.zip |
C++-ify lockfile routines
-rw-r--r-- | Makefile.in | 4 | ||||
-rw-r--r-- | src/Lockfile.cpp | 216 | ||||
-rw-r--r-- | src/Lockfile.hpp | 55 | ||||
-rw-r--r-- | src/stats.cpp | 38 | ||||
-rw-r--r-- | unittest/main.cpp | 2 | ||||
-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 |