Depfile.cpp
Fd.cpp
Hash.cpp
- Lockfile.cpp
Logging.cpp
ProgressBar.cpp
Result.cpp
+++ /dev/null
-// Copyright (C) 2020-2021 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 "Logging.hpp"
-#include "Util.hpp"
-#include "Win32Util.hpp"
-#include "fmtmacros.hpp"
-
-#include <core/wincompat.hpp>
-
-#include "third_party/fmt/core.h"
-
-#include <thread>
-
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
-
-#include <algorithm>
-#include <sstream>
-#include <thread>
-
-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;
-
- std::stringstream ss;
- ss << Util::get_hostname() << ':' << getpid() << ':'
- << std::this_thread::get_id();
- const auto content_prefix = ss.str();
-
- while (true) {
- auto my_content = FMT("{}:{}", content_prefix, time(nullptr));
-
- if (symlink(my_content.c_str(), lockfile.c_str()) == 0) {
- // We got the lock.
- return true;
- }
-
- int saved_errno = errno;
- LOG("lockfile_acquire: symlink {}: {}", lockfile, 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 {
- LOG("lockfile_acquire: readlink {}: {}", lockfile, strerror(errno));
- return false;
- }
- }
-
- if (content == my_content) {
- // Lost NFS reply?
- LOG("lockfile_acquire: symlink {} failed but we got the lock anyway",
- lockfile);
- 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.
- LOG("lockfile_acquire: lock info for {}: {}", lockfile, content);
-
- if (initial_content.empty()) {
- initial_content = content;
- }
-
- if (slept <= staleness_limit) {
- LOG("lockfile_acquire: failed to acquire {}; sleeping {} microseconds",
- lockfile,
- to_sleep);
- std::this_thread::sleep_for(std::chrono::microseconds(to_sleep));
- slept += to_sleep;
- to_sleep = std::min(max_to_sleep, 2 * to_sleep);
- } else if (content != initial_content) {
- LOG("lockfile_acquire: gave up acquiring {}", lockfile);
- return false;
- } else {
- // The lock seems to be stale -- break it and try again.
- LOG("lockfile_acquire: breaking {}", lockfile);
- if (!Util::unlink_tmp(lockfile)) {
- LOG("Failed to unlink {}: {}", lockfile, 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)
-{
- const uint32_t max_to_sleep = 10000; // Microseconds.
- uint32_t to_sleep = 1000; // Microseconds.
- uint32_t 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)
- nullptr, // security attributes
- CREATE_ALWAYS, // creation disposition
- flags, // flags and attributes
- nullptr // template file
- );
- if (handle != INVALID_HANDLE_VALUE) {
- break;
- }
-
- DWORD error = GetLastError();
- if (error == ERROR_PATH_NOT_FOUND) {
- // Directory doesn't exist?
- if (Util::create_dir(Util::dir_name(lockfile))) {
- // OK. Retry.
- continue;
- }
- }
-
- LOG("lockfile_acquire: CreateFile {}: {} ({})",
- lockfile,
- Win32Util::error_message(error),
- error);
-
- // 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) {
- LOG("lockfile_acquire: gave up acquiring {}", lockfile);
- break;
- }
-
- LOG("lockfile_acquire: failed to acquire {}; sleeping {} microseconds",
- lockfile,
- to_sleep);
- std::this_thread::sleep_for(std::chrono::microseconds(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()) {
- LOG("Acquired lock {}", m_lockfile);
- } else {
- LOG("Failed to acquire lock {}", m_lockfile);
- }
-}
-
-Lockfile::~Lockfile()
-{
- if (acquired()) {
- LOG("Releasing lock {}", m_lockfile);
-#ifndef _WIN32
- if (!Util::unlink_tmp(m_lockfile)) {
- LOG("Failed to unlink {}: {}", m_lockfile, strerror(errno));
- }
-#else
- CloseHandle(m_handle);
-#endif
- }
-}
-
-bool
-Lockfile::acquired() const
-{
-#ifndef _WIN32
- return m_acquired;
-#else
- return m_handle != INVALID_HANDLE_VALUE;
-#endif
-}
+++ /dev/null
-// Copyright (C) 2020-2021 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 <cstdint>
-#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
- void* m_handle = nullptr;
-#endif
-};
#include "File.hpp"
#include "Finalizer.hpp"
#include "Hash.hpp"
-#include "Lockfile.hpp"
#include "Logging.hpp"
#include "MiniTrace.hpp"
#include "Result.hpp"
#include "StatsFile.hpp"
#include <AtomicFile.hpp>
-#include <Lockfile.hpp>
#include <Logging.hpp>
#include <Util.hpp>
#include <core/exceptions.hpp>
#include <fmtmacros.hpp>
+#include <util/LockFile.hpp>
namespace storage::primary {
StatsFile::update(
std::function<void(core::StatisticsCounters& counters)> function) const
{
- Lockfile lock(m_path);
+ util::ShortLivedLockFile lock_file(m_path);
+ util::LockFileGuard lock(lock_file);
if (!lock.acquired()) {
LOG("Failed to acquire lock for {}", m_path);
return std::nullopt;
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#include <Config.hpp>
-#include <Lockfile.hpp>
#include <Logging.hpp>
#include <fmtmacros.hpp>
+#include <util/LockFile.hpp>
#include <util/string.hpp>
+#include <memory>
#include <string>
#include <thread>
int
main(int argc, char** argv)
{
- if (argc != 3) {
- PRINT_RAW(stderr, "Usage: test-lockfile PATH SECONDS\n");
+ if (argc != 5) {
+ PRINT_RAW(stderr,
+ "Usage: test-lockfile PATH SECONDS <short|long>"
+ " <blocking|non-blocking>\n");
return 1;
}
Config config;
config.update_from_environment();
Logging::init(config);
- std::string path(argv[1]);
- auto seconds = util::parse_signed(argv[2]);
+ const std::string path(argv[1]);
+ const auto seconds = util::parse_signed(argv[2]);
+ const bool long_lived = std::string(argv[3]) == "long";
+ const bool blocking = std::string(argv[4]) == "blocking";
if (!seconds) {
PRINT_RAW(stderr, "Error: Failed to parse seconds\n");
return 1;
}
- PRINT_RAW(stdout, "Acquiring\n");
+ std::unique_ptr<util::LockFile> lock_file;
+ lock_file = long_lived ? std::unique_ptr<
+ util::LockFile>{std::make_unique<util::LongLivedLockFile>(path)}
+ : std::unique_ptr<util::LockFile>{
+ std::make_unique<util::ShortLivedLockFile>(path)};
+ const auto mode = blocking ? util::LockFileGuard::Mode::blocking
+ : util::LockFileGuard::Mode::non_blocking;
+
+ PRINT(stdout, "{}\n", blocking ? "Acquiring" : "Trying to acquire");
+ bool acquired = false;
{
- Lockfile lock(path);
- if (lock.acquired()) {
+ util::LockFileGuard lock(*lock_file, mode);
+ acquired = lock.acquired();
+ if (acquired) {
PRINT_RAW(stdout, "Acquired\n");
PRINT(
stdout, "Sleeping {} second{}\n", *seconds, *seconds == 1 ? "" : "s");
std::this_thread::sleep_for(std::chrono::seconds{*seconds});
} else {
- PRINT_RAW(stdout, "Failed to acquire\n");
+ PRINT(stdout, "{} acquire\n", blocking ? "Failed to" : "Did not");
+ }
+ if (acquired) {
+ PRINT_RAW(stdout, "Releasing\n");
}
- PRINT_RAW(stdout, "Releasing\n");
}
- PRINT_RAW(stdout, "Released\n");
+ if (acquired) {
+ PRINT_RAW(stdout, "Released\n");
+ }
}
set(
sources
+ ${CMAKE_CURRENT_SOURCE_DIR}/LockFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TextTable.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Tokenizer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/file.cpp
--- /dev/null
+// Copyright (C) 2020-2022 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 "Logging.hpp"
+#include "Util.hpp"
+#include "Win32Util.hpp"
+#include "fmtmacros.hpp"
+
+#include <core/exceptions.hpp>
+#include <core/wincompat.hpp>
+#include <util/file.hpp>
+
+#include "third_party/fmt/core.h"
+
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <algorithm>
+#include <random>
+#include <sstream>
+
+// Seconds.
+const double k_min_sleep_time = 0.010;
+const double k_max_sleep_time = 0.050;
+const double k_staleness_limit = 2;
+const double k_keep_alive_interval = k_staleness_limit / 4;
+const auto k_keep_alive_interval_ms = std::chrono::milliseconds{
+ static_cast<uint64_t>(k_keep_alive_interval * 1000)};
+
+namespace {
+
+class RandomNumberGenerator
+{
+public:
+ RandomNumberGenerator(int32_t min, int32_t max)
+ : m_random_engine(m_random_device()),
+ m_distribution(min, max)
+ {
+ }
+
+ int32_t
+ get()
+ {
+ return m_distribution(m_random_engine);
+ };
+
+private:
+ std::random_device m_random_device;
+ std::default_random_engine m_random_engine;
+ std::uniform_int_distribution<int32_t> m_distribution;
+};
+
+} // namespace
+
+namespace util {
+
+LockFile::LockFile(const std::string& path)
+ : m_lock_file(path + ".lock"),
+#ifndef _WIN32
+ m_acquired(false)
+#else
+ m_handle(INVALID_HANDLE_VALUE)
+#endif
+{
+}
+
+bool
+LockFile::acquire()
+{
+ LOG("Acquiring {}", m_lock_file);
+ return acquire(true);
+}
+
+bool
+LockFile::try_acquire()
+{
+ LOG("Trying to acquire {}", m_lock_file);
+ return acquire(false);
+}
+
+void
+LockFile::release()
+{
+ if (!acquired()) {
+ return;
+ }
+
+ LOG("Releasing {}", m_lock_file);
+#ifndef _WIN32
+ on_before_release();
+ Util::unlink_tmp(m_lock_file);
+#else
+ CloseHandle(m_handle);
+#endif
+ LOG("Released {}", m_lock_file);
+#ifndef _WIN32
+ m_acquired = false;
+#else
+ m_handle = INVALID_HANDLE_VALUE;
+#endif
+}
+
+bool
+LockFile::acquired() const
+{
+#ifndef _WIN32
+ return m_acquired;
+#else
+ return m_handle != INVALID_HANDLE_VALUE;
+#endif
+}
+
+bool
+LockFile::acquire(const bool blocking)
+{
+ ASSERT(!acquired());
+
+#ifndef _WIN32
+ m_acquired = do_acquire(blocking);
+#else
+ m_handle = do_acquire(blocking);
+#endif
+ if (acquired()) {
+ LOG("Acquired {}", m_lock_file);
+ on_after_acquire();
+ } else {
+ LOG("Failed to acquire lock {}", m_lock_file);
+ }
+
+ return acquired();
+}
+
+#ifndef _WIN32
+
+static double
+time_from_clock()
+{
+ timeval tv;
+ gettimeofday(&tv, nullptr);
+ return tv.tv_sec + static_cast<double>(tv.tv_usec) / 1'000'000;
+}
+
+static double
+time_from_stat(const Stat& stat)
+{
+ const auto mtime = stat.mtim();
+ return mtime.tv_sec + static_cast<double>(mtime.tv_nsec) / 1'000'000'000;
+}
+
+bool
+LockFile::do_acquire(const bool blocking)
+{
+ std::stringstream ss;
+ ss << Util::get_hostname() << '-' << getpid() << '-'
+ << std::this_thread::get_id();
+ const auto content_prefix = ss.str();
+
+ double last_seen_activity = [this] {
+ const auto last_lock_update = get_last_lock_update();
+ return last_lock_update ? *last_lock_update : time_from_clock();
+ }();
+
+ std::string initial_content;
+ RandomNumberGenerator sleep_ms_generator(k_min_sleep_time * 1000,
+ k_max_sleep_time * 1000);
+
+ while (true) {
+ const auto my_content = FMT("{}-{}", content_prefix, time_from_clock());
+
+ if (symlink(my_content.c_str(), m_lock_file.c_str()) == 0) {
+ // We got the lock.
+ return true;
+ }
+
+ int saved_errno = errno;
+ LOG("Could not acquire {}: {}", m_lock_file, strerror(saved_errno));
+ if (saved_errno == ENOENT) {
+ // Directory doesn't exist?
+ if (Util::create_dir(Util::dir_name(m_lock_file))) {
+ // 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(m_lock_file);
+ if (content.empty()) {
+ if (errno == ENOENT) {
+ // The symlink was removed after the symlink() call above, so retry
+ // acquiring it.
+ continue;
+ } else {
+ LOG("Could not read symlink {}: {}", m_lock_file, strerror(errno));
+ return false;
+ }
+ }
+
+ if (content == my_content) {
+ // Lost NFS reply?
+ LOG("Symlinking {} failed but we got the lock anyway", m_lock_file);
+ return true;
+ }
+
+ LOG("Lock info for {}: {}", m_lock_file, content);
+
+ if (initial_content.empty()) {
+ initial_content = content;
+ }
+
+ const auto last_lock_update = get_last_lock_update();
+ if (last_lock_update) {
+ last_seen_activity = std::max(last_seen_activity, *last_lock_update);
+ }
+
+ const double inactive_duration = time_from_clock() - last_seen_activity;
+
+ if (inactive_duration < k_staleness_limit) {
+ LOG("Lock {} held by another process active {:.3f} seconds ago",
+ m_lock_file,
+ inactive_duration);
+ if (!blocking) {
+ return false;
+ }
+ } else if (content == initial_content) {
+ // The lock seems to be stale -- break it and try again.
+ LOG("Breaking {} since it has been inactive for {:.3f} seconds",
+ m_lock_file,
+ inactive_duration);
+ if (!on_before_break() || !Util::unlink_tmp(m_lock_file)) {
+ return false;
+ }
+
+ // Note: There is an inherent race condition here where two processes may
+ // believe they both acquired the lock after breaking it:
+ //
+ // 1. A decides to break the lock.
+ // 2. B decides to break the lock.
+ // 3. A removes the file and retries.
+ // 4. A acquires the lock.
+ // 5. B removes the file and retries.
+ // 6. B acquires the lock.
+ //
+ // To reduce the risk we sleep for a while before retrying so that it's
+ // likely that step 5 happens before step 4.
+ } else {
+ LOG("Lock {} reacquired by another process", m_lock_file);
+ if (!blocking) {
+ return false;
+ }
+ initial_content = content;
+ }
+
+ const std::chrono::milliseconds to_sleep{sleep_ms_generator.get()};
+ LOG("Sleeping {} ms", to_sleep.count());
+ std::this_thread::sleep_for(to_sleep);
+ }
+}
+
+#else // !_WIN32
+
+void*
+LockFile::do_acquire(const bool blocking)
+{
+ void* handle;
+ RandomNumberGenerator sleep_ms_generator(k_min_sleep_time * 1000,
+ k_max_sleep_time * 1000);
+
+ while (true) {
+ DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE;
+ handle = CreateFile(m_lock_file.c_str(),
+ GENERIC_WRITE, // desired access
+ 0, // shared mode (0 = not shared)
+ nullptr, // security attributes
+ CREATE_ALWAYS, // creation disposition
+ flags, // flags and attributes
+ nullptr // template file
+ );
+ if (handle != INVALID_HANDLE_VALUE) {
+ break;
+ }
+
+ DWORD error = GetLastError();
+ if (error == ERROR_PATH_NOT_FOUND) {
+ // Directory doesn't exist?
+ if (Util::create_dir(Util::dir_name(m_lock_file))) {
+ // OK. Retry.
+ continue;
+ }
+ }
+
+ LOG("Could not acquire {}: {} ({})",
+ m_lock_file,
+ Win32Util::error_message(error),
+ error);
+
+ // 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;
+ }
+
+ LOG("Lock {} held by another process", m_lock_file);
+ if (!blocking) {
+ break;
+ }
+
+ const std::chrono::milliseconds to_sleep{sleep_ms_generator.get()};
+ LOG("Sleeping {} ms", to_sleep.count());
+ std::this_thread::sleep_for(to_sleep);
+ }
+
+ return handle;
+}
+
+#endif // !_WIN32
+
+ShortLivedLockFile::ShortLivedLockFile(const std::string& path) : LockFile(path)
+{
+}
+
+LongLivedLockFile::LongLivedLockFile(const std::string& path)
+ : LockFile(path)
+#ifndef _WIN32
+ ,
+ m_alive_file(path + ".alive")
+#endif
+{
+}
+
+#ifndef _WIN32
+
+void
+LongLivedLockFile::on_after_acquire()
+{
+ try {
+ Util::write_file(m_alive_file, "");
+ } catch (const core::Error& e) {
+ LOG("Failed to create {}: {}", m_alive_file, e.what());
+ }
+ LOG_RAW("Starting keep-alive thread");
+ m_keep_alive_thread = std::thread([=] {
+ while (true) {
+ std::unique_lock<std::mutex> lock(m_stop_keep_alive_mutex);
+ m_stop_keep_alive_condition.wait_for(
+ lock, k_keep_alive_interval_ms, [this] { return m_stop_keep_alive; });
+ if (m_stop_keep_alive) {
+ return;
+ }
+ util::set_timestamps(m_alive_file);
+ }
+ });
+ LOG_RAW("Started keep-alive thread");
+}
+
+void
+LongLivedLockFile::on_before_release()
+{
+ if (m_keep_alive_thread.joinable()) {
+ {
+ std::unique_lock<std::mutex> lock(m_stop_keep_alive_mutex);
+ m_stop_keep_alive = true;
+ }
+ m_stop_keep_alive_condition.notify_one();
+ m_keep_alive_thread.join();
+
+ Util::unlink_tmp(m_alive_file);
+ }
+}
+
+bool
+LongLivedLockFile::on_before_break()
+{
+ return Util::unlink_tmp(m_alive_file);
+}
+
+std::optional<double>
+LongLivedLockFile::get_last_lock_update()
+{
+ if (const auto stat = Stat::stat(m_alive_file); stat) {
+ return time_from_stat(stat);
+ } else {
+ return std::nullopt;
+ }
+}
+
+#endif
+
+LockFileGuard::LockFileGuard(LockFile& lock_file, Mode mode)
+ : m_lock_file(lock_file)
+{
+ if (mode == Mode::blocking) {
+ lock_file.acquire();
+ } else {
+ lock_file.try_acquire();
+ }
+}
+
+LockFileGuard::~LockFileGuard() noexcept
+{
+ m_lock_file.release();
+}
+
+bool
+LockFileGuard::acquired() const
+{
+ return m_lock_file.acquired();
+}
+
+} // namespace util
--- /dev/null
+// Copyright (C) 2020-2022 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 <NonCopyable.hpp>
+
+#include <condition_variable>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <thread>
+
+namespace util {
+
+class LockFile : NonCopyable
+{
+public:
+ virtual ~LockFile() noexcept = default;
+
+ // Acquire lock, blocking. Returns true if acquired, otherwise false.
+ bool acquire();
+
+ // Acquire lock, non-blocking. Returns true if acquired, otherwise false.
+ bool try_acquire();
+
+ // Release lock. If not previously acquired, nothing happens.
+ void release();
+
+ // Return whether the lock was acquired successfully.
+ bool acquired() const;
+
+protected:
+ LockFile(const std::string& path);
+
+private:
+ std::string m_lock_file;
+#ifndef _WIN32
+ bool m_acquired;
+#else
+ void* m_handle;
+#endif
+
+ bool acquire(bool blocking);
+ virtual void on_after_acquire();
+ virtual void on_before_release();
+#ifndef _WIN32
+ bool do_acquire(bool blocking);
+ virtual bool on_before_break();
+ virtual std::optional<double> get_last_lock_update();
+#else
+ void* do_acquire(bool blocking);
+#endif
+};
+
+// A short-lived lock.
+//
+// The lock is expected to be released shortly after being acquired - if it is
+// held for more than two seconds it risks being considered stale by another
+// client.
+class ShortLivedLockFile : public LockFile
+{
+public:
+ ShortLivedLockFile(const std::string& path);
+};
+
+// A long-lived lock.
+//
+// The lock will (depending on implementation) be kept alive by a helper thread.
+class LongLivedLockFile : public LockFile
+{
+public:
+ LongLivedLockFile(const std::string& path);
+
+private:
+#ifndef _WIN32
+ std::string m_alive_file;
+ std::thread m_keep_alive_thread;
+ std::mutex m_stop_keep_alive_mutex;
+ bool m_stop_keep_alive = false;
+ std::condition_variable m_stop_keep_alive_condition;
+
+ void on_after_acquire() override;
+ void on_before_release() override;
+ bool on_before_break() override;
+ std::optional<double> get_last_lock_update() override;
+#endif
+};
+
+class LockFileGuard : NonCopyable
+{
+public:
+ enum class Mode { blocking, non_blocking };
+
+ LockFileGuard(LockFile& lock_file, Mode mode = Mode::blocking);
+ ~LockFileGuard() noexcept;
+
+ bool acquired() const;
+
+private:
+ LockFile& m_lock_file;
+};
+
+inline void
+LockFile::on_after_acquire()
+{
+}
+
+inline void
+LockFile::on_before_release()
+{
+}
+
+#ifndef _WIN32
+
+inline bool
+LockFile::on_before_break()
+{
+ return true;
+}
+
+inline std::optional<double>
+LockFile::get_last_lock_update()
+{
+ return std::nullopt;
+}
+
+#endif
+
+} // namespace util
if (mtime) {
atime_mtime.actime = atime ? atime->tv_sec : mtime->tv_sec;
atime_mtime.modtime = mtime->tv_sec;
+ utime(path.c_str(), &atime_mtime);
+ } else {
+ utime(path.c_str(), nullptr);
}
- utime(path.c_str(), &atime_mtime);
#endif
}
test_Config.cpp
test_Depfile.cpp
test_Hash.cpp
- test_Lockfile.cpp
test_NullCompression.cpp
test_Stat.cpp
test_Util.cpp
test_hashutil.cpp
test_storage_primary_StatsFile.cpp
test_storage_primary_util.cpp
+ test_util_LockFile.cpp
test_util_TextTable.cpp
test_util_Tokenizer.cpp
test_util_XXH3_128.cpp
+++ /dev/null
-// Copyright (C) 2020-2021 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 "../src/Lockfile.hpp"
-#include "../src/Stat.hpp"
-#include "TestUtil.hpp"
-
-#include <core/wincompat.hpp>
-
-#include "third_party/doctest.h"
-
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
-
-TEST_SUITE_BEGIN("LockFile");
-
-using TestUtil::TestContext;
-
-TEST_CASE("Lockfile acquire and release")
-{
- TestContext test_context;
-
- {
- 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"));
-}
-
-TEST_CASE("Lockfile creates missing directories")
-{
- TestContext test_context;
-
- Lockfile lock("a/b/c/test", 1000);
- CHECK(lock.acquired());
- CHECK(Stat::lstat("a/b/c/test.lock"));
-}
-
-#ifndef _WIN32
-TEST_CASE("Lockfile breaking")
-{
- TestContext test_context;
-
- CHECK(symlink("foo", "test.lock") == 0);
-
- Lockfile lock("test", 1000);
- CHECK(lock.acquired());
-}
-#endif // !_WIN32
-
-TEST_SUITE_END();
--- /dev/null
+// Copyright (C) 2020-2022 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 "../src/Stat.hpp"
+#include "TestUtil.hpp"
+
+#include <Util.hpp>
+#include <core/wincompat.hpp>
+#include <util/LockFile.hpp>
+#include <util/file.hpp>
+
+#include "third_party/doctest.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+using namespace std::chrono_literals;
+
+TEST_SUITE_BEGIN("LockFile");
+
+using TestUtil::TestContext;
+
+TEST_CASE("Acquire and release short-lived lock file")
+{
+ TestContext test_context;
+
+ util::ShortLivedLockFile lock_file("test");
+ {
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock_file.acquired());
+ CHECK(lock.acquired());
+ CHECK(!Stat::lstat("test.alive"));
+ const auto st = Stat::lstat("test.lock");
+ CHECK(st);
+#ifndef _WIN32
+ CHECK(st.is_symlink());
+#else
+ CHECK(st.is_regular());
+#endif
+ }
+
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+}
+
+TEST_CASE("Non-blocking short-lived lock")
+{
+ TestContext test_context;
+
+ util::ShortLivedLockFile lock_file_1("test");
+ CHECK(!lock_file_1.acquired());
+
+ util::ShortLivedLockFile lock_file_2("test");
+ CHECK(!lock_file_2.acquired());
+
+ CHECK(lock_file_1.try_acquire());
+ CHECK(lock_file_1.acquired());
+
+ CHECK(!lock_file_2.try_acquire());
+ CHECK(lock_file_1.acquired());
+ CHECK(!lock_file_2.acquired());
+
+ lock_file_2.release();
+ CHECK(lock_file_1.acquired());
+ CHECK(!lock_file_2.acquired());
+
+ lock_file_1.release();
+ CHECK(!lock_file_1.acquired());
+ CHECK(!lock_file_2.acquired());
+}
+
+TEST_CASE("Acquire and release long-lived lock file")
+{
+ TestContext test_context;
+
+ util::LongLivedLockFile lock_file("test");
+ {
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock_file.acquired());
+ CHECK(lock.acquired());
+#ifndef _WIN32
+ CHECK(Stat::lstat("test.alive"));
+#endif
+ const auto st = Stat::lstat("test.lock");
+ CHECK(st);
+#ifndef _WIN32
+ CHECK(st.is_symlink());
+#else
+ CHECK(st.is_regular());
+#endif
+ }
+
+ CHECK(!lock_file.acquired());
+ CHECK(!Stat::lstat("test.lock"));
+ CHECK(!Stat::lstat("test.alive"));
+}
+
+TEST_CASE("LockFile creates missing directories")
+{
+ TestContext test_context;
+
+ util::ShortLivedLockFile lock_file("a/b/c/test");
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock.acquired());
+ CHECK(Stat::lstat("a/b/c/test.lock"));
+}
+
+#ifndef _WIN32
+TEST_CASE("Break stale lock, blocking")
+{
+ TestContext test_context;
+
+ Util::write_file("test.alive", "");
+ const timespec long_time_ago{0, 0};
+ util::set_timestamps("test.alive", long_time_ago);
+ CHECK(symlink("foo", "test.lock") == 0);
+
+ util::LongLivedLockFile lock_file("test");
+ util::LockFileGuard lock(lock_file);
+ CHECK(lock.acquired());
+}
+
+TEST_CASE("Break stale lock, non-blocking")
+{
+ TestContext test_context;
+
+ Util::write_file("test.alive", "");
+ const timespec long_time_ago{0, 0};
+ util::set_timestamps("test.alive", long_time_ago);
+ CHECK(symlink("foo", "test.lock") == 0);
+
+ util::LongLivedLockFile lock_file("test");
+ util::LockFileGuard lock(lock_file, util::LockFileGuard::Mode::non_blocking);
+ CHECK(lock.acquired());
+}
+#endif // !_WIN32
+
+TEST_SUITE_END();