123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- #include <wil/com_apartment_variable.h>
- #include <wil/com.h>
- #include <functional>
- #include "common.h"
- #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
- template <typename... args_t>
- inline void LogOutput(_Printf_format_string_ PCWSTR format, args_t&&... args)
- {
- OutputDebugStringW(wil::str_printf_failfast<wil::unique_cotaskmem_string>(format, wistd::forward<args_t>(args)...).get());
- }
- inline bool IsComInitialized()
- {
- APTTYPE type{}; APTTYPEQUALIFIER qualifier{};
- return CoGetApartmentType(&type, &qualifier) == S_OK;
- }
- inline void WaitForAllComApartmentsToRundown()
- {
- while (IsComInitialized())
- {
- Sleep(0);
- }
- }
- void co_wait(const wil::unique_event& e)
- {
- HANDLE raw[] = { e.get() };
- ULONG index{};
- REQUIRE_SUCCEEDED(CoWaitForMultipleHandles(COWAIT_DISPATCH_CALLS, INFINITE, static_cast<ULONG>(std::size(raw)), raw, &index));
- }
- void RunApartmentVariableTest(void(*test)())
- {
- test();
- // Apartment variable rundown is async, wait for the last COM apartment
- // to rundown before proceeding to the next test.
- WaitForAllComApartmentsToRundown();
- }
- struct mock_platform
- {
- static unsigned long long GetApartmentId()
- {
- APTTYPE type; APTTYPEQUALIFIER qualifer;
- REQUIRE_SUCCEEDED(CoGetApartmentType(&type, &qualifer)); // ensure COM is inited
- // Approximate apartment Id
- if (type == APTTYPE_STA)
- {
- REQUIRE_FALSE(GetCurrentThreadId() < APTTYPE_MAINSTA);
- return GetCurrentThreadId();
- }
- else
- {
- // APTTYPE_MTA (1), APTTYPE_NA (2), APTTYPE_MAINSTA (3)
- return type;
- }
- }
- static auto RegisterForApartmentShutdown(IApartmentShutdown* observer)
- {
- const auto id = GetApartmentId();
- auto apt_observers = m_observers.find(id);
- if (apt_observers == m_observers.end())
- {
- m_observers.insert({ id, { observer} });
- }
- else
- {
- apt_observers->second.emplace_back(observer);
- }
- return shutdown_type{ reinterpret_cast<APARTMENT_SHUTDOWN_REGISTRATION_COOKIE>(id) };
- }
- static void UnRegisterForApartmentShutdown(APARTMENT_SHUTDOWN_REGISTRATION_COOKIE cookie)
- {
- auto id = reinterpret_cast<unsigned long long>(cookie);
- m_observers.erase(id);
- }
- using shutdown_type = wil::unique_any<APARTMENT_SHUTDOWN_REGISTRATION_COOKIE, decltype(&UnRegisterForApartmentShutdown), UnRegisterForApartmentShutdown>;
- // This is needed to simulate the platform for unit testing.
- static auto CoInitializeEx(DWORD coinitFlags = 0 /*COINIT_MULTITHREADED*/)
- {
- return wil::scope_exit([aptId = GetCurrentThreadId(), init = wil::CoInitializeEx(coinitFlags)]()
- {
- const auto id = GetApartmentId();
- auto apt_observers = m_observers.find(id);
- if (apt_observers != m_observers.end())
- {
- const auto& observers = apt_observers->second;
- for (auto& observer : observers)
- {
- observer->OnUninitialize(id);
- }
- m_observers.erase(apt_observers);
- }
- });
- }
- // Enable the test hook to force losing the race
- inline static constexpr unsigned long AsyncRundownDelayForTestingRaces = 1; // enable test hook
- inline static std::unordered_map<unsigned long long, std::vector<wil::com_ptr<IApartmentShutdown>>> m_observers;
- };
- auto fn() { return 42; };
- auto fn2() { return 43; };
- wil::apartment_variable<int, wil::apartment_variable_leak_action::ignore, mock_platform> g_v1;
- wil::apartment_variable<int, wil::apartment_variable_leak_action::ignore> g_v2;
- template <typename platform = wil::apartment_variable_platform>
- void TestApartmentVariableAllMethods()
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- std::ignore = g_v1.get_or_create(fn);
- wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform> v1;
- REQUIRE(v1.get_if() == nullptr);
- REQUIRE(v1.get_or_create(fn) == 42);
- int value = 43;
- v1.set(value);
- REQUIRE(v1.get_or_create(fn) == 43);
- REQUIRE(v1.get_existing() == 43);
- v1.clear();
- REQUIRE(v1.get_if() == nullptr);
- }
- template <typename platform = wil::apartment_variable_platform>
- void TestApartmentVariableGetOrCreateForms()
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform> v1;
- REQUIRE(v1.get_or_create(fn) == 42);
- v1.clear();
- REQUIRE(v1.get_or_create([&]
- {
- return 1;
- }) == 1);
- v1.clear();
- REQUIRE(v1.get_or_create() == 0);
- }
- template <typename platform = wil::apartment_variable_platform>
- void TestApartmentVariableLifetimes()
- {
- wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform> av1, av2;
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- auto v1 = av1.get_or_create(fn);
- REQUIRE(av1.storage().size() == 1);
- auto v2 = av1.get_existing();
- REQUIRE(av1.current_apartment_variable_count() == 1);
- REQUIRE(v1 == v2);
- }
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- auto v1 = av1.get_or_create(fn);
- auto v2 = av2.get_or_create(fn2);
- REQUIRE((av1.current_apartment_variable_count() == 2));
- REQUIRE(v1 != v2);
- REQUIRE(av1.storage().size() == 1);
- }
- REQUIRE(av1.storage().size() == 0);
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- auto v = av1.get_or_create(fn);
- REQUIRE(av1.current_apartment_variable_count() == 1);
- std::thread([&]() // join below makes this ok
- {
- SetThreadDescription(GetCurrentThread(), L"STA");
- auto coUninit = platform::CoInitializeEx(COINIT_APARTMENTTHREADED);
- std::ignore = av1.get_or_create(fn);
- REQUIRE(av1.storage().size() == 2);
- REQUIRE(av1.current_apartment_variable_count() == 1);
- }).join();
- REQUIRE(av1.storage().size() == 1);
- av1.get_or_create(fn)++;
- v = av1.get_existing();
- REQUIRE(v == 43);
- }
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- std::ignore = av1.get_or_create(fn);
- REQUIRE(av1.current_apartment_variable_count() == 1);
- int i = 1;
- av1.set(i);
- av1.clear();
- REQUIRE(av1.current_apartment_variable_count() == 0);
- // will fail fast since clear() was called.
- // av1.set(1);
- av1.clear_all_apartments_async().get();
- }
- REQUIRE(av1.storage().size() == 0);
- }
- template <typename platform = wil::apartment_variable_platform>
- void TestMultipleApartments()
- {
- wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform> av1, av2;
- wil::unique_event t1Created{ wil::EventOptions::None }, t2Created{ wil::EventOptions::None };
- wil::unique_event t1Shutdown{ wil::EventOptions::None }, t2Shutdown{ wil::EventOptions::None };
- auto apt1_thread = std::thread([&]() // join below makes this ok
- {
- SetThreadDescription(GetCurrentThread(), L"STA 1");
- auto coUninit = platform::CoInitializeEx(COINIT_APARTMENTTHREADED);
- std::ignore = av1.get_or_create(fn);
- std::ignore = av2.get_or_create(fn);
- t1Created.SetEvent();
- co_wait(t1Shutdown);
- });
- auto apt2_thread = std::thread([&]() // join below makes this ok
- {
- SetThreadDescription(GetCurrentThread(), L"STA 2");
- auto coUninit = platform::CoInitializeEx(COINIT_APARTMENTTHREADED);
- std::ignore = av1.get_or_create(fn);
- std::ignore = av2.get_or_create(fn);
- t2Created.SetEvent();
- co_wait(t2Shutdown);
- });
- t1Created.wait();
- t2Created.wait();
- av1.clear_all_apartments_async().get();
- av2.clear_all_apartments_async().get();
- t1Shutdown.SetEvent();
- t2Shutdown.SetEvent();
- apt1_thread.join();
- apt2_thread.join();
- REQUIRE((wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform>::storage().size() == 0));
- }
- template <typename platform = wil::apartment_variable_platform>
- void TestWinningApartmentAlreadyRundownRace()
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform> av;
- std::ignore = av.get_or_create(fn);
- const auto& storage = av.storage(); // for viewing the storage in the debugger
- wil::unique_event otherAptVarCreated{ wil::EventOptions::None };
- wil::unique_event startApartmentRundown{ wil::EventOptions::None };
- wil::unique_event comRundownComplete{ wil::EventOptions::None };
- auto apt_thread = std::thread([&]() // join below makes this ok
- {
- SetThreadDescription(GetCurrentThread(), L"STA");
- auto coUninit = platform::CoInitializeEx(COINIT_APARTMENTTHREADED);
- std::ignore = av.get_or_create(fn);
- otherAptVarCreated.SetEvent();
- co_wait(startApartmentRundown);
- });
- otherAptVarCreated.wait();
- // we now have av in this apartment and in the STA
- REQUIRE(storage.size() == 2);
- // wait for async clean to complete
- av.clear_all_apartments_async().get();
- startApartmentRundown.SetEvent();
- REQUIRE(av.storage().size() == 0);
- apt_thread.join();
- }
- template <typename platform = wil::apartment_variable_platform>
- void TestLosingApartmentAlreadyRundownRace()
- {
- auto coUninit = platform::CoInitializeEx(COINIT_MULTITHREADED);
- wil::apartment_variable<int, wil::apartment_variable_leak_action::fail_fast, platform> av;
- std::ignore = av.get_or_create(fn);
- const auto& storage = av.storage(); // for viewing the storage in the debugger
- wil::unique_event otherAptVarCreated{ wil::EventOptions::None };
- wil::unique_event startApartmentRundown{ wil::EventOptions::None };
- wil::unique_event comRundownComplete{ wil::EventOptions::None };
- auto apt_thread = std::thread([&]() // join below makes this ok
- {
- SetThreadDescription(GetCurrentThread(), L"STA");
- auto coUninit = platform::CoInitializeEx(COINIT_APARTMENTTHREADED);
- std::ignore = av.get_or_create(fn);
- otherAptVarCreated.SetEvent();
- co_wait(startApartmentRundown);
- coUninit.reset();
- comRundownComplete.SetEvent();
- });
- otherAptVarCreated.wait();
- // we now have av in this apartment and in the STA
- REQUIRE(storage.size() == 2);
- auto clearAllOperation = av.clear_all_apartments_async();
- startApartmentRundown.SetEvent();
- comRundownComplete.wait();
- clearAllOperation.get(); // wait for the async rundowns to complete
- REQUIRE(av.storage().size() == 0);
- apt_thread.join();
- }
- TEST_CASE("ComApartmentVariable::ShutdownRegistration", "[LocalOnly][com][unique_apartment_shutdown_registration]")
- {
- {
- wil::unique_apartment_shutdown_registration r;
- }
- {
- auto coUninit = wil::CoInitializeEx(COINIT_MULTITHREADED);
- struct ApartmentObserver : public winrt::implements<ApartmentObserver, IApartmentShutdown>
- {
- void STDMETHODCALLTYPE OnUninitialize(unsigned long long apartmentId) noexcept override
- {
- LogOutput(L"OnUninitialize %ull\n", apartmentId);
- }
- };
- wil::unique_apartment_shutdown_registration apt_shutdown_registration;
- unsigned long long id{};
- REQUIRE_SUCCEEDED(::RoRegisterForApartmentShutdown(winrt::make<ApartmentObserver>().get(), &id, apt_shutdown_registration.put()));
- LogOutput(L"RoRegisterForApartmentShutdown %p\r\n", apt_shutdown_registration.get());
- // don't unregister and let the pending COM apartment rundown invoke the callback.
- apt_shutdown_registration.release();
- }
- }
- TEST_CASE("ComApartmentVariable::CallAllMethods", "[com][apartment_variable]")
- {
- RunApartmentVariableTest(TestApartmentVariableAllMethods<mock_platform>);
- }
- TEST_CASE("ComApartmentVariable::GetOrCreateForms", "[com][apartment_variable]")
- {
- RunApartmentVariableTest(TestApartmentVariableGetOrCreateForms<mock_platform>);
- }
- TEST_CASE("ComApartmentVariable::VariableLifetimes", "[com][apartment_variable]")
- {
- RunApartmentVariableTest(TestApartmentVariableLifetimes<mock_platform>);
- }
- TEST_CASE("ComApartmentVariable::WinningApartmentAlreadyRundownRace", "[com][apartment_variable]")
- {
- RunApartmentVariableTest(TestWinningApartmentAlreadyRundownRace<mock_platform>);
- }
- TEST_CASE("ComApartmentVariable::LosingApartmentAlreadyRundownRace", "[com][apartment_variable]")
- {
- RunApartmentVariableTest(TestLosingApartmentAlreadyRundownRace<mock_platform>);
- }
- TEST_CASE("ComApartmentVariable::MultipleApartments", "[com][apartment_variable]")
- {
- RunApartmentVariableTest(TestMultipleApartments<mock_platform>);
- }
- TEST_CASE("ComApartmentVariable::UseRealPlatformRunAllTests", "[com][apartment_variable]")
- {
- if (!wil::are_apartment_variables_supported())
- {
- return;
- }
- RunApartmentVariableTest(TestApartmentVariableAllMethods);
- RunApartmentVariableTest(TestApartmentVariableGetOrCreateForms);
- RunApartmentVariableTest(TestApartmentVariableLifetimes);
- RunApartmentVariableTest(TestWinningApartmentAlreadyRundownRace);
- RunApartmentVariableTest(TestLosingApartmentAlreadyRundownRace);
- RunApartmentVariableTest(TestMultipleApartments);
- }
- #endif
|