Line data Source code
1 1 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : /// \file
5 : /// Defines class template GlobalCache.
6 :
7 : #pragma once
8 :
9 : #include <charm++.h>
10 : #include <memory>
11 : #include <mutex>
12 : #include <numeric>
13 : #include <optional>
14 : #include <pup.h>
15 : #include <string>
16 : #include <tuple>
17 : #include <unordered_map>
18 : #include <utility>
19 : #include <vector>
20 :
21 : #include "DataStructures/DataBox/Tag.hpp"
22 : #include "DataStructures/DataBox/TagTraits.hpp"
23 : #include "Parallel/ArrayComponentId.hpp"
24 : #include "Parallel/Callback.hpp"
25 : #include "Parallel/CharmRegistration.hpp"
26 : #include "Parallel/Info.hpp"
27 : #include "Parallel/Local.hpp"
28 : #include "Parallel/ParallelComponentHelpers.hpp"
29 : #include "Parallel/ResourceInfo.hpp"
30 : #include "Parallel/Tags/ResourceInfo.hpp"
31 : #include "Utilities/ErrorHandling/Assert.hpp"
32 : #include "Utilities/ErrorHandling/Error.hpp"
33 : #include "Utilities/Gsl.hpp"
34 : #include "Utilities/Numeric.hpp"
35 : #include "Utilities/PrettyType.hpp"
36 : #include "Utilities/Requires.hpp"
37 : #include "Utilities/Serialization/PupStlCpp17.hpp"
38 : #include "Utilities/Serialization/Serialize.hpp"
39 : #include "Utilities/System/ParallelInfo.hpp"
40 : #include "Utilities/TMPL.hpp"
41 : #include "Utilities/TaggedTuple.hpp"
42 : #include "Utilities/TypeTraits/CreateGetTypeAliasOrDefault.hpp"
43 : #include "Utilities/TypeTraits/IsA.hpp"
44 :
45 : #include "Parallel/GlobalCache.decl.h"
46 : #include "Parallel/Main.decl.h"
47 :
48 : /// \cond
49 : namespace Parallel {
50 : template <typename Metavariables>
51 : struct GlobalCache;
52 : } // namespace Parallel
53 : namespace mem_monitor {
54 : template <typename Metavariables>
55 : struct MemoryMonitor;
56 : template <typename ContributingComponent>
57 : struct ContributeMemoryData;
58 : } // namespace mem_monitor
59 : /// \endcond
60 :
61 : namespace Parallel {
62 :
63 : namespace GlobalCache_detail {
64 :
65 : template <class GlobalCacheTag, class Metavariables>
66 : using get_matching_tag = typename matching_tag_helper<
67 : GlobalCacheTag,
68 : tmpl::append<get_const_global_cache_tags<Metavariables>,
69 : get_mutable_global_cache_tags<Metavariables>>>::type;
70 :
71 : template <class GlobalCacheTag, class Metavariables>
72 : using type_for_get = typename type_for_get_helper<
73 : typename get_matching_tag<GlobalCacheTag, Metavariables>::type>::type;
74 :
75 : CREATE_GET_TYPE_ALIAS_OR_DEFAULT(component_being_mocked)
76 :
77 : template <typename... Tags>
78 : auto make_mutable_cache_tag_storage(tuples::TaggedTuple<Tags...>&& input) {
79 : return tuples::TaggedTuple<MutableCacheTag<Tags>...>(
80 : std::make_tuple(std::move(tuples::get<Tags>(input)),
81 : std::unordered_map<Parallel::ArrayComponentId,
82 : std::unique_ptr<Callback>>{})...);
83 : }
84 :
85 : template <typename ParallelComponent, typename ComponentList>
86 : auto get_component_if_mocked_impl() {
87 : if constexpr (tmpl::list_contains_v<ComponentList, ParallelComponent>) {
88 : return ParallelComponent{};
89 : } else {
90 : using mock_find = tmpl::find<
91 : ComponentList,
92 : std::is_same<get_component_being_mocked_or_default<tmpl::_1, void>,
93 : tmpl::pin<ParallelComponent>>>;
94 : static_assert(
95 : tmpl::size<mock_find>::value > 0,
96 : "The requested parallel component (first template argument) is not a "
97 : "known parallel component (second template argument) or a component "
98 : "being mocked by one of those components.");
99 : return tmpl::front<mock_find>{};
100 : }
101 : }
102 :
103 : /// In order to be able to use a mock action testing framework we need to be
104 : /// able to get the correct parallel component from the global cache even when
105 : /// the correct component is a mock. We do this by having the mocked
106 : /// components have a member type alias `component_being_mocked`, and having
107 : /// `Parallel::get_component` check if the component to be retrieved is in the
108 : /// `metavariables::component_list`. If it is not in the `component_list` then
109 : /// we search for a mock component that is mocking the component we are trying
110 : /// to retrieve.
111 : template <typename ComponentList, typename ParallelComponent>
112 : using get_component_if_mocked =
113 : decltype(get_component_if_mocked_impl<ParallelComponent, ComponentList>());
114 :
115 : /// This class replaces the Charm++ base class of the GlobalCache in unit tests
116 : /// that don't have a Charm++ main function.
117 : struct MockGlobalCache {
118 : MockGlobalCache() = default;
119 : explicit MockGlobalCache(CkMigrateMessage* /*msg*/) {}
120 : virtual ~MockGlobalCache() = default;
121 : virtual void pup(PUP::er& /*p*/) {}
122 :
123 : static bool isIrreducible() { return true; }
124 :
125 : #if defined(__GNUC__) && !defined(__clang__)
126 : #pragma GCC diagnostic push
127 : #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
128 : #elif defined(__clang__)
129 : #pragma GCC diagnostic push
130 : #pragma GCC diagnostic ignored "-Wmissing-noreturn"
131 : #endif // defined(__GNUC__) && !defined(__clang__)
132 : template <typename T>
133 : static void contribute(T&& /*unused*/) {
134 : ERROR("Not implemented.");
135 : }
136 : #if defined(__GNUC__) || defined(__clang__)
137 : #pragma GCC diagnostic pop
138 : #endif
139 : };
140 :
141 : #ifdef SPECTRE_CHARM_HAS_MAIN
142 : static constexpr bool mock_global_cache = false;
143 : #else
144 : static constexpr bool mock_global_cache = true;
145 : #endif // SPECTRE_CHARM_HAS_MAIN
146 :
147 : } // namespace GlobalCache_detail
148 :
149 : /// \cond
150 : template <typename ParallelComponentTag, typename Metavariables>
151 : auto get_parallel_component(GlobalCache<Metavariables>& cache)
152 : -> Parallel::proxy_from_parallel_component<
153 : GlobalCache_detail::get_component_if_mocked<
154 : typename Metavariables::component_list, ParallelComponentTag>>&;
155 :
156 : template <typename ParallelComponentTag, typename Metavariables>
157 : auto get_parallel_component(const GlobalCache<Metavariables>& cache)
158 : -> const Parallel::proxy_from_parallel_component<
159 : GlobalCache_detail::get_component_if_mocked<
160 : typename Metavariables::component_list, ParallelComponentTag>>&;
161 : /// \endcond
162 :
163 : /*!
164 : * \ingroup ParallelGroup
165 : * \brief A Charm++ chare that caches global data once per Charm++ node.
166 : *
167 : * \details There are two types of global data that are stored; const data and
168 : * mutable data. Once the GlobalCache is created, const data cannot be edited
169 : * but mutable data can be edited using `Parallel::mutate`.
170 : *
171 : * The template parameter `Metavariables` must define the following type
172 : * aliases:
173 : * - `component_list` typelist of ParallelComponents
174 : * - `const_global_cache_tags` (possibly empty) typelist of tags of
175 : * constant data
176 : * - `mutable_global_cache_tags` (possibly empty) typelist of tags of
177 : * non-constant data
178 : *
179 : * The tag lists for the const items added to the GlobalCache is created by
180 : * combining the following tag lists:
181 : * - `Metavariables::const_global_cache_tags` which should contain only those
182 : * tags that cannot be added from the other tag lists below.
183 : * - `Component::const_global_cache_tags` for each `Component` in
184 : * `Metavariables::component_list` which should contain the tags needed by
185 : * any simple actions called on the Component, as well as tags need by the
186 : * `allocate_array` function of an array component. The type alias may be
187 : * omitted for an empty list.
188 : * - `Action::const_global_cache_tags` for each `Action` in the
189 : * `phase_dependent_action_list` of each `Component` of
190 : * `Metavariables::component_list` which should contain the tags needed by
191 : * that Action. The type alias may be omitted for an empty list.
192 : *
193 : * The tag lists for the mutable items added to the GlobalCache is created
194 : * by combining exactly the same tag lists as for the const items, except with
195 : * `const_global_cache_tags` replaced by `mutable_global_cache_tags`.
196 : *
197 : * The tags in the `const_global_cache_tags` and
198 : * `mutable_global_cache_tags` type lists are db::SimpleTag%s that
199 : * have a `using option_tags` type alias and a static function
200 : * `create_from_options` that are used to create the constant data (or initial
201 : * mutable data) from input file options.
202 : *
203 : * References to const items in the GlobalCache are also added to the
204 : * db::DataBox of each `Component` in the
205 : * `Metavariables::component_list` with the same tag with which they
206 : * were inserted into the GlobalCache. References to mutable items
207 : * in the GlobalCache are not added to the db::DataBox.
208 : *
209 : * Since mutable data is stored once per Charm++ node, we require that
210 : * data structures held by mutable tags have some sort of thread-safety.
211 : * Particularly, we require data structures in mutable tags be Single
212 : * Producer-Multiple Consumer. This means that the data structure should be
213 : * readable/accessible by multiple threads at once, even while being mutated
214 : * (multiple consumer), but will not be edited/mutated simultaneously on
215 : * multiple threads (single producer).
216 : */
217 : template <typename Metavariables>
218 1 : class GlobalCache
219 : : public std::conditional_t<GlobalCache_detail::mock_global_cache,
220 : GlobalCache_detail::MockGlobalCache,
221 : CBase_GlobalCache<Metavariables>> {
222 0 : using Base = std::conditional_t<GlobalCache_detail::mock_global_cache,
223 : GlobalCache_detail::MockGlobalCache,
224 : CBase_GlobalCache<Metavariables>>;
225 0 : using parallel_component_tag_list = tmpl::transform<
226 : typename Metavariables::component_list,
227 : tmpl::bind<
228 : tmpl::type_,
229 : tmpl::bind<Parallel::proxy_from_parallel_component, tmpl::_1>>>;
230 0 : using ParallelComponentTuple =
231 : tuples::tagged_tuple_from_typelist<parallel_component_tag_list>;
232 :
233 : public:
234 0 : static constexpr bool is_mocked = GlobalCache_detail::mock_global_cache;
235 0 : using proxy_type = CProxy_GlobalCache<Metavariables>;
236 0 : using main_proxy_type = CProxy_Main<Metavariables>;
237 : /// Access to the Metavariables template parameter
238 1 : using metavariables = Metavariables;
239 : /// Typelist of the ParallelComponents stored in the GlobalCache
240 1 : using component_list = typename Metavariables::component_list;
241 : // Even though the GlobalCache doesn't run the Algorithm, this type alias
242 : // helps in identifying that the GlobalCache is a Nodegroup using
243 : // Parallel::is_nodegroup_v
244 0 : using chare_type = Parallel::Algorithms::Nodegroup;
245 0 : using const_tags_list = get_const_global_cache_tags<Metavariables>;
246 0 : using ConstTagsTuple = tuples::tagged_tuple_from_typelist<const_tags_list>;
247 0 : using ConstTagsStorage = ConstTagsTuple;
248 0 : using mutable_tags_list = get_mutable_global_cache_tags<Metavariables>;
249 0 : using MutableTagsTuple =
250 : tuples::tagged_tuple_from_typelist<mutable_tags_list>;
251 0 : using MutableTagsStorage = tuples::tagged_tuple_from_typelist<
252 : get_mutable_global_cache_tag_storage<Metavariables>>;
253 :
254 : /// Constructor meant to be used in the ActionTesting framework.
255 1 : GlobalCache(ConstTagsTuple const_global_cache,
256 : MutableTagsTuple mutable_global_cache = {},
257 : std::vector<size_t> procs_per_node = {1}, const int my_proc = 0,
258 : const int my_node = 0, const int my_local_rank = 0);
259 :
260 : /// Constructor meant to be used in charm-aware settings (with a Main proxy).
261 1 : GlobalCache(ConstTagsTuple const_global_cache,
262 : MutableTagsTuple mutable_global_cache,
263 : std::optional<main_proxy_type> main_proxy);
264 :
265 0 : explicit GlobalCache(CkMigrateMessage* msg) : Base(msg) {}
266 :
267 0 : ~GlobalCache() override {
268 : (void)Parallel::charmxx::RegisterChare<
269 : GlobalCache<Metavariables>,
270 : CkIndex_GlobalCache<Metavariables>>::registrar;
271 : }
272 : /// \cond
273 : GlobalCache() = default;
274 : GlobalCache(const GlobalCache&) = default;
275 : GlobalCache& operator=(const GlobalCache&) = default;
276 : GlobalCache(GlobalCache&&) = default;
277 : GlobalCache& operator=(GlobalCache&&) = default;
278 : /// \endcond
279 :
280 : /// Entry method to set the ParallelComponents (should only be called once)
281 1 : void set_parallel_components(ParallelComponentTuple&& parallel_components,
282 : const CkCallback& callback);
283 :
284 : /*!
285 : * \brief Returns whether the object referred to by `GlobalCacheTag`
286 : * (which must be a mutable cache tag) is ready to be accessed by a
287 : * `get` call.
288 : *
289 : * \details `function` is a user-defined invokable that:
290 : * - takes one argument: a const reference to the object referred to by the
291 : * `GlobalCacheTag`.
292 : * - if the data is ready, returns a default constructed
293 : * `std::unique_ptr<CallBack>`
294 : * - if the data is not ready, returns a `std::unique_ptr<CallBack>`,
295 : * where the `Callback` will re-invoke the current action on the
296 : * current parallel component. This callback should be a
297 : * `Parallel::PerformAlgorithmCallback`. Other types of callbacks are not
298 : * supported at this time.
299 : *
300 : * \parblock
301 : * \warning The `function` may be called twice so it should not modify any
302 : * state in its scope.
303 : * \endparblock
304 : *
305 : * \parblock
306 : * \warning If there has already been a callback registered for the given
307 : * `array_component_id`, then the callback returned by `function` will **not**
308 : * be registered or called.
309 : * \endparblock
310 : */
311 : template <typename GlobalCacheTag, typename Function>
312 1 : bool mutable_cache_item_is_ready(
313 : const Parallel::ArrayComponentId& array_component_id,
314 : const Function& function);
315 :
316 : /// Mutates the non-const object identified by GlobalCacheTag.
317 : /// \requires `GlobalCacheTag` is a tag in `mutable_global_cache_tags`
318 : /// defined by the Metavariables and in Actions.
319 : ///
320 : /// Internally calls `Function::apply()`, where `Function` is a
321 : /// user-defined struct and `Function::apply()` is a user-defined
322 : /// static function that mutates the object. `Function::apply()`
323 : /// takes as its first argument a `gsl::not_null` pointer to the
324 : /// object named by the GlobalCacheTag (or if that object is a
325 : /// `std::unique_ptr<T>`, a `gsl::not_null<T*>`), and takes the contents of
326 : /// `args` as subsequent arguments.
327 : template <typename GlobalCacheTag, typename Function, typename... Args>
328 1 : void mutate(const std::tuple<Args...>& args);
329 :
330 : /// Entry method that computes the size of the local branch of the
331 : /// GlobalCache and sends it to the MemoryMonitor parallel component.
332 : ///
333 : /// \note This can only be called if the MemoryMonitor component is in the
334 : /// `component_list` of the metavariables. Also can't be called in the testing
335 : /// framework. Trying to do either of these will result in an ERROR.
336 1 : void compute_size_for_memory_monitor(const double time);
337 :
338 : /// Entry method that will set the value of the Parallel::Tags::ResourceInfo
339 : /// tag to the value passed in (if the tag exists in the GlobalCache)
340 : ///
341 : /// This is only meant to be called once.
342 1 : void set_resource_info(
343 : const Parallel::ResourceInfo<Metavariables>& resource_info);
344 :
345 : /// Retrieve the resource_info
346 1 : const Parallel::ResourceInfo<Metavariables>& get_resource_info() const {
347 : return resource_info_;
348 : }
349 :
350 : /// Retrieve the proxy to the global cache
351 1 : proxy_type get_this_proxy();
352 :
353 0 : void pup(PUP::er& p) override; // NOLINT
354 :
355 : /// Retrieve the proxy to the Main chare (or std::nullopt if the proxy has not
356 : /// been set, i.e. we are not charm-aware).
357 1 : std::optional<main_proxy_type> get_main_proxy();
358 :
359 : /// @{
360 : /// Wrappers for charm++ informational functions.
361 :
362 : /// Number of processing elements
363 1 : int number_of_procs() const;
364 : /// Number of nodes.
365 1 : int number_of_nodes() const;
366 : /// Number of processing elements on the given node.
367 1 : int procs_on_node(const int node_index) const;
368 : /// %Index of first processing element on the given node.
369 1 : int first_proc_on_node(const int node_index) const;
370 : /// %Index of the node for the given processing element.
371 1 : int node_of(const int proc_index) const;
372 : /// The local index for the given processing element on its node.
373 1 : int local_rank_of(const int proc_index) const;
374 : /// %Index of my processing element.
375 1 : int my_proc() const;
376 : /// %Index of my node.
377 1 : int my_node() const;
378 : /// The local index of my processing element on my node.
379 : /// This is in the interval 0, ..., procs_on_node(my_node()) - 1.
380 1 : int my_local_rank() const;
381 : /// @}
382 :
383 : private:
384 : // clang-tidy: false positive, redundant declaration
385 : template <typename GlobalCacheTag, typename MV>
386 0 : friend auto get(const GlobalCache<MV>& cache) // NOLINT
387 : -> const GlobalCache_detail::type_for_get<GlobalCacheTag, MV>&;
388 :
389 : // clang-tidy: false positive, redundant declaration
390 : template <typename ParallelComponentTag, typename MV>
391 0 : friend auto get_parallel_component( // NOLINT
392 : GlobalCache<MV>& cache)
393 : -> Parallel::proxy_from_parallel_component<
394 : GlobalCache_detail::get_component_if_mocked<
395 : typename MV::component_list, ParallelComponentTag>>&;
396 :
397 : // clang-tidy: false positive, redundant declaration
398 : template <typename ParallelComponentTag, typename MV>
399 0 : friend auto get_parallel_component( // NOLINT
400 : const GlobalCache<MV>& cache)
401 : -> const Parallel::proxy_from_parallel_component<
402 : GlobalCache_detail::get_component_if_mocked<
403 : typename MV::component_list,
404 : ParallelComponentTag>>&; // NOLINT
405 :
406 0 : ConstTagsStorage const_global_cache_{};
407 0 : MutableTagsStorage mutable_global_cache_{};
408 : // Wrap mutable tags in Parallel::MutexTag. The type of MutexTag is a
409 : // pair<mutex, mutex>. The first mutex is for editing the value of the mutable
410 : // tag. The second mutex is for editing the vector of callbacks associated
411 : // with the mutable tag.
412 : tuples::tagged_tuple_from_typelist<
413 : tmpl::transform<MutableTagsStorage, tmpl::bind<MutexTag, tmpl::_1>>>
414 0 : mutexes_{};
415 0 : ParallelComponentTuple parallel_components_{};
416 0 : Parallel::ResourceInfo<Metavariables> resource_info_{};
417 0 : bool parallel_components_have_been_set_{false};
418 0 : bool resource_info_has_been_set_{false};
419 0 : std::optional<main_proxy_type> main_proxy_;
420 : // Defaults for testing framework
421 0 : int my_proc_{0};
422 0 : int my_node_{0};
423 0 : int my_local_rank_{0};
424 0 : std::vector<size_t> procs_per_node_{1};
425 : };
426 :
427 : template <typename Metavariables>
428 : GlobalCache<Metavariables>::GlobalCache(ConstTagsTuple const_global_cache,
429 : MutableTagsTuple mutable_global_cache,
430 : std::vector<size_t> procs_per_node,
431 : const int my_proc, const int my_node,
432 : const int my_local_rank)
433 : : const_global_cache_(std::move(const_global_cache)),
434 : mutable_global_cache_(GlobalCache_detail::make_mutable_cache_tag_storage(
435 : std::move(mutable_global_cache))),
436 : main_proxy_(std::nullopt),
437 : my_proc_(my_proc),
438 : my_node_(my_node),
439 : my_local_rank_(my_local_rank),
440 : procs_per_node_(std::move(procs_per_node)) {}
441 :
442 : template <typename Metavariables>
443 : GlobalCache<Metavariables>::GlobalCache(
444 : ConstTagsTuple const_global_cache, MutableTagsTuple mutable_global_cache,
445 : std::optional<main_proxy_type> main_proxy)
446 : : const_global_cache_(std::move(const_global_cache)),
447 : mutable_global_cache_(GlobalCache_detail::make_mutable_cache_tag_storage(
448 : std::move(mutable_global_cache))),
449 : main_proxy_(std::move(main_proxy)) {}
450 :
451 : template <typename Metavariables>
452 : void GlobalCache<Metavariables>::set_parallel_components(
453 : ParallelComponentTuple&& parallel_components, const CkCallback& callback) {
454 : ASSERT(!parallel_components_have_been_set_,
455 : "Can only set the parallel_components once");
456 : parallel_components_ = std::move(parallel_components);
457 : parallel_components_have_been_set_ = true;
458 : this->contribute(callback);
459 : }
460 :
461 : template <typename Metavariables>
462 : template <typename GlobalCacheTag, typename Function>
463 : bool GlobalCache<Metavariables>::mutable_cache_item_is_ready(
464 : const Parallel::ArrayComponentId& array_component_id,
465 : const Function& function) {
466 : using tag = MutableCacheTag<GlobalCache_detail::get_matching_mutable_tag<
467 : GlobalCacheTag, Metavariables>>;
468 : std::unique_ptr<Callback> optional_callback{};
469 : // Returns true if a callback was returned from `function`. Returns false if
470 : // nullptr was returned
471 : const auto callback_was_registered = [this, &function,
472 : &optional_callback]() -> bool {
473 : // Reads don't need a lock.
474 : if constexpr (tt::is_a_v<std::unique_ptr, typename tag::tag::type>) {
475 : optional_callback =
476 : function(*(std::get<0>(tuples::get<tag>(mutable_global_cache_))));
477 : } else {
478 : optional_callback =
479 : function(std::get<0>(tuples::get<tag>(mutable_global_cache_)));
480 : }
481 :
482 : return optional_callback != nullptr;
483 : };
484 :
485 : if (callback_was_registered()) {
486 : optional_callback->register_with_charm();
487 : // Second mutex is for vector of callbacks
488 : std::mutex& mutex = tuples::get<MutexTag<tag>>(mutexes_).second;
489 : {
490 : // Scoped for lock guard
491 : const std::lock_guard<std::mutex> lock(mutex);
492 : std::unordered_map<Parallel::ArrayComponentId, std::unique_ptr<Callback>>&
493 : callbacks = std::get<1>(tuples::get<tag>(mutable_global_cache_));
494 :
495 : if (callbacks.count(array_component_id) != 1) {
496 : callbacks[array_component_id] = std::move(optional_callback);
497 : }
498 : }
499 :
500 : // We must check if the tag is ready again. Consider the following example:
501 : //
502 : // We have two writers, A and B preparing to make independent changes to a
503 : // cache object. We have an element E with a callback waiting for the change
504 : // B is going to make. Suppose the following sequence of events:
505 : //
506 : // 1. A mutates the object, copies the callback list (below in `mutate`),
507 : // and starts the callback for element E.
508 : // 2. E checks the current value and determines it is not ready.
509 : // 3. B mutates the object, copies the empty callback list, and returns with
510 : // nothing to do.
511 : // 4. E returns a new callback, which is added to the callback list.
512 : // 5. A returns.
513 : //
514 : // We now have E waiting on a change that has already happened. This will
515 : // most certainly result in a deadlock if E is blocking the Algorithm. Thus
516 : // we must do another check for whether the cache object is ready or not. In
517 : // the order of events, this check happens sometime after 4. When this check
518 : // happens, E concludes that the cache object *is* ready (because 3 is when
519 : // the object was mutated) and E can continue on.
520 : //
521 : // If this second check reveals that the object *is* ready, then we have an
522 : // unecessary callback in our map, so we remove it.
523 : //
524 : // If this second check reveals that the object *isn't* ready, then we don't
525 : // bother adding another callback to the map because one already exists. No
526 : // need to call a callback twice.
527 : //
528 : // This function returns true if no callback was registered and false if one
529 : // was registered.
530 : const bool cache_item_is_ready = not callback_was_registered();
531 : if (cache_item_is_ready) {
532 : const std::lock_guard<std::mutex> lock(mutex);
533 : std::unordered_map<Parallel::ArrayComponentId, std::unique_ptr<Callback>>&
534 : callbacks = std::get<1>(tuples::get<tag>(mutable_global_cache_));
535 :
536 : callbacks.erase(array_component_id);
537 : }
538 :
539 : return cache_item_is_ready;
540 : } else {
541 : // The user-defined `function` didn't specify a callback, which
542 : // means that the item is ready.
543 : return true;
544 : }
545 : }
546 :
547 : template <typename Metavariables>
548 : template <typename GlobalCacheTag, typename Function, typename... Args>
549 : void GlobalCache<Metavariables>::mutate(const std::tuple<Args...>& args) {
550 : (void)Parallel::charmxx::RegisterGlobalCacheMutate<
551 : Metavariables, GlobalCacheTag, Function, Args...>::registrar;
552 : using tag = MutableCacheTag<GlobalCache_detail::get_matching_mutable_tag<
553 : GlobalCacheTag, Metavariables>>;
554 :
555 : // Do the mutate.
556 : std::apply(
557 : [this](const auto&... local_args) {
558 : // First mutex is for value of mutable tag
559 : std::mutex& mutex = tuples::get<MutexTag<tag>>(mutexes_).first;
560 : const std::lock_guard<std::mutex> lock(mutex);
561 : if constexpr (tt::is_a_v<std::unique_ptr, typename tag::tag::type>) {
562 : Function::apply(make_not_null(&(*std::get<0>(
563 : tuples::get<tag>(mutable_global_cache_)))),
564 : local_args...);
565 : } else {
566 : Function::apply(make_not_null(&(std::get<0>(
567 : tuples::get<tag>(mutable_global_cache_)))),
568 : local_args...);
569 : }
570 : },
571 : args);
572 :
573 : // A callback might call mutable_cache_item_is_ready, which might add yet
574 : // another callback to the vector of callbacks. We don't want to immediately
575 : // invoke this new callback as it might just add another callback again (and
576 : // again in an infinite loop). And we don't want to remove it from the map of
577 : // callbacks before it is invoked otherwise we could get a deadlock.
578 : // Therefore, after locking it, we std::move the map of callbacks into a
579 : // temporary map, clear the original map, and invoke the callbacks in the
580 : // temporary map.
581 : std::unordered_map<Parallel::ArrayComponentId, std::unique_ptr<Callback>>
582 : callbacks{};
583 : // Second mutex is for map of callbacks
584 : std::mutex& mutex = tuples::get<MutexTag<tag>>(mutexes_).second;
585 : {
586 : // Scoped for lock guard
587 : const std::lock_guard<std::mutex> lock(mutex);
588 : callbacks = std::move(std::get<1>(tuples::get<tag>(mutable_global_cache_)));
589 : std::get<1>(tuples::get<tag>(mutable_global_cache_)).clear();
590 : }
591 :
592 : // Invoke the callbacks. Any new callbacks that are added to the
593 : // list (if a callback calls mutable_cache_item_is_ready) will be
594 : // saved and will not be invoked here.
595 : for (auto& [array_component_id, callback] : callbacks) {
596 : (void)array_component_id;
597 : callback->invoke();
598 : }
599 : }
600 :
601 : #if defined(__GNUC__) && !defined(__clang__)
602 : #pragma GCC diagnostic push
603 : #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
604 : #endif // defined(__GNUC__) && !defined(__clang__)
605 : template <typename Metavariables>
606 : void GlobalCache<Metavariables>::compute_size_for_memory_monitor(
607 : const double time) {
608 : if constexpr (tmpl::list_contains_v<
609 : typename Metavariables::component_list,
610 : mem_monitor::MemoryMonitor<Metavariables>>) {
611 : const double size_in_bytes =
612 : static_cast<double>(size_of_object_in_bytes(*this));
613 : const double size_in_MB = size_in_bytes / 1.0e6;
614 :
615 : auto& mem_monitor_proxy = Parallel::get_parallel_component<
616 : mem_monitor::MemoryMonitor<Metavariables>>(*this);
617 :
618 : const int my_node = Parallel::my_node<int>(*this);
619 :
620 : Parallel::simple_action<
621 : mem_monitor::ContributeMemoryData<GlobalCache<Metavariables>>>(
622 : mem_monitor_proxy, time, my_node, size_in_MB);
623 : } else {
624 : (void)time;
625 : ERROR(
626 : "GlobalCache::compute_size_for_memory_monitor can only be called if "
627 : "the MemoryMonitor is in the component list in the metavariables.\n");
628 : }
629 : }
630 :
631 : template <typename Metavariables>
632 : void GlobalCache<Metavariables>::set_resource_info(
633 : const Parallel::ResourceInfo<Metavariables>& resource_info) {
634 : ASSERT(not resource_info_has_been_set_,
635 : "Can only set the resource info once");
636 : resource_info_ = resource_info;
637 : resource_info_has_been_set_ = true;
638 : }
639 : #if defined(__GNUC__) && !defined(__clang__)
640 : #pragma GCC diagnostic pop
641 : #endif // defined(__GNUC__) && !defined(__clang__)
642 :
643 : template <typename Metavariables>
644 : typename Parallel::GlobalCache<Metavariables>::proxy_type
645 : GlobalCache<Metavariables>::get_this_proxy() {
646 : if constexpr (is_mocked) {
647 : // The proxy is not used in the testing framework
648 : return Parallel::GlobalCache<Metavariables>::proxy_type{};
649 : } else {
650 : return this->thisProxy;
651 : }
652 : }
653 :
654 : template <typename Metavariables>
655 : std::optional<typename Parallel::GlobalCache<Metavariables>::main_proxy_type>
656 : GlobalCache<Metavariables>::get_main_proxy() {
657 : return main_proxy_;
658 : }
659 :
660 : // For all these functions, if the main proxy is set (meaning we are
661 : // charm-aware) then just call the sys:: functions. Otherwise, use the values
662 : // set for the testing framework (or the defaults).
663 : template <typename Metavariables>
664 : int GlobalCache<Metavariables>::number_of_procs() const {
665 : return main_proxy_.has_value()
666 : ? sys::number_of_procs()
667 : : static_cast<int>(alg::accumulate(procs_per_node_, 0_st));
668 : }
669 :
670 : template <typename Metavariables>
671 : int GlobalCache<Metavariables>::number_of_nodes() const {
672 : return main_proxy_.has_value() ? sys::number_of_nodes()
673 : : static_cast<int>(procs_per_node_.size());
674 : }
675 :
676 : template <typename Metavariables>
677 : int GlobalCache<Metavariables>::procs_on_node(const int node_index) const {
678 : return main_proxy_.has_value()
679 : ? sys::procs_on_node(node_index)
680 : : static_cast<int>(
681 : procs_per_node_[static_cast<size_t>(node_index)]);
682 : }
683 :
684 : template <typename Metavariables>
685 : int GlobalCache<Metavariables>::first_proc_on_node(const int node_index) const {
686 : return main_proxy_.has_value()
687 : ? sys::first_proc_on_node(node_index)
688 : : static_cast<int>(
689 : std::accumulate(procs_per_node_.begin(),
690 : procs_per_node_.begin() + node_index, 0_st));
691 : }
692 :
693 : template <typename Metavariables>
694 : int GlobalCache<Metavariables>::node_of(const int proc_index) const {
695 : if (main_proxy_.has_value()) {
696 : // For some reason gcov doesn't think this line is tested even though it is
697 : // in Test_AlgorithmGlobalCache.cpp
698 : return sys::node_of(proc_index); // LCOV_EXCL_LINE
699 : } else {
700 : size_t procs_so_far = 0;
701 : size_t node = 0;
702 : while (procs_so_far <= static_cast<size_t>(proc_index)) {
703 : procs_so_far += procs_per_node_[node];
704 : ++node;
705 : }
706 : return static_cast<int>(--node);
707 : }
708 : }
709 :
710 : template <typename Metavariables>
711 : int GlobalCache<Metavariables>::local_rank_of(const int proc_index) const {
712 : return main_proxy_.has_value()
713 : ? sys::local_rank_of(proc_index)
714 : : proc_index - first_proc_on_node(node_of(proc_index));
715 : }
716 :
717 : template <typename Metavariables>
718 : int GlobalCache<Metavariables>::my_proc() const {
719 : return main_proxy_.has_value() ? sys::my_proc() : my_proc_;
720 : }
721 :
722 : template <typename Metavariables>
723 : int GlobalCache<Metavariables>::my_node() const {
724 : return main_proxy_.has_value() ? sys::my_node() : my_node_;
725 : }
726 :
727 : template <typename Metavariables>
728 : int GlobalCache<Metavariables>::my_local_rank() const {
729 : return main_proxy_.has_value() ? sys::my_local_rank() : my_local_rank_;
730 : }
731 :
732 : template <typename Metavariables>
733 : void GlobalCache<Metavariables>::pup(PUP::er& p) {
734 : p | const_global_cache_;
735 : p | parallel_components_;
736 : p | mutable_global_cache_;
737 : p | main_proxy_;
738 : p | parallel_components_have_been_set_;
739 : p | resource_info_has_been_set_;
740 : p | my_proc_;
741 : p | my_node_;
742 : p | my_local_rank_;
743 : p | procs_per_node_;
744 : }
745 :
746 : /// @{
747 : /// \ingroup ParallelGroup
748 : /// \brief Access the Charm++ proxy associated with a ParallelComponent
749 : ///
750 : /// \requires ParallelComponentTag is a tag in component_list
751 : ///
752 : /// \returns a Charm++ proxy that can be used to call an entry method on the
753 : /// chare(s)
754 : template <typename ParallelComponentTag, typename Metavariables>
755 1 : auto get_parallel_component(GlobalCache<Metavariables>& cache)
756 : -> Parallel::proxy_from_parallel_component<
757 : GlobalCache_detail::get_component_if_mocked<
758 : typename Metavariables::component_list, ParallelComponentTag>>& {
759 : return tuples::get<tmpl::type_<Parallel::proxy_from_parallel_component<
760 : GlobalCache_detail::get_component_if_mocked<
761 : typename Metavariables::component_list, ParallelComponentTag>>>>(
762 : cache.parallel_components_);
763 : }
764 :
765 : template <typename ParallelComponentTag, typename Metavariables>
766 1 : auto get_parallel_component(const GlobalCache<Metavariables>& cache)
767 : -> const Parallel::proxy_from_parallel_component<
768 : GlobalCache_detail::get_component_if_mocked<
769 : typename Metavariables::component_list, ParallelComponentTag>>& {
770 : return tuples::get<tmpl::type_<Parallel::proxy_from_parallel_component<
771 : GlobalCache_detail::get_component_if_mocked<
772 : typename Metavariables::component_list, ParallelComponentTag>>>>(
773 : cache.parallel_components_);
774 : }
775 : /// @}
776 :
777 : /// @{
778 : /// \ingroup ParallelGroup
779 : /// \brief Access data in the cache
780 : ///
781 : /// \requires GlobalCacheTag is a tag in the `mutable_global_cache_tags`
782 : /// or `const_global_cache_tags` defined by the Metavariables and in Actions.
783 : ///
784 : /// \returns a constant reference to an object in the cache
785 : template <typename GlobalCacheTag, typename Metavariables>
786 1 : auto get(const GlobalCache<Metavariables>& cache)
787 : -> const GlobalCache_detail::type_for_get<GlobalCacheTag, Metavariables>& {
788 : constexpr bool is_mutable =
789 : is_in_mutable_global_cache<Metavariables, GlobalCacheTag>;
790 : // We check if the tag is to be retrieved directly or via a base class
791 : using tmp_tag =
792 : GlobalCache_detail::get_matching_tag<GlobalCacheTag, Metavariables>;
793 : using tag =
794 : tmpl::conditional_t<is_mutable, MutableCacheTag<tmp_tag>, tmp_tag>;
795 : if constexpr (is_mutable) {
796 : // Tag is not in the const tags, so use mutable_global_cache_. No locks here
797 : // because we require all mutable tags to be able to be read at all times
798 : // (even when being written to)
799 : if constexpr (tt::is_a_v<std::unique_ptr, typename tag::tag::type>) {
800 : return *std::get<0>(tuples::get<tag>(cache.mutable_global_cache_));
801 : } else {
802 : return std::get<0>(tuples::get<tag>(cache.mutable_global_cache_));
803 : }
804 : } else {
805 : // Tag is in the const tags, so use const_global_cache_
806 : if constexpr (tt::is_a_v<std::unique_ptr, typename tag::type>) {
807 : return *(tuples::get<tag>(cache.const_global_cache_));
808 : } else {
809 : return tuples::get<tag>(cache.const_global_cache_);
810 : }
811 : }
812 : }
813 :
814 : /// \ingroup ParallelGroup
815 : /// \brief Returns whether the object identified by `GlobalCacheTag`
816 : /// is ready to be accessed by `get`.
817 : ///
818 : /// \requires `GlobalCacheTag` is a tag in `mutable_global_cache_tags`
819 : /// defined by the Metavariables and in Actions.
820 : ///
821 : /// \requires `function` is a user-defined invokable that takes one argument:
822 : /// a const reference to the object referred to by the
823 : /// `GlobalCacheTag`. `function` returns a
824 : /// `std::unique_ptr<CallBack>` that determines the readiness. To
825 : /// indicate that the item is ready, the `std::unique_ptr` returned
826 : /// by `function` must be nullptr; in this case
827 : /// `mutable_cache_item_is_ready` returns true. To indicate that the
828 : /// item is not ready, the `std::unique_ptr` returned by `function`
829 : /// must be valid; in this case, `mutable_cache_item_is_ready`
830 : /// appends the `std::unique_ptr<Callback>` to an
831 : /// internal list of callbacks to be called on `mutate`, and then
832 : /// returns false.
833 : ///
834 : /// \note If `function` is returning a valid callback, it should only return a
835 : /// `Parallel::PerformAlgorithmCallback`. Other types of callbacks are not
836 : /// supported at this time.
837 : template <typename GlobalCacheTag, typename Function, typename Metavariables>
838 1 : bool mutable_cache_item_is_ready(
839 : GlobalCache<Metavariables>& cache,
840 : const Parallel::ArrayComponentId& array_component_id,
841 : const Function& function) {
842 : return cache.template mutable_cache_item_is_ready<GlobalCacheTag>(
843 : array_component_id, function);
844 : }
845 :
846 : /// \ingroup ParallelGroup
847 : ///
848 : /// \brief Mutates non-const data in the cache, by calling `Function::apply()`
849 : ///
850 : /// \requires `GlobalCacheTag` is a tag in tag_list.
851 : /// \requires `Function` is a struct with a static void `apply()`
852 : /// function that mutates the object. `Function::apply()` takes as its
853 : /// first argument a `gsl::not_null` pointer to the object named by
854 : /// the `GlobalCacheTag`, and takes `args` as
855 : /// subsequent arguments.
856 : template <typename GlobalCacheTag, typename Function, typename Metavariables,
857 : typename... Args>
858 1 : void mutate(GlobalCache<Metavariables>& cache, Args&&... args) {
859 : if (cache.get_main_proxy().has_value()) {
860 : if constexpr (not GlobalCache<Metavariables>::is_mocked) {
861 : cache.thisProxy.template mutate<GlobalCacheTag, Function>(
862 : std::make_tuple<Args...>(std::forward<Args>(args)...));
863 : } else {
864 : ERROR(
865 : "Main proxy is set but global cache is being mocked. This is "
866 : "currently not implemented.");
867 : }
868 : } else {
869 : cache.template mutate<GlobalCacheTag, Function>(
870 : std::make_tuple<Args...>(std::forward<Args>(args)...));
871 : }
872 : }
873 :
874 : namespace Tags {
875 : /// \ingroup DataBoxTagsGroup
876 : /// \ingroup ParallelGroup
877 : /// Tag to retrieve the `Parallel::GlobalCache` from the DataBox.
878 1 : struct GlobalCache : db::BaseTag {};
879 :
880 : template <class Metavariables>
881 0 : struct GlobalCacheProxy : db::SimpleTag {
882 0 : using type = CProxy_GlobalCache<Metavariables>;
883 : };
884 :
885 : template <class Metavariables>
886 0 : struct GlobalCacheImpl : GlobalCache, db::SimpleTag {
887 0 : using type = Parallel::GlobalCache<Metavariables>*;
888 0 : static std::string name() { return "GlobalCache"; }
889 : };
890 :
891 : template <class Metavariables>
892 0 : struct GlobalCacheImplCompute : GlobalCacheImpl<Metavariables>, db::ComputeTag {
893 0 : using base = GlobalCacheImpl<Metavariables>;
894 0 : using argument_tags = tmpl::list<GlobalCacheProxy<Metavariables>>;
895 0 : using return_type = Parallel::GlobalCache<Metavariables>*;
896 0 : static void function(
897 : const gsl::not_null<Parallel::GlobalCache<Metavariables>**>
898 : local_branch_of_global_cache,
899 : const CProxy_GlobalCache<Metavariables>& global_cache_proxy) {
900 : *local_branch_of_global_cache = Parallel::local_branch(global_cache_proxy);
901 : }
902 : };
903 :
904 : /// \ingroup DataBoxTagsGroup
905 : /// \ingroup ParallelGroup
906 : /// Tag used to retrieve data from the `Parallel::GlobalCache`. This is the
907 : /// recommended way for compute tags to retrieve data out of the global cache.
908 : template <class CacheTag>
909 1 : struct FromGlobalCache : CacheTag, db::ReferenceTag {
910 : static_assert(db::is_simple_tag_v<CacheTag>);
911 0 : using base = CacheTag;
912 0 : using argument_tags = tmpl::list<GlobalCache>;
913 :
914 : template <class Metavariables>
915 0 : static const auto& get(
916 : const Parallel::GlobalCache<Metavariables>* const& cache) {
917 : return Parallel::get<CacheTag>(*cache);
918 : }
919 : };
920 :
921 : template <typename Metavariables>
922 0 : struct ResourceInfoReference : ResourceInfo<Metavariables>, db::ReferenceTag {
923 0 : using base = ResourceInfo<Metavariables>;
924 0 : using argument_tags = tmpl::list<GlobalCache>;
925 :
926 0 : static const auto& get(
927 : const Parallel::GlobalCache<Metavariables>* const& cache) {
928 : return cache->get_resource_info();
929 : }
930 : };
931 : } // namespace Tags
932 : } // namespace Parallel
933 :
934 : namespace PUP {
935 : /// \cond
936 : // Warning! This is an invalid and kludgey pupper, because when unpacking it
937 : // produces a `nullptr` rather than a valid `GlobalCache*`. This function is
938 : // provided _only_ to enable putting a `GlobalCache*` in the DataBox.
939 : //
940 : // SpECTRE parallel components with a `GlobalCache*` in their DataBox should
941 : // set this pointer using a compute item that calls `Parallel::local_branch` on
942 : // a stored proxy. When deserializing the DataBox, these components should call
943 : // `db:mutate<GlobalCacheProxy>(box)` to force the DataBox to update the
944 : // pointer from the new Charm++ proxy.
945 : //
946 : // We do not currently anticipate needing to (de)serialize a `GlobalCache*`
947 : // outside of a DataBox. But if this need arises, it will be necessary to
948 : // provide a non-kludgey pupper here.
949 : //
950 : // Correctly (de)serializing the `GlobalCache*` would require obtaining a
951 : // `CProxy_GlobalCache` item and calling `Parallel::local_branch` on it ---
952 : // just as in the workflow described above, but within the pupper vs in the
953 : // DataBox. But this strategy fails when restarting from a checkpoint file,
954 : // because calling `Parallel::local_branch` may not be well-defined in the
955 : // unpacking pup call when all Charm++ components may not yet be fully restored.
956 : // This difficulty is why we instead write an invalid pupper here.
957 : //
958 : // In future versions of Charm++, the pup function may know whether it is
959 : // called in the context of checkpointing, load balancing, etc. This knowledge
960 : // would enable us to write a valid pupper for non-checkpoint contexts, and
961 : // return a `nullptr` only when restoring from checkpoint.
962 : template <typename Metavariables>
963 : inline void operator|(PUP::er& p, // NOLINT
964 : Parallel::GlobalCache<Metavariables>*& t) {
965 : if (p.isUnpacking()) {
966 : t = nullptr;
967 : }
968 : }
969 : /// \endcond
970 : } // namespace PUP
971 :
972 0 : #define CK_TEMPLATES_ONLY
973 : #include "Parallel/GlobalCache.def.h"
974 : #undef CK_TEMPLATES_ONLY
|