master
cpp 356 lines 11.1 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 Lifetime.cpp
8
9 Abstract:
10
11 This file contains function definitions around client lifetime.
12
13 --*/
14
15 #include "precomp.h"
16 #include "Lifetime.h"
17
18 #define RETRY_TIMER_PERIOD (60 * 1000)
19 #define RETRY_TIMER_WINDOW (1000)
20
21 static bool IsSameProcess(_In_ HANDLE process1, _In_ HANDLE process2)
22 {
23 const DWORD pid1 = GetProcessId(process1);
24 THROW_LAST_ERROR_IF(pid1 == 0);
25
26 const DWORD pid2 = GetProcessId(process2);
27 THROW_LAST_ERROR_IF(pid2 == 0);
28
29 return pid1 == pid2;
30 }
31
32 LifetimeManager::LifetimeManager() : m_nextClientKey(0)
33 {
34 }
35
36 LifetimeManager::~LifetimeManager()
37 {
38 if (wil::ProcessShutdownInProgress())
39 {
40 return;
41 }
42
43 ClearCallbacks();
44 }
45
46 void LifetimeManager::ClearCallbacks()
47 {
48 // Synchronization with the termination callbacks is tricky and must avoid:
49 // (1) deadlocks
50 // (2) concurrent modification of the callback list
51 // (3) closing the process handle while the wait is still pending
52 //
53 // The strategy is to:
54 // 1. Take the lock
55 // 2. Move all clients to a local list
56 // 3. Release the lock
57 // 4. Wait for any pending callbacks (when the local vectors go out of
58 // scope).
59 std::vector<ClientCallback> callbacks;
60 std::vector<wil::unique_threadpool_wait> waits;
61 {
62 std::lock_guard<std::mutex> lock(m_lock);
63
64 // Set m_exiting to make sure no new callbacks can be scheduled.
65 m_exiting = true;
66
67 for (auto& callback : m_callbackList)
68 {
69 for (auto& child : callback.clientProcesses)
70 {
71 waits.emplace_back(child.terminationWait.release());
72 }
73
74 callbacks.emplace_back(std::move(callback));
75 }
76
77 m_callbackList.clear();
78 }
79 }
80
81 ULONG64 LifetimeManager::GetRegistrationId()
82 {
83 std::lock_guard<std::mutex> lock(m_lock);
84 THROW_IF_FAILED(ULong64Add(m_nextClientKey, 1, &m_nextClientKey));
85
86 return m_nextClientKey;
87 }
88
89 bool LifetimeManager::IsAnyProcessRegistered(_In_ ULONG64 ClientKey)
90 {
91 std::lock_guard<std::mutex> lock(m_lock);
92 const auto client = _FindClient(ClientKey);
93 return (client != m_callbackList.end());
94 }
95
96 void LifetimeManager::RegisterCallback(_In_ ULONG64 ClientKey, _In_ const std::function<bool(void)>& Callback, _In_opt_ HANDLE ClientProcess, _In_ DWORD TimeoutMs)
97 {
98 std::lock_guard<std::mutex> lock(m_lock);
99 auto client = _FindClient(ClientKey);
100 if (client == m_callbackList.end())
101 {
102 ClientCallback newClient{};
103 newClient.callback = std::move(Callback);
104 newClient.clientKey = ClientKey;
105 newClient.timeout = TimeoutMs;
106 newClient.CreateTimer(s_OnTimeout, this);
107 if (!ARGUMENT_PRESENT(ClientProcess))
108 {
109 newClient.SetTimer(TimeoutMs);
110 }
111
112 m_callbackList.emplace_back(std::move(newClient));
113 client = _FindClient(ClientKey);
114 }
115 else
116 {
117 // If a client was found, update the callback and timeout and cancel
118 // any pending timer.
119 client->callback = std::move(Callback);
120 client->timeout = TimeoutMs;
121 if (ARGUMENT_PRESENT(ClientProcess))
122 {
123 client->CancelTimer();
124 }
125 }
126
127 WI_ASSERT(client != m_callbackList.end());
128
129 if (ARGUMENT_PRESENT(ClientProcess))
130 {
131 const auto proc = client->FindProcess(ClientProcess);
132 if (proc == client->clientProcesses.end())
133 {
134 OwnedProcess newProcess{};
135 newProcess.process.reset(wsl::windows::common::wslutil::DuplicateHandle(ClientProcess));
136 newProcess.InitializeListenForTermination(s_OnClientProcessTerminated, this);
137 client->clientProcesses.emplace_back(std::move(newProcess));
138 client->clientProcesses.back().ListenForTermination();
139 }
140 }
141 }
142
143 bool LifetimeManager::RemoveCallback(_In_ ULONG64 ClientKey)
144 {
145 bool callbackFound = false;
146 ClientCallback oldClient{};
147 std::lock_guard<std::mutex> lock(m_lock);
148 const auto client = _FindClient(ClientKey);
149 if (client != m_callbackList.end())
150 {
151 oldClient = std::move(*client);
152 m_callbackList.erase(client);
153 callbackFound = true;
154 }
155
156 return callbackFound;
157 }
158
159 VOID CALLBACK LifetimeManager::s_OnClientProcessTerminated(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_WAIT Wait, _In_ TP_WAIT_RESULT WaitResult)
160 {
161 UNREFERENCED_PARAMETER(WaitResult);
162 WI_ASSERT(WaitResult == WAIT_OBJECT_0);
163
164 try
165 {
166 const auto manager = static_cast<LifetimeManager*>(Context);
167 ClientCallback clientLocal{};
168 wil::unique_threadpool_wait previousCallbackWait{};
169
170 // Search for a callback with a matching threadpool wait.
171 {
172 std::list<OwnedProcess>::iterator proc;
173 std::lock_guard<std::mutex> lock(manager->m_lock);
174 const auto client = std::find_if(manager->m_callbackList.begin(), manager->m_callbackList.end(), [&](ClientCallback& c) {
175 proc = std::find_if(c.clientProcesses.begin(), c.clientProcesses.end(), [&Wait](const OwnedProcess& p) {
176 return (p.terminationWait.get() == Wait);
177 });
178
179 return (proc != c.clientProcesses.end());
180 });
181
182 if (client != manager->m_callbackList.end())
183 {
184 previousCallbackWait.reset(proc->terminationWait.release());
185 previousCallbackWait.swap(manager->m_lastCallbackWait);
186
187 // If this is the last client process, execute the callback
188 // or queue a timer if a timeout was specified.
189 //
190 // N.B. The callback must be executed after dropping the lock.
191 client->clientProcesses.erase(proc);
192 if (client->clientProcesses.empty())
193 {
194 if (client->timeout == 0)
195 {
196 clientLocal = std::move(*client);
197 manager->m_callbackList.erase(client);
198 }
199 else
200 {
201 client->SetTimer(client->timeout);
202 }
203 }
204 }
205 }
206
207 // Callbacks that have a zero timeout must return success because they
208 // are not retried.
209 if (clientLocal.callback)
210 {
211 WI_VERIFY(clientLocal.callback());
212 }
213 }
214 CATCH_LOG()
215 }
216
217 VOID CALLBACK LifetimeManager::s_OnTimeout(_Inout_ PTP_CALLBACK_INSTANCE, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER Timer)
218 {
219 try
220 {
221 const auto manager = static_cast<LifetimeManager*>(Context);
222 ClientCallback clientLocal;
223 wil::unique_threadpool_timer previousTimerWait{};
224
225 // Search for a callback with a matching timer.
226 {
227 std::lock_guard<std::mutex> lock(manager->m_lock);
228 const auto client =
229 std::find_if(manager->m_callbackList.begin(), manager->m_callbackList.end(), [Timer](const ClientCallback& c) {
230 return (Timer == c.timer.get());
231 });
232
233 if ((client != manager->m_callbackList.end()) && (client->clientProcesses.empty()))
234 {
235 clientLocal = std::move(*client);
236 manager->m_callbackList.erase(client);
237 }
238
239 // If we took ownership of the timer (moved into clientLocal), stash it so the
240 // destructor waits for this callback to finish before exiting. Otherwise this
241 // firing is a no-op (e.g., the entry was erased, the timer was replaced, or the
242 // matched entry still has live processes).
243 if (clientLocal.timer)
244 {
245 clientLocal.CancelTimer();
246 previousTimerWait.reset(clientLocal.timer.release());
247 previousTimerWait.swap(manager->m_lastTimerWait);
248 }
249 }
250
251 // If a callback was found, execute it. If the callback succeeds the
252 // timer is cancelled. Otherwise, the callback is retried.
253 //
254 // N.B. The callback must be executed after dropping the lock.
255 if (clientLocal.callback)
256 {
257 if (!clientLocal.callback())
258 {
259 std::lock_guard<std::mutex> lock(manager->m_lock);
260
261 // Only re-queue the timer if not exiting (see ClearCallbacks())
262 if (!manager->m_exiting)
263 {
264 clientLocal.CreateTimer(s_OnTimeout, manager);
265 clientLocal.SetTimer(clientLocal.timeout);
266 manager->m_callbackList.emplace_back(std::move(clientLocal));
267 }
268 }
269 }
270 }
271 CATCH_LOG()
272 }
273
274 _Requires_lock_held_(m_lock)
275 std::list<LifetimeManager::ClientCallback>::iterator LifetimeManager::_FindClient(_In_ ULONG64 ClientKey)
276 {
277 return std::find_if(m_callbackList.begin(), m_callbackList.end(), [&ClientKey](const ClientCallback& c) {
278 return (ClientKey == c.clientKey);
279 });
280 }
281
282 LifetimeManager::OwnedProcess::OwnedProcess()
283 {
284 }
285
286 LifetimeManager::OwnedProcess::~OwnedProcess()
287 {
288 }
289
290 LifetimeManager::OwnedProcess::OwnedProcess(OwnedProcess&& other) noexcept
291 {
292 *this = std::move(other);
293 }
294
295 void LifetimeManager::OwnedProcess::operator=(OwnedProcess&& source) noexcept
296 {
297 process = std::move(source.process);
298 terminationWait = std::move(source.terminationWait);
299 }
300
301 void LifetimeManager::OwnedProcess::InitializeListenForTermination(_In_ PTP_WAIT_CALLBACK Callback, _In_ PVOID Context)
302 {
303 terminationWait.reset(CreateThreadpoolWait(Callback, Context, nullptr));
304 THROW_LAST_ERROR_IF(!terminationWait);
305 }
306
307 void LifetimeManager::OwnedProcess::ListenForTermination() const
308 {
309 SetThreadpoolWait(terminationWait.get(), process.get(), nullptr);
310 }
311
312 LifetimeManager::ClientCallback::ClientCallback()
313 {
314 }
315
316 LifetimeManager::ClientCallback::~ClientCallback()
317 {
318 }
319
320 LifetimeManager::ClientCallback::ClientCallback(ClientCallback&& other) noexcept
321 {
322 *this = std::move(other);
323 }
324
325 void LifetimeManager::ClientCallback::operator=(ClientCallback&& source) noexcept
326 {
327 timer = std::move(source.timer);
328 clientKey = source.clientKey;
329 callback = std::move(source.callback);
330 timeout = source.timeout;
331 clientProcesses = std::move(source.clientProcesses);
332 }
333
334 void LifetimeManager::ClientCallback::CancelTimer() const
335 {
336 SetThreadpoolTimer(timer.get(), nullptr, 0, 0);
337 }
338
339 void LifetimeManager::ClientCallback::CreateTimer(_In_ PTP_TIMER_CALLBACK Callback, _In_ PVOID Context)
340 {
341 timer.reset(CreateThreadpoolTimer(Callback, Context, nullptr));
342 THROW_LAST_ERROR_IF(!timer);
343 }
344
345 std::list<LifetimeManager::OwnedProcess>::iterator LifetimeManager::ClientCallback::FindProcess(_In_ HANDLE Process)
346 {
347 return std::find_if(clientProcesses.begin(), clientProcesses.end(), [&Process](const OwnedProcess& p) {
348 return IsSameProcess(Process, p.process.get());
349 });
350 }
351
352 void LifetimeManager::ClientCallback::SetTimer(_In_ DWORD DueTimeMs) const
353 {
354 FILETIME dueTime = wil::filetime::from_int64(static_cast<ULONGLONG>(-1 * wil::filetime_duration::one_millisecond * DueTimeMs));
355 SetThreadpoolTimer(timer.get(), &dueTime, RETRY_TIMER_PERIOD, RETRY_TIMER_WINDOW);
356 }