| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | COMImplClass.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains the definition for COMImplClass, a helper to forward calls from a COM class to an impl class. |
| 12 | // N.B. This class allows multiple calls to happen in parallel, and only blocks Disconnect() until there are either no more callers, or only calling thread is in a call to the underlying class. |
| 13 | // This is implemented that way so a caller can call Disconnect() from within a call to the COM class without causing a deadlock. |
| 14 | |
| 15 | --*/ |
| 16 | |
| 17 | #pragma once |
| 18 | |
| 19 | namespace wsl::windows::service::wslc { |
| 20 | |
| 21 | template <typename TImpl, typename TPointer = TImpl*> |
| 22 | class COMImplClass |
| 23 | { |
| 24 | public: |
| 25 | void Initialize(TPointer impl) |
| 26 | { |
| 27 | std::unique_lock lock(m_lock); |
| 28 | m_impl = std::move(impl); |
| 29 | } |
| 30 | |
| 31 | void Disconnect() noexcept |
| 32 | { |
| 33 | std::unique_lock lock(m_lock); |
| 34 | |
| 35 | // Only continue if either: |
| 36 | // - There are no current callers |
| 37 | // - This thread is the only caller |
| 38 | |
| 39 | m_cv.wait(lock, [this] { |
| 40 | return m_callers.empty() || m_callers.size() == 1 && *m_callers.begin() == std::this_thread::get_id(); |
| 41 | }); |
| 42 | |
| 43 | m_impl = {}; |
| 44 | } |
| 45 | |
| 46 | protected: |
| 47 | template <typename... Args> |
| 48 | HRESULT CallImpl(void (TImpl::*routine)(Args... args), Args... args) |
| 49 | try |
| 50 | { |
| 51 | auto [lock, impl] = LockImpl(); |
| 52 | ((*impl).*routine)(std::forward<Args>(args)...); |
| 53 | |
| 54 | return S_OK; |
| 55 | } |
| 56 | CATCH_RETURN(); |
| 57 | |
| 58 | template <typename... Args> |
| 59 | HRESULT CallImpl(void (TImpl::*routine)(Args... args) const, Args... args) |
| 60 | try |
| 61 | { |
| 62 | auto [lock, impl] = LockImpl(); |
| 63 | ((*impl).*routine)(std::forward<Args>(args)...); |
| 64 | |
| 65 | return S_OK; |
| 66 | } |
| 67 | CATCH_RETURN(); |
| 68 | |
| 69 | auto GetPointer() |
| 70 | { |
| 71 | if constexpr (std::is_same_v<TPointer, TImpl*>) |
| 72 | { |
| 73 | return m_impl; |
| 74 | } |
| 75 | else |
| 76 | { |
| 77 | return m_impl.lock(); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | [[nodiscard]] auto LockImpl() |
| 82 | { |
| 83 | auto impl = [this] { |
| 84 | std::unique_lock lock{m_lock}; |
| 85 | |
| 86 | auto pointer = GetPointer(); |
| 87 | THROW_HR_IF(RPC_E_DISCONNECTED, !pointer); |
| 88 | |
| 89 | auto [_, inserted] = m_callers.insert(std::this_thread::get_id()); |
| 90 | WI_ASSERT(inserted); |
| 91 | |
| 92 | return pointer; |
| 93 | }(); |
| 94 | |
| 95 | auto release = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [this]() { |
| 96 | std::unique_lock lock{m_lock}; |
| 97 | |
| 98 | auto removed = m_callers.erase(std::this_thread::get_id()); |
| 99 | WI_ASSERT(removed == 1); |
| 100 | |
| 101 | m_cv.notify_one(); |
| 102 | }); |
| 103 | |
| 104 | return std::make_pair(std::move(release), std::move(impl)); |
| 105 | } |
| 106 | |
| 107 | private: |
| 108 | std::mutex m_lock; |
| 109 | std::condition_variable m_cv; |
| 110 | _Guarded_by_(m_lock) std::unordered_set<std::thread::id> m_callers; |
| 111 | TPointer m_impl{}; |
| 112 | }; |
| 113 | |
| 114 | } // namespace wsl::windows::service::wslc |