mirror of https://github.com/mamba-org/mamba.git
Changed LockFile to be a non-throwing checkable type, no pointers use.
This changes how we use LockFile. See documentation/comments in the type's header `util.hpp`.
This commit is contained in:
parent
7e939f5096
commit
2b7b230613
|
@ -27,6 +27,7 @@ namespace mamba
|
||||||
env_lockfile_parsing_failed,
|
env_lockfile_parsing_failed,
|
||||||
openssl_failed,
|
openssl_failed,
|
||||||
internal_failure,
|
internal_failure,
|
||||||
|
lockfile_failure,
|
||||||
};
|
};
|
||||||
|
|
||||||
class mamba_error : public std::runtime_error
|
class mamba_error : public std::runtime_error
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "mamba/core/util_string.hpp"
|
#include "mamba/core/util_string.hpp"
|
||||||
|
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
#include "tl/expected.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
@ -47,6 +48,14 @@ namespace mamba
|
||||||
#error "no supported OS detected"
|
#error "no supported OS detected"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Used when we want a callback which does nothing.
|
||||||
|
struct no_op
|
||||||
|
{
|
||||||
|
void operator()() const noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool is_package_file(const std::string_view& fn);
|
bool is_package_file(const std::string_view& fn);
|
||||||
|
|
||||||
bool lexists(const fs::u8path& p);
|
bool lexists(const fs::u8path& p);
|
||||||
|
@ -103,9 +112,62 @@ namespace mamba
|
||||||
|
|
||||||
class LockFileOwner;
|
class LockFileOwner;
|
||||||
|
|
||||||
|
// This is a non-throwing file-locking mechanism.
|
||||||
|
// It can be used on a file or directory path. In the case of a directory path a file will be
|
||||||
|
// created to be locked. The locking will be implemented using the OS's filesystem locking
|
||||||
|
// capabilities, if available.
|
||||||
|
//
|
||||||
|
// Once constructed, use `is_locked()` or `operator bool` to check if the lock did happen
|
||||||
|
// successfully. When locking fails because of an error, the error can be retrieved using
|
||||||
|
// `error()`. When attempting to lock a path which is already locked by another process, the
|
||||||
|
// attempt will fail and `is_locked()` will return false.
|
||||||
|
//
|
||||||
|
// When the same process attempts to lock the same path more than once (multiple instances of
|
||||||
|
// `LockFile` target the same path), creating a new `LockFile` for that path will always succeed
|
||||||
|
// and increment the lock owner count which can be retrieved using `count_lock_owners()`.
|
||||||
|
// Basically, all instacnes of `LockFile` locking the same path are sharing the lock, which will
|
||||||
|
// only be released once there is no instance alive.
|
||||||
|
//
|
||||||
|
// Use `Context::instance().use_lockfiles = false` to never have locking happen, in which case
|
||||||
|
// the created `LockFile` instance will not be locked (`is_locked()` will return false) but will
|
||||||
|
// have no error either (`error()` will return `noopt`).
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// LockFile some_work_on(some_path)
|
||||||
|
// {
|
||||||
|
// LockFile lock{ some_path, timeout };
|
||||||
|
// if(lock) // make sure the locking happened
|
||||||
|
// {
|
||||||
|
// print("locked file {}, locking counts: {}", some_path,
|
||||||
|
// lock.count_lock_owners()); // success might mean we are locking the same path
|
||||||
|
// from multiple threads do_something(som_path); // locking was a success
|
||||||
|
// }
|
||||||
|
// else // locking didnt succeed for some reason
|
||||||
|
// {
|
||||||
|
// if(auto error = lock.error) print(error); // some error happened while
|
||||||
|
// attempting the lock, maybe some other process already locks the path else
|
||||||
|
// print("didn't attempt locking {}") // locking didn't happen for some other
|
||||||
|
// reason, maybe a configuration option
|
||||||
|
// }
|
||||||
|
// some_more_work(some_path); // do this that the lock failed or not
|
||||||
|
// return lock; // The locking ownership can be transfered to another function if
|
||||||
|
// necessary
|
||||||
|
// }
|
||||||
|
//
|
||||||
class LockFile
|
class LockFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// Non-throwing constructors, attempting lock on the provided path, file or directory.
|
||||||
|
// In case of a directory, a lock-file will be created, located at `this->lockfile_path()`
|
||||||
|
// and `this->is_locked()` (and `if(*this))` will always return true (unless this instance
|
||||||
|
// is moved-from). If the lock acquisition failed or `Context::instance().use_lockfiles ==
|
||||||
|
// false` and until re-assigned:
|
||||||
|
// - `this->is_locked() == false` and `if(*this) ...` will go in the `false` branch.
|
||||||
|
// - accessors will throw, except `is_locked()`, `count_lock_owners()`, and `error()`
|
||||||
|
LockFile(const fs::u8path& path);
|
||||||
|
LockFile(const fs::u8path& path, const std::chrono::seconds& timeout);
|
||||||
|
|
||||||
~LockFile();
|
~LockFile();
|
||||||
|
|
||||||
LockFile(const LockFile&) = delete;
|
LockFile(const LockFile&) = delete;
|
||||||
|
@ -114,15 +176,13 @@ namespace mamba
|
||||||
LockFile(LockFile&&);
|
LockFile(LockFile&&);
|
||||||
LockFile& operator=(LockFile&&);
|
LockFile& operator=(LockFile&&);
|
||||||
|
|
||||||
static std::unique_ptr<LockFile> create_lock(const fs::u8path& path);
|
|
||||||
static std::unique_ptr<LockFile> try_lock(const fs::u8path& path) noexcept;
|
|
||||||
|
|
||||||
// Returns true if this LockFile is currently maintaining a lock on the target path.
|
// Returns true if this LockFile is currently maintaining a lock on the target path.
|
||||||
// Returns false if this instance have been moved-from without being re-assigned,
|
// Returns false if this instance have been moved-from without being re-assigned,
|
||||||
// or if the lock acquisition failed.
|
// or if the lock acquisition failed.
|
||||||
bool is_locked() const
|
bool is_locked() const
|
||||||
{
|
{
|
||||||
return impl ? true: false;
|
return impl.has_value() // we have a owner
|
||||||
|
&& (impl.value() ? true : false); // it's not null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenient operator to check if a lockfile is actually locking a path.
|
// Convenient operator to check if a lockfile is actually locking a path.
|
||||||
|
@ -131,13 +191,13 @@ namespace mamba
|
||||||
return is_locked();
|
return is_locked();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the fd of the path being locked if `is_locked() == true`.
|
// Returns the fd of the path being locked, throws if `is_locked() == false`.
|
||||||
int fd() const;
|
int fd() const;
|
||||||
|
|
||||||
// Returns the path being locked if `is_locked() == true`.
|
// Returns the path being locked, throws if `is_locked() == false`.
|
||||||
fs::u8path path() const;
|
fs::u8path path() const;
|
||||||
|
|
||||||
// Returns the path of the lock-file being locked if `is_locked() == true`.
|
// Returns the path of the lock-file being locked, throws if `is_locked() == false`.
|
||||||
fs::u8path lockfile_path() const;
|
fs::u8path lockfile_path() const;
|
||||||
|
|
||||||
// Returns the count of LockFile instances which are currently locking
|
// Returns the count of LockFile instances which are currently locking
|
||||||
|
@ -145,7 +205,7 @@ namespace mamba
|
||||||
// Returns 0 if `is_locked() == false`.
|
// Returns 0 if `is_locked() == false`.
|
||||||
std::size_t count_lock_owners() const
|
std::size_t count_lock_owners() const
|
||||||
{
|
{
|
||||||
return impl.use_count();
|
return impl.has_value() ? impl.value().use_count() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -158,18 +218,25 @@ namespace mamba
|
||||||
|
|
||||||
static bool is_locked(const LockFile& lockfile)
|
static bool is_locked(const LockFile& lockfile)
|
||||||
{
|
{
|
||||||
|
return lockfile.is_locked() &&
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return is_locked(lockfile.lockfile_path());
|
is_locked(lockfile.lockfile_path());
|
||||||
#else
|
#else
|
||||||
// Opening a new file descriptor on Unix would clear locks
|
// Opening a new file descriptor on Unix would clear locks
|
||||||
return is_locked(lockfile.fd());
|
is_locked(lockfile.fd());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<mamba_error> error() const
|
||||||
|
{
|
||||||
|
if (impl.has_value())
|
||||||
|
return {};
|
||||||
|
else
|
||||||
|
return impl.error();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LockFile(const fs::u8path& path);
|
tl::expected<std::shared_ptr<LockFileOwner>, mamba_error> impl;
|
||||||
LockFile(const fs::u8path& path, const std::chrono::seconds& timeout);
|
|
||||||
std::shared_ptr<LockFileOwner> impl;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -305,7 +305,7 @@ namespace mamba
|
||||||
archive_write_disk_set_options(ext, flags);
|
archive_write_disk_set_options(ext, flags);
|
||||||
archive_write_disk_set_standard_lookup(ext);
|
archive_write_disk_set_standard_lookup(ext);
|
||||||
|
|
||||||
auto lock = LockFile::try_lock(file);
|
auto lock = LockFile(file);
|
||||||
r = archive_read_open_filename(a, file.string().c_str(), 10240);
|
r = archive_read_open_filename(a, file.string().c_str(), 10240);
|
||||||
|
|
||||||
if (r != ARCHIVE_OK)
|
if (r != ARCHIVE_OK)
|
||||||
|
|
|
@ -317,7 +317,7 @@ namespace mamba
|
||||||
|
|
||||||
if (is_solv)
|
if (is_solv)
|
||||||
{
|
{
|
||||||
auto lock = LockFile::try_lock(m_solv_file);
|
auto lock = LockFile(m_solv_file);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
auto fp = _wfopen(m_solv_file.wstring().c_str(), L"rb");
|
auto fp = _wfopen(m_solv_file.wstring().c_str(), L"rb");
|
||||||
#else
|
#else
|
||||||
|
@ -393,7 +393,7 @@ namespace mamba
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = LockFile::try_lock(m_json_file);
|
auto lock = LockFile(m_json_file);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
auto fp = _wfopen(m_json_file.wstring().c_str(), L"r");
|
auto fp = _wfopen(m_json_file.wstring().c_str(), L"r");
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -309,7 +309,7 @@ namespace mamba
|
||||||
if (!fs::exists(json_file, ec))
|
if (!fs::exists(json_file, ec))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto lock = LockFile::try_lock(cache_path / "cache");
|
auto lock = LockFile(cache_path / "cache");
|
||||||
auto cache_age = check_cache(json_file, now);
|
auto cache_age = check_cache(json_file, now);
|
||||||
|
|
||||||
if (cache_age != fs::file_time_type::duration::max() && !forbid_cache())
|
if (cache_age != fs::file_time_type::duration::max() && !forbid_cache())
|
||||||
|
@ -480,7 +480,7 @@ namespace mamba
|
||||||
LOG_DEBUG << "Copying repodata cache files from '" << m_expired_cache_path.string()
|
LOG_DEBUG << "Copying repodata cache files from '" << m_expired_cache_path.string()
|
||||||
<< "' to '" << m_writable_pkgs_dir.string() << "'";
|
<< "' to '" << m_writable_pkgs_dir.string() << "'";
|
||||||
fs::u8path writable_cache_dir = create_cache_dir(m_writable_pkgs_dir);
|
fs::u8path writable_cache_dir = create_cache_dir(m_writable_pkgs_dir);
|
||||||
auto lock = LockFile::create_lock(writable_cache_dir);
|
auto lock = LockFile(writable_cache_dir);
|
||||||
|
|
||||||
auto copied_json_file = writable_cache_dir / m_json_fn;
|
auto copied_json_file = writable_cache_dir / m_json_fn;
|
||||||
if (fs::exists(copied_json_file))
|
if (fs::exists(copied_json_file))
|
||||||
|
@ -502,13 +502,13 @@ namespace mamba
|
||||||
|
|
||||||
{
|
{
|
||||||
LOG_TRACE << "Refreshing '" << json_file.string() << "'";
|
LOG_TRACE << "Refreshing '" << json_file.string() << "'";
|
||||||
auto lock = LockFile::create_lock(json_file);
|
auto lock = LockFile(json_file);
|
||||||
fs::last_write_time(json_file, now);
|
fs::last_write_time(json_file, now);
|
||||||
}
|
}
|
||||||
if (fs::exists(solv_file) && solv_age.count() <= json_age.count())
|
if (fs::exists(solv_file) && solv_age.count() <= json_age.count())
|
||||||
{
|
{
|
||||||
LOG_TRACE << "Refreshing '" << solv_file.string() << "'";
|
LOG_TRACE << "Refreshing '" << solv_file.string() << "'";
|
||||||
auto lock = LockFile::create_lock(solv_file);
|
auto lock = LockFile(solv_file);
|
||||||
fs::last_write_time(solv_file, now);
|
fs::last_write_time(solv_file, now);
|
||||||
m_solv_cache_valid = true;
|
m_solv_cache_valid = true;
|
||||||
}
|
}
|
||||||
|
@ -545,7 +545,7 @@ namespace mamba
|
||||||
|
|
||||||
fs::u8path writable_cache_dir = create_cache_dir(m_writable_pkgs_dir);
|
fs::u8path writable_cache_dir = create_cache_dir(m_writable_pkgs_dir);
|
||||||
json_file = writable_cache_dir / m_json_fn;
|
json_file = writable_cache_dir / m_json_fn;
|
||||||
auto lock = LockFile::create_lock(writable_cache_dir);
|
auto lock = LockFile(writable_cache_dir);
|
||||||
|
|
||||||
m_mod_etag.clear();
|
m_mod_etag.clear();
|
||||||
m_mod_etag["_url"] = m_repodata_url;
|
m_mod_etag["_url"] = m_repodata_url;
|
||||||
|
|
|
@ -921,7 +921,7 @@ namespace mamba
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lf = LockFile::create_lock(ctx.target_prefix / "conda-meta");
|
auto lf = LockFile(ctx.target_prefix / "conda-meta");
|
||||||
clean_trash_files(ctx.target_prefix, false);
|
clean_trash_files(ctx.target_prefix, false);
|
||||||
|
|
||||||
Console::stream() << "\nTransaction starting";
|
Console::stream() << "\nTransaction starting";
|
||||||
|
|
|
@ -52,7 +52,7 @@ extern "C"
|
||||||
#include "mamba/core/fsutil.hpp"
|
#include "mamba/core/fsutil.hpp"
|
||||||
#include "mamba/core/url.hpp"
|
#include "mamba/core/url.hpp"
|
||||||
#include "mamba/core/shell_init.hpp"
|
#include "mamba/core/shell_init.hpp"
|
||||||
|
#include "mamba/core/invoke.hpp"
|
||||||
|
|
||||||
namespace mamba
|
namespace mamba
|
||||||
{
|
{
|
||||||
|
@ -812,6 +812,18 @@ namespace mamba
|
||||||
int m_fd = -1;
|
int m_fd = -1;
|
||||||
bool m_locked;
|
bool m_locked;
|
||||||
bool m_lockfile_existed;
|
bool m_lockfile_existed;
|
||||||
|
|
||||||
|
template <typename Func = no_op>
|
||||||
|
void throw_lock_error(std::string error_message, Func before_throw_task = no_op{}) const
|
||||||
|
{
|
||||||
|
auto complete_error_message = fmt::format("LockFile acquisition failed, aborting: {}",
|
||||||
|
std::move(error_message));
|
||||||
|
LOG_ERROR << error_message;
|
||||||
|
safe_invoke(before_throw_task)
|
||||||
|
.map_error([](const auto& error)
|
||||||
|
{ LOG_ERROR << "While handling LockFile failure: " << error.what(); });
|
||||||
|
throw mamba_error(complete_error_message, mamba_error_code::lockfile_failure);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
LockFileOwner::LockFileOwner(const fs::u8path& path, const std::chrono::seconds timeout)
|
LockFileOwner::LockFileOwner(const fs::u8path& path, const std::chrono::seconds timeout)
|
||||||
|
@ -822,8 +834,7 @@ namespace mamba
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
if (!fs::exists(path, ec))
|
if (!fs::exists(path, ec))
|
||||||
{
|
{
|
||||||
LOG_ERROR << "Could not lock non-existing path '" << path.string() << "'";
|
throw_lock_error(fmt::format("Could not lock non-existing path '{}'", path.string()));
|
||||||
throw std::runtime_error("LockFile error. Aborting.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs::is_directory(path))
|
if (fs::is_directory(path))
|
||||||
|
@ -845,9 +856,8 @@ namespace mamba
|
||||||
#endif
|
#endif
|
||||||
if (m_fd <= 0)
|
if (m_fd <= 0)
|
||||||
{
|
{
|
||||||
LOG_ERROR << "Could not open lockfile '" << m_lockfile_path.string() << "'";
|
throw_lock_error(fmt::format("Could not open lockfile '{}'", m_lockfile_path.string()),
|
||||||
unlock();
|
[this] { unlock(); });
|
||||||
throw std::runtime_error("LockFile error. Aborting.");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -866,11 +876,12 @@ namespace mamba
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_ERROR << "LockFile can't be set at '" << m_path.string() << "'\n"
|
throw_lock_error(
|
||||||
<< "This could be fixed by changing the locks' timeout or "
|
fmt::format("LockFile can't be set at '{}'\n"
|
||||||
<< "cleaning your environment from previous runs";
|
"This could be fixed by changing the locks' timeout or "
|
||||||
unlock();
|
"cleaning your environment from previous runs",
|
||||||
throw std::runtime_error("LockFile error. Aborting.");
|
m_path.string()),
|
||||||
|
[this] { unlock(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1062,9 +1073,15 @@ namespace mamba
|
||||||
LockedFilesRegistry& operator=(const LockedFilesRegistry&) = delete;
|
LockedFilesRegistry& operator=(const LockedFilesRegistry&) = delete;
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<LockFileOwner> acquire_lock(const fs::u8path& file_path,
|
tl::expected<std::shared_ptr<LockFileOwner>, mamba_error> acquire_lock(
|
||||||
const std::chrono::seconds timeout)
|
const fs::u8path& file_path, const std::chrono::seconds timeout)
|
||||||
{
|
{
|
||||||
|
if (!Context::instance().use_lockfiles)
|
||||||
|
{
|
||||||
|
// No locking allowed, so do nothing.
|
||||||
|
return std::shared_ptr<LockFileOwner>{};
|
||||||
|
}
|
||||||
|
|
||||||
const auto absolute_file_path = fs::absolute(file_path);
|
const auto absolute_file_path = fs::absolute(file_path);
|
||||||
std::scoped_lock lock{ mutex };
|
std::scoped_lock lock{ mutex };
|
||||||
|
|
||||||
|
@ -1079,12 +1096,17 @@ namespace mamba
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, we didn't find a lockfile alive, so we create one.
|
// At this point, we didn't find a lockfile alive, so we create one.
|
||||||
auto lockedfile = std::make_shared<LockFileOwner>(absolute_file_path, timeout);
|
return safe_invoke(
|
||||||
auto tracker = std::weak_ptr{ lockedfile };
|
[&]
|
||||||
locked_files.insert_or_assign(absolute_file_path, std::move(tracker));
|
{
|
||||||
fd_to_locked_path.insert_or_assign(lockedfile->fd(), absolute_file_path);
|
auto lockedfile
|
||||||
assert(is_lockfile_locked(*lockedfile));
|
= std::make_shared<LockFileOwner>(absolute_file_path, timeout);
|
||||||
return lockedfile;
|
auto tracker = std::weak_ptr{ lockedfile };
|
||||||
|
locked_files.insert_or_assign(absolute_file_path, std::move(tracker));
|
||||||
|
fd_to_locked_path.insert_or_assign(lockedfile->fd(), absolute_file_path);
|
||||||
|
assert(is_lockfile_locked(*lockedfile));
|
||||||
|
return lockedfile;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: the resulting value will be obsolete before returning.
|
// note: the resulting value will be obsolete before returning.
|
||||||
|
@ -1154,26 +1176,17 @@ namespace mamba
|
||||||
|
|
||||||
int LockFile::fd() const
|
int LockFile::fd() const
|
||||||
{
|
{
|
||||||
return impl->fd();
|
return impl.value()->fd();
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::u8path LockFile::path() const
|
fs::u8path LockFile::path() const
|
||||||
{
|
{
|
||||||
return impl->path();
|
return impl.value()->path();
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::u8path LockFile::lockfile_path() const
|
fs::u8path LockFile::lockfile_path() const
|
||||||
{
|
{
|
||||||
return impl->lockfile_path();
|
return impl.value()->lockfile_path();
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<LockFile> LockFile::create_lock(const fs::u8path& path)
|
|
||||||
{
|
|
||||||
if (!Context::instance().use_lockfiles)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto ptr = std::unique_ptr<LockFile>(new LockFile(path));
|
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -1182,6 +1195,16 @@ namespace mamba
|
||||||
// Windows locks are isolated between file descriptor
|
// Windows locks are isolated between file descriptor
|
||||||
// We can then test if locked by opening a new one
|
// We can then test if locked by opening a new one
|
||||||
int fd = _wopen(path.wstring().c_str(), O_RDWR | O_CREAT, 0666);
|
int fd = _wopen(path.wstring().c_str(), O_RDWR | O_CREAT, 0666);
|
||||||
|
if (fd == -1)
|
||||||
|
{
|
||||||
|
if (errno == EACCES)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// In other cases, something is wrong.
|
||||||
|
throw mamba_error{ fmt::format("failed to check if path is locked : '{}'",
|
||||||
|
path.string()),
|
||||||
|
mamba_error_code::lockfile_failure };
|
||||||
|
}
|
||||||
_lseek(fd, MAMBA_LOCK_POS, SEEK_SET);
|
_lseek(fd, MAMBA_LOCK_POS, SEEK_SET);
|
||||||
char buffer[1];
|
char buffer[1];
|
||||||
bool is_locked = _read(fd, buffer, 1) == -1;
|
bool is_locked = _read(fd, buffer, 1) == -1;
|
||||||
|
@ -1230,25 +1253,6 @@ namespace mamba
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<LockFile> LockFile::try_lock(const fs::u8path& path) noexcept
|
|
||||||
{
|
|
||||||
// Don't even lock if the file/directory isn't writable by someone or doesnt exists.
|
|
||||||
if (!Context::instance().use_lockfiles || !path::is_writable(path))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
auto ptr = std::unique_ptr<LockFile>(new LockFile(path));
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
LOG_WARNING << "LockFile creation for path '" << path.string()
|
|
||||||
<< "' failed, continuing without it";
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string timestamp(const std::time_t& utc_time)
|
std::string timestamp(const std::time_t& utc_time)
|
||||||
{
|
{
|
||||||
char buf[sizeof("2011-10-08T07:07:09Z")];
|
char buf[sizeof("2011-10-08T07:07:09Z")];
|
||||||
|
|
|
@ -52,38 +52,45 @@ namespace mamba
|
||||||
TEST_F(LockDirTest, disable_locking)
|
TEST_F(LockDirTest, disable_locking)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
auto _ = on_scope_exit([] { mamba::Context::instance().use_lockfiles = true; });
|
||||||
mamba::Context::instance().use_lockfiles = false;
|
mamba::Context::instance().use_lockfiles = false;
|
||||||
auto lock = LockFile::create_lock(tempdir_path);
|
auto lock = LockFile(tempdir_path);
|
||||||
EXPECT_TRUE(lock == nullptr);
|
EXPECT_FALSE(lock);
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(mamba::Context::instance().use_lockfiles);
|
||||||
|
{
|
||||||
|
ASSERT_TRUE(mamba::Context::instance().use_lockfiles);
|
||||||
|
auto lock = LockFile(tempdir_path);
|
||||||
|
EXPECT_TRUE(lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LockDirTest, same_pid)
|
TEST_F(LockDirTest, same_pid)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto lock = LockFile::create_lock(tempdir_path);
|
auto lock = LockFile(tempdir_path);
|
||||||
EXPECT_TRUE(lock->is_locked());
|
EXPECT_TRUE(lock.is_locked());
|
||||||
EXPECT_EQ(lock->count_lock_owners(), 1);
|
EXPECT_EQ(lock.count_lock_owners(), 1);
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
|
|
||||||
{
|
{
|
||||||
auto other_lock = LockFile::create_lock(tempdir_path);
|
auto other_lock = LockFile(tempdir_path);
|
||||||
EXPECT_TRUE(other_lock->is_locked());
|
EXPECT_TRUE(other_lock.is_locked());
|
||||||
EXPECT_EQ(other_lock->count_lock_owners(), 2);
|
EXPECT_EQ(other_lock.count_lock_owners(), 2);
|
||||||
EXPECT_EQ(lock->count_lock_owners(), 2);
|
EXPECT_EQ(lock.count_lock_owners(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_EQ(lock->count_lock_owners(), 1);
|
EXPECT_EQ(lock.count_lock_owners(), 1);
|
||||||
|
|
||||||
// check the first lock is still locked
|
// check the first lock is still locked
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
}
|
}
|
||||||
EXPECT_FALSE(fs::exists(tempdir_path / (tempdir_path.filename().string() + ".lock")));
|
EXPECT_FALSE(fs::exists(tempdir_path / (tempdir_path.filename().string() + ".lock")));
|
||||||
|
|
||||||
// we can still re-lock afterwards
|
// we can still re-lock afterwards
|
||||||
{
|
{
|
||||||
auto lock = LockFile::create_lock(tempdir_path);
|
auto lock = LockFile(tempdir_path);
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,14 +107,14 @@ namespace mamba
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{
|
{
|
||||||
auto lock = LockFile::create_lock(tempdir_path);
|
auto lock = LockFile(tempdir_path);
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
|
|
||||||
// Check lock status
|
// Check lock status
|
||||||
EXPECT_TRUE(mamba::LockFile::is_locked(*lock));
|
EXPECT_TRUE(mamba::LockFile::is_locked(lock));
|
||||||
|
|
||||||
// Check lock status from another process
|
// Check lock status from another process
|
||||||
args = { lock_cli, "is-locked", lock->lockfile_path().string() };
|
args = { lock_cli, "is-locked", lock.lockfile_path().string() };
|
||||||
out.clear();
|
out.clear();
|
||||||
err.clear();
|
err.clear();
|
||||||
reproc::run(
|
reproc::run(
|
||||||
|
@ -186,22 +193,22 @@ namespace mamba
|
||||||
TEST_F(LockFileTest, same_pid)
|
TEST_F(LockFileTest, same_pid)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto lock = LockFile::create_lock(tempfile_path);
|
auto lock = LockFile(tempfile_path);
|
||||||
EXPECT_TRUE(lock->is_locked());
|
EXPECT_TRUE(lock.is_locked());
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
EXPECT_EQ(lock->count_lock_owners(), 1);
|
EXPECT_EQ(lock.count_lock_owners(), 1);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto other_lock = LockFile::create_lock(tempfile_path);
|
auto other_lock = LockFile(tempfile_path);
|
||||||
EXPECT_TRUE(other_lock->is_locked());
|
EXPECT_TRUE(other_lock.is_locked());
|
||||||
EXPECT_EQ(other_lock->count_lock_owners(), 2);
|
EXPECT_EQ(other_lock.count_lock_owners(), 2);
|
||||||
EXPECT_EQ(lock->count_lock_owners(), 2);
|
EXPECT_EQ(lock.count_lock_owners(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_EQ(lock->count_lock_owners(), 1);
|
EXPECT_EQ(lock.count_lock_owners(), 1);
|
||||||
|
|
||||||
// check the first lock is still locked
|
// check the first lock is still locked
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
}
|
}
|
||||||
EXPECT_FALSE(fs::exists(tempfile_path.string() + ".lock"));
|
EXPECT_FALSE(fs::exists(tempfile_path.string() + ".lock"));
|
||||||
}
|
}
|
||||||
|
@ -219,14 +226,14 @@ namespace mamba
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
// Create a lock
|
// Create a lock
|
||||||
auto lock = LockFile::create_lock(tempfile_path);
|
auto lock = LockFile(tempfile_path);
|
||||||
EXPECT_TRUE(fs::exists(lock->lockfile_path()));
|
EXPECT_TRUE(fs::exists(lock.lockfile_path()));
|
||||||
|
|
||||||
// Check lock status from current PID
|
// Check lock status from current PID
|
||||||
EXPECT_TRUE(mamba::LockFile::is_locked(*lock));
|
EXPECT_TRUE(mamba::LockFile::is_locked(lock));
|
||||||
|
|
||||||
// Check lock status from another process
|
// Check lock status from another process
|
||||||
args = { lock_cli, "is-locked", lock->lockfile_path().string() };
|
args = { lock_cli, "is-locked", lock.lockfile_path().string() };
|
||||||
out.clear();
|
out.clear();
|
||||||
err.clear();
|
err.clear();
|
||||||
reproc::run(
|
reproc::run(
|
||||||
|
|
|
@ -42,8 +42,11 @@ main(int argc, char** argv)
|
||||||
mamba::Context::instance().lock_timeout = timeout;
|
mamba::Context::instance().lock_timeout = timeout;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto lock = mamba::LockFile::create_lock(path);
|
auto lock = mamba::LockFile(path);
|
||||||
std::cout << 1;
|
if (lock)
|
||||||
|
std::cout << 1;
|
||||||
|
else
|
||||||
|
std::cout << 0;
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,7 +69,7 @@ PYBIND11_MODULE(bindings, m)
|
||||||
{ return fmt::format("fs::u8path[{}]", self.string()); });
|
{ return fmt::format("fs::u8path[{}]", self.string()); });
|
||||||
py::implicitly_convertible<std::string, fs::u8path>();
|
py::implicitly_convertible<std::string, fs::u8path>();
|
||||||
|
|
||||||
py::class_<mamba::LockFile>(m, "LockFile").def(py::init(&mamba::LockFile::create_lock));
|
py::class_<mamba::LockFile>(m, "LockFile").def(py::init<fs::u8path>());
|
||||||
|
|
||||||
py::register_exception<mamba_error>(m, "MambaNativeException");
|
py::register_exception<mamba_error>(m, "MambaNativeException");
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "mamba/core/util_os.hpp"
|
#include "mamba/core/util_os.hpp"
|
||||||
#include "mamba/core/util_random.hpp"
|
#include "mamba/core/util_random.hpp"
|
||||||
#include "mamba/core/execution.hpp"
|
#include "mamba/core/execution.hpp"
|
||||||
|
#include "mamba/core/error_handling.hpp"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
@ -102,19 +103,31 @@ namespace mamba
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<LockFile> lock_proc_dir()
|
LockFile lock_proc_dir()
|
||||||
{
|
{
|
||||||
try
|
const auto proc_dir_path = proc_dir();
|
||||||
|
auto lockfile = LockFile(proc_dir_path);
|
||||||
|
if (!lockfile)
|
||||||
{
|
{
|
||||||
auto lockfile = LockFile::create_lock(proc_dir());
|
if (auto error = lockfile.error())
|
||||||
return lockfile;
|
{
|
||||||
}
|
throw mamba_error{
|
||||||
catch (...)
|
fmt::format(
|
||||||
{
|
"'mamba run' failed to lock ({}) or lockfile was not properly deleted - error: {}",
|
||||||
throw std::runtime_error(
|
proc_dir_path.string(),
|
||||||
fmt::format("'mamba run' failed to lock ({}) or lockfile was not properly deleted",
|
error->what()),
|
||||||
proc_dir().string()));
|
mamba_error_code::lockfile_failure
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_DEBUG
|
||||||
|
<< "`mamba run` file locking attempt ignored because locking is disabled - path: "
|
||||||
|
<< proc_dir_path.string();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return lockfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json get_all_running_processes_info(
|
nlohmann::json get_all_running_processes_info(
|
||||||
|
@ -161,7 +174,7 @@ namespace mamba
|
||||||
public:
|
public:
|
||||||
ScopedProcFile(const std::string& name,
|
ScopedProcFile(const std::string& name,
|
||||||
const std::vector<std::string>& command,
|
const std::vector<std::string>& command,
|
||||||
std::unique_ptr<LockFile> proc_dir_lock = lock_proc_dir())
|
LockFile proc_dir_lock = lock_proc_dir())
|
||||||
: location{ proc_dir() / fmt::format("{}.json", getpid()) }
|
: location{ proc_dir() / fmt::format("{}.json", getpid()) }
|
||||||
{
|
{
|
||||||
// Lock must be hold for the duraction of this constructor.
|
// Lock must be hold for the duraction of this constructor.
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace mamba
|
||||||
bool is_process_name_running(const std::string& name);
|
bool is_process_name_running(const std::string& name);
|
||||||
std::string generate_unique_process_name(std::string_view program_name);
|
std::string generate_unique_process_name(std::string_view program_name);
|
||||||
const fs::u8path& proc_dir();
|
const fs::u8path& proc_dir();
|
||||||
std::unique_ptr<LockFile> lock_proc_dir();
|
LockFile lock_proc_dir();
|
||||||
|
|
||||||
nlohmann::json get_all_running_processes_info(
|
nlohmann::json get_all_running_processes_info(
|
||||||
const std::function<bool(const nlohmann::json&)>& filter
|
const std::function<bool(const nlohmann::json&)>& filter
|
||||||
|
|
Loading…
Reference in New Issue