ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
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.