1 // Copyright 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expresso or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "DeviceOpTracker.h"
16
17 #include <algorithm>
18 #include <type_traits>
19
20 #include "host-common/GfxstreamFatalError.h"
21 #include "host-common/logging.h"
22
23 namespace gfxstream {
24 namespace vk {
25 namespace {
26
27 using emugl::ABORT_REASON_OTHER;
28 using emugl::FatalError;
29
30 constexpr const size_t kSizeLoggingThreshold = 200;
31 constexpr const auto kSizeLoggingTimeThreshold = std::chrono::seconds(1);
32
33 constexpr const auto kAutoDeleteTimeThreshold = std::chrono::seconds(5);
34
35 template <typename T>
36 inline constexpr bool always_false_v = false;
37
38 } // namespace
39
DeviceOpTracker(VkDevice device,VulkanDispatch * deviceDispatch)40 DeviceOpTracker::DeviceOpTracker(VkDevice device, VulkanDispatch* deviceDispatch)
41 : mDevice(device), mDeviceDispatch(deviceDispatch) {}
42
AddPendingGarbage(DeviceOpWaitable waitable,VkFence fence)43 void DeviceOpTracker::AddPendingGarbage(DeviceOpWaitable waitable, VkFence fence) {
44 std::lock_guard<std::mutex> lock(mPendingGarbageMutex);
45
46 mPendingGarbage.push_back(PendingGarbage{
47 .waitable = std::move(waitable),
48 .obj = fence,
49 .timepoint = std::chrono::system_clock::now(),
50 });
51
52 if (mPendingGarbage.size() > kSizeLoggingThreshold) {
53 WARN("VkDevice:%p has %d pending garbage objects.", mDevice, mPendingGarbage.size());
54 }
55 }
56
AddPendingGarbage(DeviceOpWaitable waitable,VkSemaphore semaphore)57 void DeviceOpTracker::AddPendingGarbage(DeviceOpWaitable waitable, VkSemaphore semaphore) {
58 std::lock_guard<std::mutex> lock(mPendingGarbageMutex);
59
60 mPendingGarbage.push_back(PendingGarbage{
61 .waitable = std::move(waitable),
62 .obj = semaphore,
63 .timepoint = std::chrono::system_clock::now(),
64 });
65
66 if (mPendingGarbage.size() > kSizeLoggingThreshold) {
67 WARN("VkDevice:%p has %d pending garbage objects.", mDevice, mPendingGarbage.size());
68 }
69 }
70
PollAndProcessGarbage()71 void DeviceOpTracker::PollAndProcessGarbage() {
72 std::lock_guard<std::mutex> pollFunctionsLock(mPollFunctionsMutex);
73 mPollFunctions.erase(std::remove_if(mPollFunctions.begin(), mPollFunctions.end(),
74 [](const PollFunction& pollingFunc) {
75 DeviceOpStatus status = pollingFunc.func();
76 return status != DeviceOpStatus::kPending;
77 }),
78 mPollFunctions.end());
79
80 if (mPollFunctions.size() > kSizeLoggingThreshold) {
81 // Only report old-enough objects to avoid reporting lots of pending waitables
82 // when many requests have been done in a small amount of time.
83 const auto now = std::chrono::system_clock::now();
84 const auto old = now - kSizeLoggingTimeThreshold;
85 size_t numOldFuncs = std::count_if(
86 mPollFunctions.begin(), mPollFunctions.end(), [old](const PollFunction& pollingFunc) {
87 return (pollingFunc.timepoint < old);
88 });
89 if (numOldFuncs > kSizeLoggingThreshold) {
90 //TODO(b/382028853): should be a warning
91 VERBOSE("VkDevice:%p has %d pending waitables, %d taking more than %d milliseconds.",
92 mDevice, mPollFunctions.size(), numOldFuncs,
93 std::chrono::duration_cast<std::chrono::milliseconds>(kSizeLoggingTimeThreshold));
94 }
95 }
96
97 const auto now = std::chrono::system_clock::now();
98 const auto old = now - kAutoDeleteTimeThreshold;
99 {
100 std::lock_guard<std::mutex> pendingGarbageLock(mPendingGarbageMutex);
101
102 // Assuming that pending garbage is added to the queue in the roughly the order
103 // they are used, encountering an unsignaled/pending waitable likely means that
104 // all pending garbage after is also still pending. This might not necessarily
105 // always be the case but it is a simple heuristic to try to minimize the amount
106 // of work performed here as it is expected that this function will be called
107 // while processing other guest vulkan functions.
108 auto firstPendingIt = std::find_if(mPendingGarbage.begin(), mPendingGarbage.end(),
109 [old](const PendingGarbage& pendingGarbage) {
110 if (pendingGarbage.timepoint < old) {
111 return /*still pending=*/false;
112 }
113 return !IsDone(pendingGarbage.waitable);
114 });
115
116 for (auto it = mPendingGarbage.begin(); it != firstPendingIt; it++) {
117 PendingGarbage& pendingGarbage = *it;
118
119 if (pendingGarbage.timepoint < old) {
120 const auto difference = std::chrono::duration_cast<std::chrono::milliseconds>(
121 pendingGarbage.timepoint - now);
122 WARN("VkDevice:%p had a waitable pending for %d milliseconds. Leaking object.",
123 mDevice, difference.count());
124 continue;
125 }
126
127 std::visit(
128 [this](auto&& arg) {
129 using T = std::decay_t<decltype(arg)>;
130 if constexpr (std::is_same_v<T, VkFence>) {
131 mDeviceDispatch->vkDestroyFence(mDevice, arg, nullptr);
132 } else if constexpr (std::is_same_v<T, VkSemaphore>) {
133 mDeviceDispatch->vkDestroySemaphore(mDevice, arg, nullptr);
134 } else {
135 static_assert(always_false_v<T>, "non-exhaustive visitor!");
136 }
137 },
138 pendingGarbage.obj);
139 }
140
141 mPendingGarbage.erase(mPendingGarbage.begin(), firstPendingIt);
142
143 if (mPendingGarbage.size() > kSizeLoggingThreshold) {
144 WARN("VkDevice:%p has %d pending garbage objects.", mDevice, mPendingGarbage.size());
145 }
146 }
147 }
148
OnDestroyDevice()149 void DeviceOpTracker::OnDestroyDevice() {
150 mDeviceDispatch->vkDeviceWaitIdle(mDevice);
151
152 PollAndProcessGarbage();
153
154 {
155 std::lock_guard<std::mutex> lock(mPendingGarbageMutex);
156 if (!mPendingGarbage.empty()) {
157 WARN("VkDevice:%p has %d leaking garbage objects on destruction.", mDevice,
158 mPendingGarbage.size());
159 }
160 }
161 }
162
AddPendingDeviceOp(std::function<DeviceOpStatus ()> pollFunction)163 void DeviceOpTracker::AddPendingDeviceOp(std::function<DeviceOpStatus()> pollFunction) {
164 std::lock_guard<std::mutex> lock(mPollFunctionsMutex);
165 mPollFunctions.push_back(PollFunction{
166 .func = std::move(pollFunction),
167 .timepoint = std::chrono::system_clock::now(),
168 });
169 }
170
DeviceOpBuilder(DeviceOpTracker & tracker)171 DeviceOpBuilder::DeviceOpBuilder(DeviceOpTracker& tracker) : mTracker(tracker) {}
172
~DeviceOpBuilder()173 DeviceOpBuilder::~DeviceOpBuilder() {
174 if (!mSubmittedFence) {
175 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
176 << "Invalid usage: failed to call OnQueueSubmittedWithFence().";
177 }
178 }
179
CreateFenceForOp()180 VkFence DeviceOpBuilder::CreateFenceForOp() {
181 const VkFenceCreateInfo fenceCreateInfo = {
182 .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
183 .pNext = nullptr,
184 .flags = 0,
185 };
186 VkFence fence = VK_NULL_HANDLE;
187 VkResult result = mTracker.mDeviceDispatch->vkCreateFence(mTracker.mDevice, &fenceCreateInfo,
188 nullptr, &fence);
189
190 mCreatedFence = fence;
191 if (result != VK_SUCCESS) {
192 ERR("DeviceOpBuilder failed to create VkFence!");
193 return VK_NULL_HANDLE;
194 }
195 return fence;
196 }
197
OnQueueSubmittedWithFence(VkFence fence)198 DeviceOpWaitable DeviceOpBuilder::OnQueueSubmittedWithFence(VkFence fence) {
199 if (mCreatedFence.has_value() && fence != mCreatedFence) {
200 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
201 << "Invalid usage: failed to call OnQueueSubmittedWithFence() with the fence "
202 << "requested from CreateFenceForOp.";
203 }
204 mSubmittedFence = fence;
205
206 const bool destroyFenceOnCompletion = mCreatedFence.has_value();
207
208 std::shared_ptr<std::promise<void>> promise = std::make_shared<std::promise<void>>();
209 DeviceOpWaitable future = promise->get_future().share();
210
211 mTracker.AddPendingDeviceOp([device = mTracker.mDevice,
212 deviceDispatch = mTracker.mDeviceDispatch, fence,
213 promise = std::move(promise), destroyFenceOnCompletion] {
214 if (fence == VK_NULL_HANDLE) {
215 return DeviceOpStatus::kDone;
216 }
217
218 VkResult result = deviceDispatch->vkGetFenceStatus(device, fence);
219 if (result == VK_NOT_READY) {
220 return DeviceOpStatus::kPending;
221 }
222
223 if (destroyFenceOnCompletion) {
224 deviceDispatch->vkDestroyFence(device, fence, nullptr);
225 }
226 promise->set_value();
227
228 return result == VK_SUCCESS ? DeviceOpStatus::kDone : DeviceOpStatus::kFailure;
229 });
230
231 return future;
232 }
233
234 } // namespace vk
235 } // namespace gfxstream
236