forked from microsoft/cppwinrt
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathawait_adapter.cpp
More file actions
171 lines (140 loc) · 5.71 KB
/
await_adapter.cpp
File metadata and controls
171 lines (140 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#include "pch.h"
#include "winrt/Windows.System.h"
using namespace std::literals;
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::System;
namespace
{
bool is_sta()
{
APTTYPE type;
APTTYPEQUALIFIER qualifier;
check_hresult(CoGetApartmentType(&type, &qualifier));
return (type == APTTYPE_STA) || (type == APTTYPE_MAINSTA);
}
static handle signal{ CreateEventW(nullptr, false, false, nullptr) };
IAsyncAction OtherForegroundAsync(DispatcherQueue dispatcher)
{
// Simple coroutine that completes on the specified STA thread.
co_await resume_foreground(dispatcher);
}
IAsyncAction OtherBackgroundAsync()
{
// Simple coroutine that completes on some MTA thread.
co_await resume_background();
}
// Coroutine that completes on dispatcher1, while potentially blocking dispatcher2.
IAsyncAction ForegroundAsync(DispatcherQueue dispatcher1, DispatcherQueue dispatcher2)
{
REQUIRE(!is_sta());
co_await resume_foreground(dispatcher1);
REQUIRE(is_sta());
// This exercises one STA thread waiting on another thus one context callback
// completing on another.
uint32_t id = GetCurrentThreadId();
co_await OtherForegroundAsync(dispatcher2);
REQUIRE(id == GetCurrentThreadId());
// This Sleep() makes it more likely that the caller will actually suspend in await_suspend,
// so that the Completed handler triggers a resumption from the dispatcher1 thread.
Sleep(100);
}
fire_and_forget SignalFromForeground(DispatcherQueue dispatcher1)
{
REQUIRE(!is_sta());
co_await resume_foreground(dispatcher1);
REQUIRE(is_sta());
// Previously, we never got here because of a deadlock:
// The dispatcher1 thread was blocked waiting for ContextCallback to return,
// but the ContextCallback is waiting for this event to get signaled.
REQUIRE(SetEvent(signal.get()));
}
IAsyncAction BackgroundAsync(DispatcherQueue dispatcher1, DispatcherQueue dispatcher2)
{
// Switch to a background (MTA) thread.
co_await resume_background();
REQUIRE(!is_sta());
// This exercises one MTA thread waiting on another and just completing
// directly without the overhead of a context switch.
co_await OtherBackgroundAsync();
REQUIRE(!is_sta());
// Wait for a coroutine that completes on a the dispatcher1 thread (STA).
co_await ForegroundAsync(dispatcher1, dispatcher2);
// Resumption should automatically switch to a background (MTA) thread
// without blocking the Completed handler (which would in turn block the dispatcher1 thread).
REQUIRE(!is_sta());
// Attempt to signal from the dispatcher1 thread under the assumption
// that the dispatcher1 thread is not blocked.
SignalFromForeground(dispatcher1);
// Block the background (MTA) thread indefinitely until the signal is raised.
// Previously this would hang because the signal never got raised.
REQUIRE(WAIT_OBJECT_0 == WaitForSingleObject(signal.get(), INFINITE));
}
}
#if defined(__clang__) && defined(_MSC_VER) && (defined(_M_IX86) || defined(__i386__))
// FIXME: Test is known to segfault on x86 when built with Clang.
TEST_CASE("await_adapter", "[.clang-crash]")
#else
TEST_CASE("await_adapter")
#endif
{
auto controller1 = DispatcherQueueController::CreateOnDedicatedThread();
auto controller2 = DispatcherQueueController::CreateOnDedicatedThread();
BackgroundAsync(controller1.DispatcherQueue(), controller2.DispatcherQueue()).get();
controller1.ShutdownQueueAsync().get();
controller2.ShutdownQueueAsync().get();
}
namespace
{
IAsyncAction OtherBackgroundDelayAsync()
{
// Simple coroutine that completes on some MTA thread after a brief delay
// to ensure that the caller has suspended.
co_await resume_after(100ms);
}
IAsyncAction AgileAsync(DispatcherQueue dispatcher)
{
// Switch to the STA.
co_await resume_foreground(dispatcher);
REQUIRE(is_sta());
// Ask for agile resumption of a coroutine that finishes on a background thread.
// Add a 100ms delay to ensure we suspend.
co_await resume_agile(OtherBackgroundDelayAsync());
// We should be on the background thread now.
REQUIRE(!is_sta());
}
}
TEST_CASE("await_adapter_agile")
{
auto controller = DispatcherQueueController::CreateOnDedicatedThread();
auto dispatcher = controller.DispatcherQueue();
AgileAsync(dispatcher).get();
controller.ShutdownQueueAsync().get();
}
namespace
{
IAsyncAction AgileAsyncVariable(DispatcherQueue dispatcher)
{
// Switch to the STA.
co_await resume_foreground(dispatcher);
REQUIRE(is_sta());
// Ask for agile resumption of a coroutine that finishes on a background thread.
// Add a 100ms delay to ensure we suspend. Store the resume_agile in a variable
// and await the variable.
auto op = resume_agile(OtherBackgroundDelayAsync());
co_await op;
// We should be on the background thread now.
REQUIRE(!is_sta());
// Second attempt to await the op should fail cleanly.
REQUIRE_THROWS_AS(co_await op, hresult_illegal_delegate_assignment);
// We should still be on the background thread.
REQUIRE(!is_sta());
}
}
TEST_CASE("await_adapter_agile_variable")
{
auto controller = DispatcherQueueController::CreateOnDedicatedThread();
auto dispatcher = controller.DispatcherQueue();
AgileAsyncVariable(dispatcher).get();
controller.ShutdownQueueAsync().get();
}