Skip to content

Commit a0b1889

Browse files
authored
Resuming neutral context from STA should force background thread (#662)
1 parent 8c0832f commit a0b1889

File tree

4 files changed

+138
-36
lines changed

4 files changed

+138
-36
lines changed

strings/base_coroutine_foundation.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace winrt::impl
3535
{
3636
// Note: A blocking wait on the UI thread for an asynchronous operation can cause a deadlock.
3737
// See https://docs.microsoft.com/windows/uwp/cpp-and-winrt-apis/concurrency#block-the-calling-thread
38-
WINRT_ASSERT(!is_sta());
38+
WINRT_ASSERT(!is_sta_thread());
3939
}
4040

4141
template <typename T, typename H>
@@ -119,7 +119,7 @@ namespace winrt::impl
119119

120120
private:
121121
std::experimental::coroutine_handle<> m_handle;
122-
com_ptr<IContextCallback> m_context = apartment_context();
122+
resume_apartment_context m_context;
123123

124124
void Complete()
125125
{

strings/base_coroutine_threadpool.h

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,108 @@
11

22
namespace winrt::impl
33
{
4+
inline auto submit_threadpool_callback(void(__stdcall* callback)(void*, void* context), void* context)
5+
{
6+
if (!WINRT_IMPL_TrySubmitThreadpoolCallback(callback, context, nullptr))
7+
{
8+
throw_last_error();
9+
}
10+
}
11+
412
inline void __stdcall resume_background_callback(void*, void* context) noexcept
513
{
614
std::experimental::coroutine_handle<>::from_address(context)();
715
};
816

917
inline auto resume_background(std::experimental::coroutine_handle<> handle)
1018
{
11-
if (!WINRT_IMPL_TrySubmitThreadpoolCallback(resume_background_callback, handle.address(), nullptr))
12-
{
13-
throw_last_error();
14-
}
19+
submit_threadpool_callback(resume_background_callback, handle.address());
1520
}
1621

17-
inline bool is_sta() noexcept
22+
inline std::pair<int32_t, int32_t> get_apartment_type() noexcept
1823
{
1924
int32_t aptType;
2025
int32_t aptTypeQualifier;
21-
return (0 == WINRT_IMPL_CoGetApartmentType(&aptType, &aptTypeQualifier)) && ((aptType == 0 /*APTTYPE_STA*/) || (aptType == 3 /*APTTYPE_MAINSTA*/));
26+
if (0 == WINRT_IMPL_CoGetApartmentType(&aptType, &aptTypeQualifier))
27+
{
28+
return { aptType, aptTypeQualifier };
29+
}
30+
else
31+
{
32+
return { 1 /* APTTYPE_MTA */, 1 /* APTTYPEQUALIFIER_IMPLICIT_MTA */ };
33+
}
2234
}
2335

24-
inline bool requires_apartment_context() noexcept
36+
inline bool is_sta_thread() noexcept
2537
{
26-
int32_t aptType;
27-
int32_t aptTypeQualifier;
28-
return (0 == WINRT_IMPL_CoGetApartmentType(&aptType, &aptTypeQualifier)) && ((aptType == 0 /*APTTYPE_STA*/) || (aptType == 2 /*APTTYPE_NA*/) || (aptType == 3 /*APTTYPE_MAINSTA*/));
38+
auto type = get_apartment_type();
39+
switch (type.first)
40+
{
41+
case 0: /* APTTYPE_STA */
42+
case 3: /* APTTYPE_MAINSTA */
43+
return true;
44+
case 2: /* APTTYPE_NA */
45+
return type.second == 3 /* APTTYPEQUALIFIER_NA_ON_STA */ ||
46+
type.second == 5 /* APTTYPEQUALIFIER_NA_ON_MAINSTA */;
47+
}
48+
return false;
2949
}
3050

31-
inline auto apartment_context()
51+
struct resume_apartment_context
3252
{
33-
return requires_apartment_context() ? capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext) : nullptr;
34-
}
53+
com_ptr<IContextCallback> m_context = try_capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext);
54+
int32_t m_context_type = get_apartment_type().first;
55+
};
3556

3657
inline int32_t __stdcall resume_apartment_callback(com_callback_args* args) noexcept
3758
{
3859
std::experimental::coroutine_handle<>::from_address(args->data)();
3960
return 0;
4061
};
4162

42-
inline auto resume_apartment(com_ptr<IContextCallback> const& context, std::experimental::coroutine_handle<> handle)
63+
inline void resume_apartment_sync(com_ptr<IContextCallback> const& context, std::experimental::coroutine_handle<> handle)
64+
{
65+
com_callback_args args{};
66+
args.data = handle.address();
67+
68+
check_hresult(context->ContextCallback(resume_apartment_callback, &args, guid_of<ICallbackWithNoReentrancyToApplicationSTA>(), 5, nullptr));
69+
}
70+
71+
inline void resume_apartment_on_threadpool(com_ptr<IContextCallback> const& context, std::experimental::coroutine_handle<> handle)
4372
{
44-
if (context)
73+
struct threadpool_resume
4574
{
46-
com_callback_args args{};
47-
args.data = handle.address();
75+
threadpool_resume(com_ptr<IContextCallback> const& context, std::experimental::coroutine_handle<> handle) :
76+
m_context(context), m_handle(handle) { }
77+
com_ptr<IContextCallback> m_context;
78+
std::experimental::coroutine_handle<> m_handle;
79+
};
80+
auto state = std::make_unique<threadpool_resume>(context, handle);
81+
submit_threadpool_callback([](void*, void* p)
82+
{
83+
std::unique_ptr<threadpool_resume> state{ static_cast<threadpool_resume*>(p) };
84+
resume_apartment_sync(state->m_context, state->m_handle);
85+
}, state.get());
86+
state.release();
87+
}
4888

49-
check_hresult(context->ContextCallback(resume_apartment_callback, &args, guid_of<ICallbackWithNoReentrancyToApplicationSTA>(), 5, nullptr));
89+
inline auto resume_apartment(resume_apartment_context const& context, std::experimental::coroutine_handle<> handle)
90+
{
91+
if ((context.m_context == nullptr) || (context.m_context == try_capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext)))
92+
{
93+
handle();
94+
}
95+
else if (context.m_context_type == 1 /* APTTYPE_MTA */)
96+
{
97+
resume_background(handle);
98+
}
99+
else if ((context.m_context_type == 2 /* APTTYPE_NTA */) && is_sta_thread())
100+
{
101+
resume_apartment_on_threadpool(context.m_context, handle);
50102
}
51103
else
52104
{
53-
if (requires_apartment_context())
54-
{
55-
resume_background(handle);
56-
}
57-
else
58-
{
59-
handle();
60-
}
105+
resume_apartment_sync(context.m_context, handle);
61106
}
62107
}
63108

@@ -294,7 +339,7 @@ WINRT_EXPORT namespace winrt
294339
impl::resume_apartment(context, handle);
295340
}
296341

297-
com_ptr<impl::IContextCallback> context = impl::apartment_context();
342+
impl::resume_apartment_context context;
298343
};
299344

300345
[[nodiscard]] inline auto resume_after(Windows::Foundation::TimeSpan duration) noexcept

test/old_tests/UnitTests/apartment_context.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include "pch.h"
22
#include "catch.hpp"
3+
#include <ctxtcall.h>
34

45
using namespace winrt;
56
using namespace Windows::Foundation;
7+
using namespace Windows::System;
68

79
namespace
810
{
@@ -12,9 +14,56 @@ namespace
1214

1315
co_await context;
1416
}
17+
18+
template<typename TLambda>
19+
void InvokeInContext(IContextCallback* context, TLambda&& lambda)
20+
{
21+
ComCallData data;
22+
data.pUserDefined = &lambda;
23+
check_hresult(context->ContextCallback([](ComCallData* data) -> HRESULT
24+
{
25+
auto& lambda = *reinterpret_cast<TLambda*>(data->pUserDefined);
26+
lambda();
27+
return S_OK;
28+
}, &data, IID_ICallbackWithNoReentrancyToApplicationSTA, 5, nullptr));
29+
}
30+
31+
auto get_winrt_apartment_context_for_com_context(com_ptr<::IContextCallback> const& com_context)
32+
{
33+
std::optional<decltype(apartment_context())> context;
34+
InvokeInContext(com_context.get(), [&] {
35+
context = apartment_context();
36+
});
37+
return context.value();
38+
}
39+
40+
bool is_nta_on_mta()
41+
{
42+
APTTYPE type;
43+
APTTYPEQUALIFIER qualifier;
44+
check_hresult(CoGetApartmentType(&type, &qualifier));
45+
return (type == APTTYPE_NA) && (qualifier == APTTYPEQUALIFIER_NA_ON_MTA || qualifier == APTTYPEQUALIFIER_NA_ON_IMPLICIT_MTA);
46+
}
47+
48+
IAsyncAction TestNeutralApartmentContext()
49+
{
50+
auto controller = DispatcherQueueController::CreateOnDedicatedThread();
51+
co_await resume_foreground(controller.DispatcherQueue());
52+
53+
// Entering neutral apartment from STA should resume on explicit background thread.
54+
auto nta = get_winrt_apartment_context_for_com_context(capture<::IContextCallback>(CoGetDefaultContext, APTTYPE_NA));
55+
co_await nta;
56+
57+
REQUIRE(is_nta_on_mta());
58+
}
1559
}
1660

1761
TEST_CASE("apartment_context coverage")
1862
{
1963
Async().get();
2064
}
65+
66+
TEST_CASE("apartment_context nta")
67+
{
68+
TestNeutralApartmentContext().get();
69+
}

test/test/await_adapter.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ using namespace Windows::System;
88

99
namespace
1010
{
11+
bool is_sta()
12+
{
13+
APTTYPE type;
14+
APTTYPEQUALIFIER qualifier;
15+
check_hresult(CoGetApartmentType(&type, &qualifier));
16+
return (type == APTTYPE_STA) || (type == APTTYPE_MAINSTA);
17+
}
18+
1119
static handle signal{ CreateEventW(nullptr, false, false, nullptr) };
1220

1321
IAsyncAction OtherForegroundAsync()
@@ -29,9 +37,9 @@ namespace
2937

3038
IAsyncAction ForegroundAsync(DispatcherQueue dispatcher)
3139
{
32-
REQUIRE(!impl::is_sta());
40+
REQUIRE(!is_sta());
3341
co_await resume_foreground(dispatcher);
34-
REQUIRE(impl::is_sta());
42+
REQUIRE(is_sta());
3543

3644
// This exercises one STA thread waiting on another thus one context callback
3745
// completing on another.
@@ -48,9 +56,9 @@ namespace
4856

4957
fire_and_forget SignalFromForeground(DispatcherQueue dispatcher)
5058
{
51-
REQUIRE(!impl::is_sta());
59+
REQUIRE(!is_sta());
5260
co_await resume_foreground(dispatcher);
53-
REQUIRE(impl::is_sta());
61+
REQUIRE(is_sta());
5462

5563
// Previously, this signal was never raised because the foreground thread
5664
// was always blocked waiting for ContextCallback to return.
@@ -61,19 +69,19 @@ namespace
6169
{
6270
// Switch to a background (MTA) thread.
6371
co_await resume_background();
64-
REQUIRE(!impl::is_sta());
72+
REQUIRE(!is_sta());
6573

6674
// This exercises one MTA thread waiting on another and just completing
6775
// directly without the overhead of a context switch.
6876
co_await OtherBackgroundAsync();
69-
REQUIRE(!impl::is_sta());
77+
REQUIRE(!is_sta());
7078

7179
// Wait for a coroutine that completes on a foreground (STA) thread.
7280
co_await ForegroundAsync(dispatcher);
7381

7482
// Resumption should automatically switch to a background (MTA) thread
7583
// without blocking the Completed handler (which would in turn block the foreground thread).
76-
REQUIRE(!impl::is_sta());
84+
REQUIRE(!is_sta());
7785

7886
// Attempt to signal from the foreground thread under the assumption
7987
// that the foreground thread is not blocked.

0 commit comments

Comments
 (0)