-
Notifications
You must be signed in to change notification settings - Fork 275
Expand file tree
/
Copy pathdisconnected.cpp
More file actions
297 lines (248 loc) · 8.04 KB
/
disconnected.cpp
File metadata and controls
297 lines (248 loc) · 8.04 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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#include "pch.h"
#include <ctxtcall.h>
#include <winrt/Windows.System.h>
using namespace std::literals;
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::System;
namespace
{
IAsyncAction Action()
{
co_return;
}
IAsyncActionWithProgress<int> ActionProgress()
{
co_await 500ms;
auto progress = co_await get_progress_token();
progress(123);
co_return;
}
IAsyncOperation<int> Operation()
{
co_return 123;
}
IAsyncOperationWithProgress<int, int> OperationProgress()
{
co_await 500ms;
auto progress = co_await get_progress_token();
progress(123);
co_return 123;
}
// Exclude on mingw-w64 to suppress `-Wunused-function`
#if !defined(__MINGW32__)
bool is_mta()
{
APTTYPE type;
APTTYPEQUALIFIER qualifier;
check_hresult(CoGetApartmentType(&type, &qualifier));
return type == APTTYPE_MTA;
}
#endif
}
TEST_CASE("disconnected,handler,1")
{
event<EventHandler<int>> source;
source.add([](auto...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
auto token = source.add([](auto...)
{
throw hresult_error(E_INVALIDARG);
});
// Should have two delegates
REQUIRE(source);
// Should lose the disconnected delegate
source(nullptr, 123);
REQUIRE(source);
// Fire the remaining delegate
source(nullptr, 123);
REQUIRE(source);
// Remove the final delegate
source.remove(token);
// No more delegates
REQUIRE(!source);
source(nullptr, 123);
}
TEST_CASE("disconnected,handler,2")
{
auto async = Action();
async.Completed([](auto&&...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
}
TEST_CASE("disconnected,handler,3")
{
auto async = ActionProgress();
handle signal{ CreateEventW(nullptr, true, false, nullptr) };
async.Progress([](auto&&...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
async.Completed([&](auto&&...)
{
SetEvent(signal.get());
throw hresult_error(RPC_E_DISCONNECTED);
});
WaitForSingleObject(signal.get(), INFINITE);
// Give some time for to_hresult() to complete.
Sleep(500);
}
TEST_CASE("disconnected,handler,4")
{
auto async = Operation();
async.Completed([](auto&&...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
}
TEST_CASE("disconnected,handler,5")
{
auto async = OperationProgress();
handle signal{ CreateEventW(nullptr, true, false, nullptr) };
async.Progress([](auto&&...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
async.Completed([&](auto&&...)
{
SetEvent(signal.get());
throw hresult_error(RPC_E_DISCONNECTED);
});
WaitForSingleObject(signal.get(), INFINITE);
// Give some time for to_hresult() to complete.
Sleep(500);
}
// Custom action to simulate an out-of-process server that crashes before it can complete.
struct non_agile_abandoned_action : implements<non_agile_abandoned_action, IAsyncAction, IAsyncInfo, non_agile>
{
non_agile_abandoned_action(delegate<> disconnect) : m_disconnect(disconnect) {}
static fire_and_forget final_release(std::unique_ptr<non_agile_abandoned_action> /*self*/)
{
// The C++/WinRT m_handler is agile but not context-aware,
// so we need to make sure to release it from the context it
// was created from, which for this particular test is the MTA.
co_await resume_background();
// Now we can destruct.
}
void Completed(AsyncActionCompletedHandler const& handler) {
m_handler = handler;
m_disconnect();
}
auto Completed() { return m_handler; }
void GetResults() {}
auto Id() { return 0U; }
auto Status() { return AsyncStatus::Completed; }
auto ErrorCode() { return hresult(0); }
void Cancel() {}
void Close() {}
AsyncActionCompletedHandler m_handler;
delegate<> m_disconnect;
};
// Not yet buildable on mingw-w64.
// Missing CLSID_ContextSwitcher, IID_ICallbackWithNoReentrancyToApplicationSTA
// and __uuidof(IContextCallback). Also, the lambda needs to have __stdcall
// specified on it but there is a Clang crash bug blocking this:
// https://github.com/llvm/llvm-project/issues/58366
#if !defined(__MINGW32__)
namespace
{
template<typename TLambda>
void InvokeInContext(IContextCallback* context, TLambda&& lambda)
{
ComCallData data;
data.pUserDefined = λ
check_hresult(context->ContextCallback([](ComCallData* data) -> HRESULT
{
auto& lambda = *reinterpret_cast<TLambda*>(data->pUserDefined);
lambda();
return S_OK;
}, &data, IID_ICallbackWithNoReentrancyToApplicationSTA, 5, nullptr));
}
fire_and_forget disconnect_on_signal(com_ptr<IContextCallback> context, void* signal)
{
co_await resume_on_signal(signal);
InvokeInContext(context.get(), []()
{
// This disconnects the IAsyncAction, simulating a server crash.
CoDisconnectContext(INFINITE);
});
}
}
struct holds_hresult : public Catch::MatcherBase<hresult_error>
{
holds_hresult(hresult value) : expected(value) {}
hresult expected;
bool match(hresult_error const& e) const override
{
return e.code() == expected;
}
virtual std::string describe() const override
{
return "is code " + std::to_string(expected.value);
}
};
TEST_CASE("disconnected,action")
{
auto private_context = create_instance<IContextCallback>(CLSID_ContextSwitcher);
handle signal{ CreateEventW(nullptr, true, false, nullptr) };
disconnect_on_signal(private_context, signal.get());
agile_ref<IAsyncAction> action;
InvokeInContext(private_context.get(), [&]()
{
action = make<non_agile_abandoned_action>([&]{ SetEvent(signal.get()); });
});
auto result = [](IAsyncAction action) -> IAsyncAction
{
co_await action;
}(action.get());
REQUIRE_THROWS_MATCHES(result.get(), hresult_error, holds_hresult(RPC_E_DISCONNECTED));
}
#if defined(__clang__) && (defined(_M_IX86) || defined(__i386__))
// FIXME: Test is known to crash with exit code 0xc000070a on x86 when built with Clang.
TEST_CASE("disconnected,double", "[.clang-crash]")
#else
TEST_CASE("disconnected,double")
#endif
{
// The double-disconnect case, where the IAsyncAction disconnects,
// and tries to return to the original context, but it too has disconnected!
auto test = []() -> IAsyncAction
{
auto private_context = create_instance<IContextCallback>(CLSID_ContextSwitcher);
handle signal{ CreateEventW(nullptr, true, false, nullptr) };
disconnect_on_signal(private_context, signal.get());
// Create an STA thread that we will destroy while awaiting.
auto controller = DispatcherQueueController::CreateOnDedicatedThread();
agile_ref<IAsyncAction> action;
InvokeInContext(private_context.get(), [&]()
{
action = make<non_agile_abandoned_action>([&]() -> fire_and_forget
{
// Get off the DispatcherQueue thread.
co_await resume_background();
// Destroy the DispatcherQueue, so the co_await has nowhere to return to.
co_await controller.ShutdownQueueAsync();
// Now set the event to force the action to disconnect.
SetEvent(signal.get());
});
});
// Go to our STA thread.
co_await resume_foreground(controller.DispatcherQueue());
HRESULT hr = S_OK;
try
{
co_await action.get();
}
catch (...)
{
hr = to_hresult();
REQUIRE(is_mta());
}
REQUIRE(FAILED(hr));
}();
test.get();
}
#endif