Loading [MathJax]/jax/output/HTML-CSS/config.js
ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages Concepts
ResourceCleanup.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2023 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#pragma once
21
22#include <list>
23#include <memory>
24#include <type_traits>
25
26/**
27 * @brief Queue to deallocate resources before normal program termination.
28 *
29 * Some resources can only be deallocated when a static global object is
30 * still alive, for example a MPI manager, a GPU context or a file handle.
31 * This class can be registered as a callback for normal program termination;
32 * any registered resource that didn't expire will be forcefully deallocated.
33 *
34 * To this end, the "client" class needs to implement cleanup methods that
35 * deallocate managed resources, to be called by the class destructor when
36 * the instance lifetime expires, or by an "attorney" class at normal
37 * program termination, whichever comes first. The attorney class creates
38 * a callback that is pushed in a queue that gets processed before normal
39 * program termination at the user's discretion, or when the queue itself
40 * expires, if the client class instance hasn't expired already.
41 *
42 * Please note the cleanup methods need to be able to run twice, since
43 * the client class destructor will be called eventually, possibly after
44 * @c __run_exit_handlers() is called. The attorney-client idiom is used to
45 * make the private deallocation methods accessible to the cleanup callbacks.
46 */
48 struct Callback {
49 virtual ~Callback() = default;
50 };
51
52 std::list<std::unique_ptr<Callback>> m_resources;
53
54public:
55 ResourceCleanup() = default;
57 while (not m_resources.empty()) {
58 m_resources.pop_back();
59 }
60 }
61
62 /**
63 * @brief Attorney for a resource deallocator.
64 * Usage:
65 * @code{.cpp}
66 * #include "system/ResourceCleanup.hpp"
67 * #include <thrust/device_free.h>
68 * #include <thrust/device_malloc.h>
69 * #include <memory>
70 *
71 * class MyClass;
72 * static std::shared_ptr<ResourceCleanup> resource_queue = nullptr;
73 * static std::shared_ptr<MyClass> resource = nullptr;
74 *
75 * class MyClass {
76 * private:
77 * thrust::device_ptr<float> data_dev;
78 * void free_device_memory() { thrust::device_free(data_dev); }
79 * using Cleanup = ResourceCleanup::Attorney<&MyClass::free_device_memory>;
80 * friend Cleanup;
81 *
82 * public:
83 * MyClass(int n) { data_dev = thrust::device_malloc<float>(n); }
84 * ~MyClass() { free_device_memory(); }
85 * template <class... Args>
86 * static auto make_shared(Args ...args) {
87 * auto obj = std::make_shared<MyClass>(args...);
88 * // comment the next line of code to trigger the CUDA runtime error
89 * // "device free failed: cudaErrorCudartUnloading: driver shutting down"
90 * ::resource_queue->push<MyClass::Cleanup>(obj);
91 * return obj;
92 * }
93 * };
94 *
95 * int main() {
96 * ::resource_queue = std::make_shared<ResourceCleanup>();
97 * ::resource = MyClass::make_shared(5);
98 * // The CUDA primary context expires before normal program termination.
99 * // Yet thrust vectors require a primary context to deallocate device
100 * // memory. Force deallocation before normal program termination.
101 * ::resource_queue.reset();
102 * }
103 * @endcode
104 * @tparam deallocator Member function that deallocates managed resources.
105 */
106 template <auto deallocator> class Attorney {
107 struct detail {
108 template <class C> struct get_class_from_member_function_pointer;
109 template <class C, class Ret, class... Args>
110 struct get_class_from_member_function_pointer<Ret (C::*)(Args...)> {
111 using type = C;
112 };
113 };
114 using Deallocator = decltype(deallocator);
115 static_assert(std::is_member_function_pointer_v<Deallocator>);
116 using Container =
117 typename detail::template get_class_from_member_function_pointer<
118 Deallocator>::type;
119 static_assert(std::is_invocable_v<Deallocator, Container>,
120 "deallocator must have signature void(S::*)()");
121 static_assert(
122 std::is_same_v<std::invoke_result_t<Deallocator, Container>, void>,
123 "deallocator must return void");
124
125 struct CallbackImpl : public Callback {
126 std::weak_ptr<Container> weak_ref;
127 CallbackImpl(std::shared_ptr<Container> const &ref) : weak_ref{ref} {}
128 ~CallbackImpl() override {
129 if (auto const ref = weak_ref.lock()) {
130 (ref.get()->*deallocator)();
131 }
132 }
133 };
134
135 static std::unique_ptr<Callback>
136 make_callback(std::shared_ptr<Container> const &container) {
137 return std::make_unique<CallbackImpl>(container);
138 }
139 friend class ResourceCleanup;
140 };
141
142 [[nodiscard]] auto size() const { return m_resources.size(); }
143 [[nodiscard]] auto empty() const { return m_resources.empty(); }
144
145 /**
146 * @brief Register a resource for cleanup.
147 * Internally, a weak pointer is created and stored in a callback.
148 * @tparam Attorney Specialization of @ref ResourceCleanup::Attorney
149 * that wraps the class @c Container deallocator.
150 * @tparam Container Class that manages resources.
151 */
152 template <typename Attorney, typename Container>
153 void push(std::shared_ptr<Container> const &resource) {
154 m_resources.emplace_back(Attorney::make_callback(resource));
155 }
156};
Attorney for a resource deallocator.
Queue to deallocate resources before normal program termination.
ResourceCleanup()=default
void push(std::shared_ptr< Container > const &resource)
Register a resource for cleanup.
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.