| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | SlowOperationWatcher.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | RAII guard that watches a scoped operation. A threadpool timer is armed in the |
| 12 | constructor for `SlowThreshold` (10 s default). On the fast path (scope exits |
| 13 | before the threshold) the watcher's destructor cancels and drains the timer and |
| 14 | nothing is emitted. If the threshold is reached first -- including while the |
| 15 | scope is still running or hung indefinitely -- the timer callback fires once, |
| 16 | emitting a single `SlowOperation` telemetry event carrying the phase name and |
| 17 | the call site captured via std::source_location, so the backend can attribute |
| 18 | where time is spent. |
| 19 | |
| 20 | Usage: |
| 21 | |
| 22 | SlowOperationWatcher slow{"WaitForMiniInitConnect"}; |
| 23 | m_miniInitChannel = wsl::shared::SocketChannel{AcceptConnection(timeout), ...}; |
| 24 | |
| 25 | If the scope needs to outlive the watched operation (for example to keep a |
| 26 | pointer into an internal receive buffer alive without a nested block), call |
| 27 | Reset() to disarm the watcher early: |
| 28 | |
| 29 | SlowOperationWatcher slow{"WaitForCreateInstanceResult"}; |
| 30 | const auto& result = channel.ReceiveMessage<...>(...); |
| 31 | slow.Reset(); |
| 32 | // result remains valid and usable here |
| 33 | |
| 34 | --*/ |
| 35 | |
| 36 | #pragma once |
| 37 | |
| 38 | #include <windows.h> |
| 39 | #include <wil/resource.h> |
| 40 | #include <chrono> |
| 41 | #include <source_location> |
| 42 | |
| 43 | class SlowOperationWatcher |
| 44 | { |
| 45 | public: |
| 46 | // Name is restricted to a string-literal reference (const char (&)[N]) to guarantee |
| 47 | // static storage duration: the raw pointer is dereferenced later from a threadpool |
| 48 | // callback, so accepting a `const char*` would make UAF via a temporary (e.g. |
| 49 | // std::string::c_str()) easy. Keep Name a short CamelCase phase identifier that the |
| 50 | // backend query can switch on (e.g. "WaitForMiniInitConnect"). |
| 51 | template <size_t N> |
| 52 | explicit SlowOperationWatcher( |
| 53 | const char (&Name)[N], |
| 54 | std::chrono::milliseconds SlowThreshold = std::chrono::seconds{10}, |
| 55 | std::source_location Location = std::source_location::current()) : |
| 56 | SlowOperationWatcher(static_cast<const char*>(Name), SlowThreshold, Location) |
| 57 | { |
| 58 | } |
| 59 | |
| 60 | ~SlowOperationWatcher() noexcept = default; |
| 61 | |
| 62 | // Disarm the watcher early. After Reset() returns, the threshold callback is |
| 63 | // guaranteed not to fire. Relies on wil::unique_threadpool_timer's destroyer to |
| 64 | // cancel pending callbacks and drain any in-flight one. |
| 65 | void Reset() noexcept; |
| 66 | |
| 67 | SlowOperationWatcher(const SlowOperationWatcher&) = delete; |
| 68 | SlowOperationWatcher& operator=(const SlowOperationWatcher&) = delete; |
| 69 | SlowOperationWatcher(SlowOperationWatcher&&) = delete; |
| 70 | SlowOperationWatcher& operator=(SlowOperationWatcher&&) = delete; |
| 71 | |
| 72 | private: |
| 73 | explicit SlowOperationWatcher(_In_z_ const char* Name, std::chrono::milliseconds SlowThreshold, std::source_location Location); |
| 74 | |
| 75 | static void CALLBACK OnTimerFired(PTP_CALLBACK_INSTANCE, PVOID Context, PTP_TIMER) noexcept; |
| 76 | |
| 77 | const char* const m_name; |
| 78 | const std::chrono::milliseconds m_slowThreshold; |
| 79 | const std::source_location m_location; |
| 80 | wil::unique_threadpool_timer m_timer; |
| 81 | }; |