• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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