Skip to content

Commit 4363e5c

Browse files
authored
Stack usage reduction in apartment switching, and lifetime fixes (#1272)
1 parent 3bbee2c commit 4363e5c

File tree

4 files changed

+71
-7
lines changed

4 files changed

+71
-7
lines changed

strings/base_coroutine_foundation.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,12 @@ namespace winrt::impl
135135
}
136136
else
137137
{
138-
resume_apartment(m_context, std::exchange(m_handle, {}), &m_awaiter->failure);
138+
auto handle = std::exchange(m_handle, {});
139+
if (!resume_apartment(m_context, handle, &m_awaiter->failure))
140+
{
141+
handle.resume();
142+
}
139143
}
140-
141144
}
142145
};
143146

@@ -182,7 +185,6 @@ namespace winrt::impl
182185
private:
183186
auto register_completed_callback(coroutine_handle<> handle)
184187
{
185-
auto extend_lifetime = async;
186188
async.Completed(disconnect_aware_handler(this, handle));
187189
#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED
188190
if (!suspending.exchange(false, std::memory_order_acquire))

strings/base_coroutine_threadpool.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,22 @@ namespace winrt::impl
118118
WINRT_ASSERT(context.valid());
119119
if ((context.m_context == nullptr) || (context.m_context == try_capture<IContextCallback>(WINRT_IMPL_CoGetObjectContext)))
120120
{
121-
handle();
121+
return false;
122122
}
123123
else if (context.m_context_type == 1 /* APTTYPE_MTA */)
124124
{
125125
resume_background(handle);
126+
return true;
126127
}
127128
else if (is_sta_thread())
128129
{
129130
resume_apartment_on_threadpool(context.m_context, handle, failure);
131+
return true;
130132
}
131133
else
132134
{
133135
resume_apartment_sync(context.m_context, handle, failure);
136+
return true;
134137
}
135138
}
136139
}
@@ -315,7 +318,7 @@ namespace winrt::impl
315318
{
316319
struct apartment_awaiter
317320
{
318-
apartment_context context; // make a copy because resuming may destruct the original
321+
apartment_context const& context;
319322
int32_t failure = 0;
320323

321324
bool await_ready() const noexcept
@@ -328,9 +331,17 @@ namespace winrt::impl
328331
check_hresult(failure);
329332
}
330333

331-
void await_suspend(impl::coroutine_handle<> handle)
334+
auto await_suspend(impl::coroutine_handle<> handle)
332335
{
333-
impl::resume_apartment(context.context, handle, &failure);
336+
auto context_copy = context;
337+
#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED
338+
if (!impl::resume_apartment(context_copy.context, handle, &failure))
339+
{
340+
handle.resume();
341+
}
342+
#else
343+
return impl::resume_apartment(context_copy.context, handle, &failure);
344+
#endif
334345
}
335346
};
336347

test/test/await_completed.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,47 @@ namespace
6666
// MSVC standard-conforming coroutines (as well as gcc and clang coroutines)
6767
// support "bool await_suspend" just fine.
6868
REQUIRE(consumed == 0);
69+
#endif
70+
}
71+
72+
// co_await the same apartment context and confirm that stack does not grow.
73+
// This is in await_completed.cpp because it's basically the same thing as awaiting
74+
// an already-completed coroutine, so the test uses the same infrastructure.
75+
IAsyncAction TestApartmentContextNop()
76+
{
77+
winrt::apartment_context same_context;
78+
79+
uintptr_t initial = approximate_stack_pointer();
80+
co_await resume_sync_from_await_suspend();
81+
uintptr_t sync_usage = initial - approximate_stack_pointer();
82+
83+
initial = approximate_stack_pointer();
84+
co_await same_context;
85+
uintptr_t consumed = initial - approximate_stack_pointer();
86+
87+
#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED
88+
// This branch is taken only for MSVC prerelease coroutines.
89+
//
90+
// MSVC prerelease coroutines prior to 16.11 do not implement "bool await_suspend" reliably,
91+
// so we can't use it impl::apartment_awaiter. We must resume inline inside await_suspend,
92+
// so there is a small amount of stack usage. (Pre-16.11 and post-16.11 prerelease coroutines
93+
// are interoperable, so we cannot change behavior based on which compiler we are using,
94+
// because that would introduce ODR violations. Our first opportunity to change behavior
95+
// is the ABI breaking change with MSVC standard-conforming coroutines.)
96+
REQUIRE(consumed <= sync_usage);
97+
#else
98+
// MSVC standard-conforming coroutines (as well as gcc and clang coroutines)
99+
// support "bool await_suspend" just fine.
100+
REQUIRE(consumed == 0);
69101
#endif
70102
}
71103
}
72104
TEST_CASE("await_completed_await")
73105
{
74106
SyncCompletion().get();
75107
}
108+
109+
TEST_CASE("apartment_context_nop")
110+
{
111+
TestApartmentContextNop().get();
112+
}

test/test_cpp20/await_completed.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,22 @@ namespace
3838
uintptr_t consumed = initial - approximate_stack_pointer();
3939
REQUIRE(consumed == 0);
4040
}
41+
42+
IAsyncAction TestApartmentContextNop()
43+
{
44+
// co_await the same apartment and confirm that stack does not grow.
45+
winrt::apartment_context same_context;
46+
uintptr_t initial = approximate_stack_pointer();
47+
co_await same_context;
48+
uintptr_t consumed = initial - approximate_stack_pointer();
49+
REQUIRE(consumed == 0);
50+
}
4151
}
4252
TEST_CASE("await_completed_await")
4353
{
4454
SyncCompletion().get();
55+
}
56+
TEST_CASE("apartment_context_nop")
57+
{
58+
TestApartmentContextNop().get();
4559
}

0 commit comments

Comments
 (0)