forked from microsoft/cppwinrt
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdisconnected.cpp
More file actions
220 lines (179 loc) · 5.62 KB
/
Copy pathdisconnected.cpp
File metadata and controls
220 lines (179 loc) · 5.62 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
#include "pch.h"
#include <ctxtcall.h>
using namespace std::literals;
using namespace winrt;
using namespace Windows::Foundation;
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;
}
}
TEST_CASE("disconnected,handler")
{
{
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);
}
{
auto async = Action();
async.Completed([](auto&&...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
}
{
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);
}
{
auto async = Operation();
async.Completed([](auto&&...)
{
throw hresult_error(RPC_E_DISCONNECTED);
});
}
{
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);
}
}
// 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(void* event_handle) : m_awaited(event_handle) {}
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;
// Tell the test to disconnect the IAsyncAction, which simulates the server crash.
SetEvent(m_awaited);
}
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;
HANDLE m_awaited;
};
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>(signal.get());
});
auto result = [](IAsyncAction action) -> IAsyncAction
{
co_await action;
}(action.get());
REQUIRE_THROWS_MATCHES(result.get(), hresult_error, holds_hresult(RPC_E_DISCONNECTED));
}