maint: use `synchronized_value` where we use a mutex to protect data (#3992)

This commit is contained in:
Klaim (Joël Lamotte) 2025-07-04 15:26:49 +02:00 committed by GitHub
parent a91898370c
commit 14a94d379a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 252 additions and 137 deletions

View File

@ -14,6 +14,7 @@
#include <vector> #include <vector>
#include "mamba/core/error_handling.hpp" #include "mamba/core/error_handling.hpp"
#include "mamba/util/synchronized_value.hpp"
namespace mamba namespace mamba
{ {
@ -71,10 +72,10 @@ namespace mamba
return; return;
} }
std::scoped_lock lock{ threads_mutex }; auto synched_threads = threads.synchronize();
if (is_open) // Double check necessary for correctness if (is_open) // Double check necessary for correctness
{ {
threads.emplace_back(std::forward<Task>(task), std::forward<Args>(args)...); synched_threads->emplace_back(std::forward<Task>(task), std::forward<Args>(args)...);
} }
} }
@ -92,10 +93,10 @@ namespace mamba
return; return;
} }
std::scoped_lock lock{ threads_mutex }; auto synched_threads = threads.synchronize();
if (is_open) // Double check necessary for correctness if (is_open) // Double check necessary for correctness
{ {
threads.push_back(std::move(thread)); synched_threads->push_back(std::move(thread));
} }
} }
@ -116,12 +117,12 @@ namespace mamba
invoke_close_handlers(); invoke_close_handlers();
std::scoped_lock lock{ threads_mutex }; auto synched_threads = threads.synchronize();
for (auto&& t : threads) for (auto&& t : *synched_threads)
{ {
t.join(); t.join();
} }
threads.clear(); synched_threads->clear();
} }
using on_close_handler = std::function<void()>; using on_close_handler = std::function<void()>;
@ -133,21 +134,20 @@ namespace mamba
return; return;
} }
std::scoped_lock lock{ handlers_mutex }; auto handlers = close_handlers.synchronize();
if (is_open) // Double check needed to avoid adding new handles while closing. if (is_open) // Double check needed to avoid adding new handles while closing.
{ {
close_handlers.push_back(std::move(handler)); handlers->push_back(std::move(handler));
} }
} }
private: private:
std::atomic<bool> is_open{ true }; std::atomic<bool> is_open{ true };
std::vector<std::thread> threads; using Threads = std::vector<std::thread>;
std::recursive_mutex threads_mutex; // TODO: replace by synchronized_value once available using CloseHandlers = std::vector<on_close_handler>;
util::synchronized_value<Threads, std::recursive_mutex> threads;
std::vector<on_close_handler> close_handlers; util::synchronized_value<CloseHandlers, std::recursive_mutex> close_handlers;
std::recursive_mutex handlers_mutex; // TODO: replace by synchronized_value once available
void invoke_close_handlers(); void invoke_close_handlers();
}; };

View File

@ -186,7 +186,7 @@ namespace mamba
inline void counting_semaphore::lock() inline void counting_semaphore::lock()
{ {
std::unique_lock<std::mutex> lock(m_access_mutex); std::unique_lock lock{ m_access_mutex };
m_cv.wait(lock, [&]() { return m_value > 0; }); m_cv.wait(lock, [&]() { return m_value > 0; });
--m_value; --m_value;
} }
@ -194,7 +194,7 @@ namespace mamba
inline void counting_semaphore::unlock() inline void counting_semaphore::unlock()
{ {
{ {
std::lock_guard<std::mutex> lock(m_access_mutex); std::unique_lock lock{ m_access_mutex };
if (++m_value <= 0) if (++m_value <= 0)
{ {
return; return;

View File

@ -9,7 +9,6 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -17,6 +16,7 @@
#include <fmt/core.h> #include <fmt/core.h>
#include "mamba/download/request.hpp" #include "mamba/download/request.hpp"
#include "mamba/util/synchronized_value.hpp"
namespace mamba::download namespace mamba::download
{ {
@ -66,6 +66,16 @@ namespace mamba::download
MirrorRequest& operator=(MirrorRequest&&) = default; MirrorRequest& operator=(MirrorRequest&&) = default;
}; };
struct MirrorStats // Moved out of Mirror internals because of compilers not agreeing:
// https://godbolt.org/z/GcjWhrb9W
{
std::optional<std::size_t> allowed_connections = std::nullopt;
std::size_t max_tried_connections = 0;
std::size_t running_transfers = 0;
std::size_t successful_transfers = 0;
std::size_t failed_transfers = 0;
};
// A Mirror represents a location from where an asset can be downloaded. // A Mirror represents a location from where an asset can be downloaded.
// It handles the generation of required requests to get the asset, and // It handles the generation of required requests to get the asset, and
// provides some statistics about its usage. // provides some statistics about its usage.
@ -109,16 +119,13 @@ namespace mamba::download
MirrorID m_id; MirrorID m_id;
size_t m_max_retries; size_t m_max_retries;
// TODO: use synchronized value static_assert(std::default_initializable<MirrorStats>);
std::mutex m_stats_mutex;
std::optional<std::size_t> m_allowed_connections = std::nullopt; util::synchronized_value<MirrorStats> m_stats;
std::size_t m_max_tried_connections = 0;
std::size_t m_running_transfers = 0;
std::size_t m_successful_transfers = 0;
std::size_t m_failed_transfers = 0;
}; };
std::unique_ptr<Mirror> make_mirror(std::string url); std::unique_ptr<Mirror> make_mirror(std::string url);
} }
#endif #endif

View File

@ -8,9 +8,11 @@
#define MAMBA_UTIL_SYNCHRONIZED_VALUE_HPP #define MAMBA_UTIL_SYNCHRONIZED_VALUE_HPP
#include <concepts> #include <concepts>
#include <functional>
#include <mutex> #include <mutex>
#include <shared_mutex> #include <shared_mutex>
#include <tuple> #include <tuple>
#include <utility>
namespace mamba::util namespace mamba::util
{ {
@ -35,6 +37,26 @@ namespace mamba::util
template <class T, template <class...> class U> template <class T, template <class...> class U>
constexpr bool is_type_instance_of_v = is_type_instance_of<T, U>::value; constexpr bool is_type_instance_of_v = is_type_instance_of<T, U>::value;
/// `true` if the instances of two provided types can be compared with operator==.
/// Notice that this concept is less restrictive than `std::equality_comparable_with`,
/// which requires the existence of a common reference type for T and U. This additional
/// restriction makes it impossible to use it in the context here (originally of sparrow), where
/// we want to compare objects that are logically similar while being "physically" different.
// Source:
// https://github.com/man-group/sparrow/blob/66f70418cf1b00cc294c99bbbe04b5b4d2f83c98/include/sparrow/utils/mp_utils.hpp#L604-L619
template <class T, class U>
concept weakly_equality_comparable_with = requires(
const std::remove_reference_t<T>& t,
const std::remove_reference_t<U>& u
) {
{ t == u } -> std::convertible_to<bool>;
{ t != u } -> std::convertible_to<bool>;
{ u == t } -> std::convertible_to<bool>;
{ u != t } -> std::convertible_to<bool>;
};
///////////////////////////// /////////////////////////////
@ -256,7 +278,9 @@ namespace mamba::util
/// Constructs with a provided value as initializer for the stored object. /// Constructs with a provided value as initializer for the stored object.
template <typename V> template <typename V>
requires std::assignable_from<T&, V> and (not std::same_as<this_type, std::decay_t<V>>) requires(not std::same_as<T, std::decay_t<V>>)
and (not std::same_as<this_type, std::decay_t<V>>)
and std::assignable_from<T&, V>
synchronized_value(V&& value) noexcept synchronized_value(V&& value) noexcept
: m_value(std::forward<V>(value)) : m_value(std::forward<V>(value))
{ {
@ -266,6 +290,11 @@ namespace mamba::util
// the definition here. // the definition here.
} }
/// Constructs with a provided value as initializer for the stored object.
// NOTE: this is redundant with the generic impl, but required to workaround
// apple-clang failing to properly constrain the generic impl.
synchronized_value(T value) noexcept;
/// Constructs with a provided initializer list used to initialize the stored object. /// Constructs with a provided initializer list used to initialize the stored object.
template <typename V> template <typename V>
requires std::constructible_from<T, std::initializer_list<V>> requires std::constructible_from<T, std::initializer_list<V>>
@ -284,14 +313,17 @@ namespace mamba::util
the call. If `SharedMutex<M> == true`, the lock is a shared-lock for the provided the call. If `SharedMutex<M> == true`, the lock is a shared-lock for the provided
`synchronized_value`'s mutex. `synchronized_value`'s mutex.
*/ */
template <std::equality_comparable_with<T> U, Mutex OtherMutex> template <std::default_initializable U, Mutex OtherMutex>
requires std::assignable_from<T&, U>
auto operator=(const synchronized_value<U, OtherMutex>& other) -> synchronized_value&; auto operator=(const synchronized_value<U, OtherMutex>& other) -> synchronized_value&;
/** Locks and assign the provided value to the stored object. /** Locks and assign the provided value to the stored object.
The lock is released before the end of the call. The lock is released before the end of the call.
*/ */
template <typename V> template <typename V>
requires std::assignable_from<T&, V> and (not std::same_as<this_type, std::decay_t<V>>) requires(not std::same_as<T, std::decay_t<V>>)
and (not std::same_as<this_type, std::decay_t<V>>)
and std::assignable_from<T&, V>
auto operator=(V&& value) noexcept -> synchronized_value& auto operator=(V&& value) noexcept -> synchronized_value&
{ {
// NOTE: when moving the definition outside the class, // NOTE: when moving the definition outside the class,
@ -303,6 +335,13 @@ namespace mamba::util
return *this; return *this;
} }
/** Locks and assign the provided value to the stored object.
The lock is released before the end of the call.
*/
// NOTE: this is redundant with the generic impl, but required to workaround
// apple-clang failing to properly constrain the generic impl.
auto operator=(const T& value) noexcept -> synchronized_value&;
/** Locks and return the value of the current object. /** Locks and return the value of the current object.
The lock is released before the end of the call. The lock is released before the end of the call.
If `SharedMutex<M> == true`, the lock is a shared-lock. If `SharedMutex<M> == true`, the lock is a shared-lock.
@ -465,12 +504,12 @@ namespace mamba::util
/** Locks (shared if possible) and compare equality of the stored object's value with the /** Locks (shared if possible) and compare equality of the stored object's value with the
provided value. provided value.
*/ */
auto operator==(const std::equality_comparable_with<T> auto& other_value) const -> bool; auto operator==(const weakly_equality_comparable_with<T> auto& other_value) const -> bool;
/** Locks both (shared if possible) and compare equality of the stored object's value with /** Locks both (shared if possible) and compare equality of the stored object's value with
the provided value. the provided value.
*/ */
template <std::equality_comparable_with<T> U, Mutex OtherMutex> template <weakly_equality_comparable_with<T> U, Mutex OtherMutex>
auto operator==(const synchronized_value<U, OtherMutex>& other_value) const -> bool; auto operator==(const synchronized_value<U, OtherMutex>& other_value) const -> bool;
auto swap(synchronized_value& other) -> void; auto swap(synchronized_value& other) -> void;
@ -508,6 +547,12 @@ namespace mamba::util
template <std::default_initializable T, Mutex M> template <std::default_initializable T, Mutex M>
synchronized_value<T, M>::synchronized_value() noexcept(std::is_nothrow_default_constructible_v<T>) = default; synchronized_value<T, M>::synchronized_value() noexcept(std::is_nothrow_default_constructible_v<T>) = default;
template <std::default_initializable T, Mutex M>
synchronized_value<T, M>::synchronized_value(T value) noexcept
: m_value(std::move(value))
{
}
template <std::default_initializable T, Mutex M> template <std::default_initializable T, Mutex M>
synchronized_value<T, M>::synchronized_value(const synchronized_value& other) synchronized_value<T, M>::synchronized_value(const synchronized_value& other)
{ {
@ -516,7 +561,8 @@ namespace mamba::util
} }
template <std::default_initializable T, Mutex M> template <std::default_initializable T, Mutex M>
template <std::equality_comparable_with<T> U, Mutex OtherMutex> template <std::default_initializable U, Mutex OtherMutex>
requires std::assignable_from<T&, U>
auto synchronized_value<T, M>::operator=(const synchronized_value<U, OtherMutex>& other) auto synchronized_value<T, M>::operator=(const synchronized_value<U, OtherMutex>& other)
-> synchronized_value<T, M>& -> synchronized_value<T, M>&
{ {
@ -526,6 +572,14 @@ namespace mamba::util
return *this; return *this;
} }
template <std::default_initializable T, Mutex M>
auto synchronized_value<T, M>::operator=(const T& value) noexcept -> synchronized_value&
{
auto _ = lock_as_exclusive(m_mutex);
m_value = value;
return *this;
}
template <std::default_initializable T, Mutex M> template <std::default_initializable T, Mutex M>
template <typename V> template <typename V>
requires std::constructible_from<T, std::initializer_list<V>> requires std::constructible_from<T, std::initializer_list<V>>
@ -584,7 +638,8 @@ namespace mamba::util
} }
template <std::default_initializable T, Mutex M> template <std::default_initializable T, Mutex M>
auto synchronized_value<T, M>::operator==(const std::equality_comparable_with<T> auto& other_value auto
synchronized_value<T, M>::operator==(const weakly_equality_comparable_with<T> auto& other_value
) const -> bool ) const -> bool
{ {
auto _ = lock_as_readonly(m_mutex); auto _ = lock_as_readonly(m_mutex);
@ -592,7 +647,7 @@ namespace mamba::util
} }
template <std::default_initializable T, Mutex M> template <std::default_initializable T, Mutex M>
template <std::equality_comparable_with<T> U, Mutex OtherMutex> template <weakly_equality_comparable_with<T> U, Mutex OtherMutex>
auto auto
synchronized_value<T, M>::operator==(const synchronized_value<U, OtherMutex>& other_value) const synchronized_value<T, M>::operator==(const synchronized_value<U, OtherMutex>& other_value) const
-> bool -> bool
@ -623,7 +678,6 @@ namespace mamba::util
{ {
return std::make_tuple(std::forward<SynchronizedValues>(sync_values).synchronize()...); return std::make_tuple(std::forward<SynchronizedValues>(sync_values).synchronize()...);
} }
} }
#endif #endif

View File

@ -8,8 +8,8 @@ namespace mamba
void MainExecutor::invoke_close_handlers() void MainExecutor::invoke_close_handlers()
{ {
std::scoped_lock lock{ handlers_mutex }; auto synched_handlers = close_handlers.synchronize();
for (auto&& handler : close_handlers) for (auto&& handler : *synched_handlers)
{ {
const auto result = safe_invoke(handler); const auto result = safe_invoke(handler);
if (!result) if (!result)

View File

@ -27,6 +27,7 @@
#include "mamba/core/util.hpp" #include "mamba/core/util.hpp"
#include "mamba/specs/conda_url.hpp" #include "mamba/specs/conda_url.hpp"
#include "mamba/util/string.hpp" #include "mamba/util/string.hpp"
#include "mamba/util/synchronized_value.hpp"
#include "mamba/util/url_manip.hpp" #include "mamba/util/url_manip.hpp"
#include "progress_bar_impl.hpp" #include "progress_bar_impl.hpp"
@ -272,6 +273,9 @@ namespace mamba
* Console * * Console *
***********/ ***********/
using ConsoleBuffer = std::vector<std::string>;
class ConsoleData class ConsoleData
{ {
public: public:
@ -281,23 +285,33 @@ namespace mamba
{ {
} }
ConsoleData(const ConsoleData&) = delete;
ConsoleData& operator=(const ConsoleData&) = delete;
ConsoleData(ConsoleData&&) noexcept = delete;
ConsoleData& operator=(ConsoleData&&) noexcept = delete;
const Context& m_context; const Context& m_context;
std::mutex m_mutex;
std::unique_ptr<ProgressBarManager> p_progress_bar_manager;
std::string json_hier; std::string json_hier;
unsigned int json_index; unsigned int json_index = 0;
nlohmann::json json_log; nlohmann::json json_log;
bool is_json_print_cancelled = false; bool is_json_print_cancelled = false;
std::vector<std::string> m_buffer; struct Data
{
std::unique_ptr<ProgressBarManager> progress_bar_manager;
ConsoleBuffer buffer;
};
util::synchronized_value<Data> m_synched_data;
TaskSynchronizer m_tasksync; TaskSynchronizer m_tasksync;
}; };
Console::Console(const Context& context) Console::Console(const Context& context)
: p_data(new ConsoleData{ context }) : p_data(std::make_unique<ConsoleData>(context))
{ {
set_singleton(*this); set_singleton(*this);
@ -346,11 +360,11 @@ namespace mamba
{ {
if (force_print || !(context().output_params.quiet || context().output_params.json)) if (force_print || !(context().output_params.quiet || context().output_params.json))
{ {
const std::lock_guard<std::mutex> lock(p_data->m_mutex); auto synched_data = p_data->m_synched_data.synchronize();
if (p_data->p_progress_bar_manager && p_data->p_progress_bar_manager->started()) if (synched_data->progress_bar_manager && synched_data->progress_bar_manager->started())
{ {
p_data->m_buffer.push_back(hide_secrets(str)); synched_data->buffer.push_back(hide_secrets(str));
} }
else else
{ {
@ -362,12 +376,8 @@ namespace mamba
void Console::print_buffer(std::ostream& ostream) void Console::print_buffer(std::ostream& ostream)
{ {
auto& data = instance().p_data; auto& data = instance().p_data;
decltype(data->m_buffer) tmp; ConsoleBuffer tmp;
data->m_synched_data->buffer.swap(tmp);
{
const std::lock_guard<std::mutex> lock(data->m_mutex);
data->m_buffer.swap(tmp);
}
for (const auto& message : tmp) for (const auto& message : tmp)
{ {
@ -436,11 +446,11 @@ namespace mamba
} }
else else
{ {
return p_data->p_progress_bar_manager->add_progress_bar( return p_data->m_synched_data->progress_bar_manager->add_progress_bar(
name, name,
{ {
/* .graphics = */ context().graphics_params, .graphics = context().graphics_params,
/* .ascii_only = */ context().ascii_only, .ascii_only = context().ascii_only,
}, },
expected_total expected_total
); );
@ -449,31 +459,35 @@ namespace mamba
void Console::clear_progress_bars() void Console::clear_progress_bars()
{ {
return p_data->p_progress_bar_manager->clear_progress_bars(); return p_data->m_synched_data->progress_bar_manager->clear_progress_bars();
} }
ProgressBarManager& Console::init_progress_bar_manager(ProgressBarMode mode) ProgressBarManager& Console::init_progress_bar_manager(ProgressBarMode mode)
{ {
p_data->p_progress_bar_manager = make_progress_bar_manager(mode); auto new_progress_bar_manager = make_progress_bar_manager(mode);
p_data->p_progress_bar_manager->register_print_hook(Console::print_buffer); new_progress_bar_manager->register_print_hook(Console::print_buffer);
p_data->p_progress_bar_manager->register_print_hook(MessageLogger::print_buffer); new_progress_bar_manager->register_print_hook(MessageLogger::print_buffer);
p_data->p_progress_bar_manager->register_pre_start_hook(MessageLogger::activate_buffer); new_progress_bar_manager->register_pre_start_hook(MessageLogger::activate_buffer);
p_data->p_progress_bar_manager->register_post_stop_hook(MessageLogger::deactivate_buffer); new_progress_bar_manager->register_post_stop_hook(MessageLogger::deactivate_buffer);
return *(p_data->p_progress_bar_manager); auto synched_data = p_data->m_synched_data.synchronize();
synched_data->progress_bar_manager = std::move(new_progress_bar_manager);
return *(synched_data->progress_bar_manager); // unsafe!
} }
void Console::terminate_progress_bar_manager() void Console::terminate_progress_bar_manager()
{ {
if (p_data->p_progress_bar_manager) auto synched_data = p_data->m_synched_data.synchronize();
if (synched_data->progress_bar_manager)
{ {
p_data->p_progress_bar_manager->terminate(); synched_data->progress_bar_manager->terminate();
} }
} }
ProgressBarManager& Console::progress_bar_manager() ProgressBarManager& Console::progress_bar_manager()
{ {
return *(p_data->p_progress_bar_manager); return *(p_data->m_synched_data->progress_bar_manager);
} }
void Console::json_print() void Console::json_print()
@ -543,12 +557,10 @@ namespace mamba
* MessageLogger * * MessageLogger *
*****************/ *****************/
struct MessageLoggerData static std::atomic<bool> message_logger_use_buffer;
{
static std::mutex m_mutex; using MessageLoggerBuffer = std::vector<std::pair<std::string, log_level>>;
static bool use_buffer; static util::synchronized_value<MessageLoggerBuffer> message_logger_buffer;
static std::vector<std::pair<std::string, log_level>> m_buffer;
};
MessageLogger::MessageLogger(log_level level) MessageLogger::MessageLogger(log_level level)
: m_level(level) : m_level(level)
@ -558,14 +570,13 @@ namespace mamba
MessageLogger::~MessageLogger() MessageLogger::~MessageLogger()
{ {
if (!MessageLoggerData::use_buffer && Console::is_available()) if (!message_logger_use_buffer && Console::is_available())
{ {
emit(m_stream.str(), m_level); emit(m_stream.str(), m_level);
} }
else else
{ {
const std::lock_guard<std::mutex> lock(MessageLoggerData::m_mutex); message_logger_buffer->push_back({ m_stream.str(), m_level });
MessageLoggerData::m_buffer.push_back({ m_stream.str(), m_level });
} }
} }
@ -608,22 +619,18 @@ namespace mamba
void MessageLogger::activate_buffer() void MessageLogger::activate_buffer()
{ {
MessageLoggerData::use_buffer = true; message_logger_use_buffer = true;
} }
void MessageLogger::deactivate_buffer() void MessageLogger::deactivate_buffer()
{ {
MessageLoggerData::use_buffer = false; message_logger_use_buffer = false;
} }
void MessageLogger::print_buffer(std::ostream& /*ostream*/) void MessageLogger::print_buffer(std::ostream& /*ostream*/)
{ {
decltype(MessageLoggerData::m_buffer) tmp; MessageLoggerBuffer tmp;
message_logger_buffer->swap(tmp);
{
const std::lock_guard<std::mutex> lock(MessageLoggerData::m_mutex);
MessageLoggerData::m_buffer.swap(tmp);
}
for (const auto& [msg, level] : tmp) for (const auto& [msg, level] : tmp)
{ {

View File

@ -458,7 +458,7 @@ namespace mamba
void PackageFetcher::update_urls_txt() const void PackageFetcher::update_urls_txt() const
{ {
// TODO: check if this lock is really required // TODO: check if this lock is really required
std::lock_guard<std::mutex> lock(urls_txt_mutex); std::unique_lock lock{ urls_txt_mutex };
const auto urls_file_path = m_cache_path / "urls.txt"; const auto urls_file_path = m_cache_path / "urls.txt";
std::ofstream urls_txt(urls_file_path.std_path(), std::ios::app); std::ofstream urls_txt(urls_file_path.std_path(), std::ios::app);
urls_txt << url() << std::endl; urls_txt << url() << std::endl;

View File

@ -740,7 +740,7 @@ namespace mamba
void extract(const fs::u8path& file, const fs::u8path& dest, const ExtractOptions& options) void extract(const fs::u8path& file, const fs::u8path& dest, const ExtractOptions& options)
{ {
static std::mutex extract_mutex; static std::mutex extract_mutex;
std::lock_guard<std::mutex> lock(extract_mutex); std::unique_lock lock{ extract_mutex };
if (util::ends_with(file.string(), ".tar.bz2")) if (util::ends_with(file.string(), ".tar.bz2"))
{ {

View File

@ -9,6 +9,7 @@
#include <mutex> #include <mutex>
#include "mamba/util/build.hpp" #include "mamba/util/build.hpp"
#include "mamba/util/synchronized_value.hpp"
extern "C" extern "C"
{ {
@ -105,19 +106,18 @@ namespace mamba
static std::atomic<MainExecutor*> main_executor{ nullptr }; static std::atomic<MainExecutor*> main_executor{ nullptr };
static std::unique_ptr<MainExecutor> default_executor; static util::synchronized_value<std::unique_ptr<MainExecutor>> default_executor;
static std::mutex default_executor_mutex; // TODO: replace by synchronized_value once available
MainExecutor& MainExecutor::instance() MainExecutor& MainExecutor::instance()
{ {
if (!main_executor) if (!main_executor)
{ {
// When no MainExecutor was created before we create a static one. // When no MainExecutor was created before we create a static one.
std::scoped_lock lock{ default_executor_mutex }; auto synched_default_executor = default_executor.synchronize();
if (!main_executor) // double check necessary to avoid data race if (!main_executor) // double check necessary to avoid data race
{ {
default_executor = std::make_unique<MainExecutor>(); *synched_default_executor = std::make_unique<MainExecutor>();
assert(main_executor == default_executor.get()); assert(main_executor == synched_default_executor->get());
} }
} }
@ -126,8 +126,7 @@ namespace mamba
void MainExecutor::stop_default() void MainExecutor::stop_default()
{ {
std::scoped_lock lock{ default_executor_mutex }; default_executor->reset();
default_executor.reset();
} }
MainExecutor::MainExecutor() MainExecutor::MainExecutor()

View File

@ -1065,6 +1065,32 @@ namespace mamba
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif
struct LockedFilesRegistry_Data // made public to workaround CWG2335, should be private
// otherwise
{
// TODO: replace by something like boost::multiindex or equivalent to avoid having
// to handle 2 hashmaps
std::unordered_map<fs::u8path, std::weak_ptr<LockFileOwner>> locked_files; // TODO:
// consider
// replacing
// by
// real
// concurrent
// set
// to
// avoid
// having
// to
// lock
// the
// whole
// container
std::unordered_map<int, fs::u8path> fd_to_locked_path; // this is a workaround the
// usage of file descriptors
// on linux instead of paths
};
class LockedFilesRegistry class LockedFilesRegistry
{ {
public: public:
@ -1105,10 +1131,10 @@ namespace mamba
} }
const auto absolute_file_path = fs::absolute(file_path); const auto absolute_file_path = fs::absolute(file_path);
std::scoped_lock lock{ mutex }; auto data = m_data.synchronize();
const auto it = locked_files.find(absolute_file_path); const auto it = data->locked_files.find(absolute_file_path);
if (it != locked_files.end()) if (it != data->locked_files.end())
{ {
if (auto lockedfile = it->second.lock()) if (auto lockedfile = it->second.lock())
{ {
@ -1123,8 +1149,8 @@ namespace mamba
{ {
auto lockedfile = std::make_shared<LockFileOwner>(absolute_file_path, timeout); auto lockedfile = std::make_shared<LockFileOwner>(absolute_file_path, timeout);
auto tracker = std::weak_ptr{ lockedfile }; auto tracker = std::weak_ptr{ lockedfile };
locked_files.insert_or_assign(absolute_file_path, std::move(tracker)); data->locked_files.insert_or_assign(absolute_file_path, std::move(tracker));
fd_to_locked_path.insert_or_assign(lockedfile->fd(), absolute_file_path); data->fd_to_locked_path.insert_or_assign(lockedfile->fd(), absolute_file_path);
assert(is_lockfile_locked(*lockedfile)); assert(is_lockfile_locked(*lockedfile));
return lockedfile; return lockedfile;
} }
@ -1135,9 +1161,9 @@ namespace mamba
bool is_locked(const fs::u8path& file_path) const bool is_locked(const fs::u8path& file_path) const
{ {
const auto absolute_file_path = fs::absolute(file_path); const auto absolute_file_path = fs::absolute(file_path);
std::scoped_lock lock{ mutex }; auto data = m_data.synchronize();
auto it = locked_files.find(file_path); auto it = data->locked_files.find(file_path);
if (it != locked_files.end()) if (it != data->locked_files.end())
{ {
return !it->second.expired(); return !it->second.expired();
} }
@ -1150,9 +1176,9 @@ namespace mamba
// note: the resulting value will be obsolete before returning. // note: the resulting value will be obsolete before returning.
bool is_locked(int fd) const bool is_locked(int fd) const
{ {
std::scoped_lock lock{ mutex }; auto data = m_data.synchronize();
const auto it = fd_to_locked_path.find(fd); const auto it = data->fd_to_locked_path.find(fd);
if (it != fd_to_locked_path.end()) if (it != data->fd_to_locked_path.end())
{ {
return is_locked(it->second); return is_locked(it->second);
} }
@ -1162,30 +1188,12 @@ namespace mamba
} }
} }
private: private:
std::atomic_bool m_is_file_locking_allowed{ true }; std::atomic_bool m_is_file_locking_allowed{ true };
std::atomic<std::chrono::seconds> m_default_lock_timeout{ std::chrono::seconds::zero() }; std::atomic<std::chrono::seconds> m_default_lock_timeout{ std::chrono::seconds::zero() };
util::synchronized_value<LockedFilesRegistry_Data, std::recursive_mutex> m_data;
// TODO: replace by something like boost::multiindex or equivalent to avoid having to
// handle 2 hashmaps
std::unordered_map<fs::u8path, std::weak_ptr<LockFileOwner>> locked_files; // TODO:
// consider
// replacing
// by real
// concurrent
// set to
// avoid
// having to
// lock the
// whole
// container
std::unordered_map<int, fs::u8path> fd_to_locked_path; // this is a workaround the
// usage of file descriptors on
// linux instead of paths
mutable std::recursive_mutex mutex; // TODO: replace by synchronized_value once
// available
}; };
static LockedFilesRegistry files_locked_by_this_process; static LockedFilesRegistry files_locked_by_this_process;

View File

@ -92,23 +92,27 @@ namespace mamba::download
std::size_t Mirror::successful_transfers() const std::size_t Mirror::successful_transfers() const
{ {
return m_successful_transfers; return m_stats->successful_transfers;
} }
std::size_t Mirror::failed_transfers() const std::size_t Mirror::failed_transfers() const
{ {
return m_failed_transfers; return m_stats->failed_transfers;
} }
bool Mirror::can_accept_more_connections() const bool Mirror::can_accept_more_connections() const
{ {
return !m_allowed_connections.has_value() || m_running_transfers < m_allowed_connections; const auto stats = m_stats.synchronize();
return !stats->allowed_connections.has_value()
|| stats->running_transfers < stats->allowed_connections;
} }
bool Mirror::can_retry_with_fewer_connections() const bool Mirror::can_retry_with_fewer_connections() const
{ {
return m_running_transfers > 0 const auto stats = m_stats.synchronize();
|| (m_successful_transfers > 0 && m_failed_transfers < m_max_tried_connections); return stats->running_transfers > 0
|| (stats->successful_transfers > 0
&& stats->failed_transfers < stats->max_tried_connections);
} }
namespace namespace
@ -118,40 +122,40 @@ namespace mamba::download
void Mirror::cap_allowed_connections() void Mirror::cap_allowed_connections()
{ {
lock_guard lock(m_stats_mutex); auto stats = m_stats.synchronize();
if (m_running_transfers > 0) if (stats->running_transfers > 0)
{ {
m_allowed_connections = m_running_transfers; stats->allowed_connections = stats->running_transfers;
} }
else else
{ {
m_allowed_connections = std::size_t(1); stats->allowed_connections = std::size_t(1);
} }
} }
void Mirror::increase_running_transfers() void Mirror::increase_running_transfers()
{ {
lock_guard lock(m_stats_mutex); auto stats = m_stats.synchronize();
++m_running_transfers; ++stats->running_transfers;
if (m_max_tried_connections < m_running_transfers) if (stats->max_tried_connections < stats->running_transfers)
{ {
m_max_tried_connections = m_running_transfers; stats->max_tried_connections = stats->running_transfers;
} }
} }
void Mirror::update_transfers_done(bool success, bool record_success) void Mirror::update_transfers_done(bool success, bool record_success)
{ {
lock_guard lock(m_stats_mutex); auto stats = m_stats.synchronize();
--m_running_transfers; --stats->running_transfers;
if (record_success) if (record_success)
{ {
if (success) if (success)
{ {
++m_successful_transfers; ++stats->successful_transfers;
} }
else else
{ {
++m_failed_transfers; ++stats->failed_transfers;
} }
} }
} }

View File

@ -95,6 +95,26 @@ namespace
auto operator<=>(const ValueType&) const noexcept = default; auto operator<=>(const ValueType&) const noexcept = default;
}; };
struct ConvertibleToValueType
{
int i = 0;
operator ValueType() const
{
return { i };
}
};
struct ComparableToValueType
{
int j = 0;
};
bool operator==(const ValueType& left, const ComparableToValueType& right)
{
return left.x == right.j;
}
// NOTE: We do not use TEMPLATE_TEST_CASE or TEMPLATE_LIST_TEST_CASE here because code coverage // NOTE: We do not use TEMPLATE_TEST_CASE or TEMPLATE_LIST_TEST_CASE here because code coverage
// tools (such as gcov/lcov) do not properly attribute coverage to tests instantiated via // tools (such as gcov/lcov) do not properly attribute coverage to tests instantiated via
// template test cases. Instead, we use individual TEST_CASEs for each mutex type, and factorize // template test cases. Instead, we use individual TEST_CASEs for each mutex type, and factorize
@ -113,6 +133,22 @@ namespace
synchronized_value a; synchronized_value a;
} }
SECTION("compatible value assignation")
{
synchronized_value a;
a = ConvertibleToValueType{ 1234 };
REQUIRE(a->x == 1234);
}
SECTION("compatible comparison")
{
synchronized_value a;
ComparableToValueType x{ a->x };
REQUIRE(a == x);
ComparableToValueType y{ a->x + 1 };
REQUIRE(a != y);
}
static constexpr auto initial_value = ValueType{ 42 }; static constexpr auto initial_value = ValueType{ 42 };
synchronized_value sv{ initial_value }; synchronized_value sv{ initial_value };