master
h 448 lines 20.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCSession.h
8
9 Abstract:
10
11 TODO
12
13 --*/
14
15 #pragma once
16
17 #include "wslc.h"
18 #include "WSLCCompat.h"
19 #include "WSLCVirtualMachine.h"
20 #include "WSLCContainer.h"
21 #include "WSLCIdleState.h"
22 #include "WSLCVolumes.h"
23 #include "WSLCSessionRuntime.h"
24 #include "WSLCNetworkMetadata.h"
25 #include "DockerEventTracker.h"
26 #include "DockerHTTPClient.h"
27 #include "EventStore.h"
28 #include "IORelay.h"
29 #include <atomic>
30 #include <list>
31 #include <optional>
32 #include <unordered_map>
33
34 namespace wsl::windows::service::wslc {
35
36 class WSLCSession;
37
38 class UserHandle
39 {
40 NON_COPYABLE(UserHandle);
41
42 public:
43 UserHandle(WSLCSession& Session, HANDLE handle);
44 UserHandle(UserHandle&& Other);
45
46 ~UserHandle();
47
48 UserHandle& operator=(UserHandle&& Other);
49
50 HANDLE Get() const noexcept;
51 void Reset();
52
53 private:
54 WSLCSession* m_session{};
55 HANDLE m_handle{};
56 };
57
58 class UserCOMCallback
59 {
60 NON_COPYABLE(UserCOMCallback);
61
62 public:
63 UserCOMCallback(WSLCSession& Session) noexcept;
64 UserCOMCallback(UserCOMCallback&& Other) noexcept;
65
66 ~UserCOMCallback() noexcept;
67
68 UserCOMCallback& operator=(UserCOMCallback&& Other) noexcept;
69 void Reset() noexcept;
70
71 private:
72 WSLCSession* m_session{};
73 DWORD m_threadId{};
74 };
75
76 //
77 // WSLCSession - Implements IWSLCSession for container management.
78 // Runs in a per-user COM server process for security isolation.
79 // The SYSTEM service passes an IWSLCVirtualMachineFactory to Initialize(); the VM is created
80 // lazily on first use and may be torn down when idle and recreated on demand.
81 //
82 class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession
83 : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRtClassicComMix>, IWSLCSession, IWSLCCompatSession, IFastRundown, ISupportErrorInfo>
84 {
85 // WSLCContainer::Delete acquires a VmLease to keep the VM alive (and block idle
86 // teardown) for the duration of a container deletion.
87 friend class WSLCContainer;
88
89 public:
90 WSLCSession() : m_runtime(*this)
91 {
92 }
93
94 ~WSLCSession();
95
96 // Sets a callback invoked when this object is destroyed.
97 // Used by the COM server host to signal process exit.
98 void SetDestructionCallback(std::function<void()>&& callback);
99
100 // Type of m_crashDumpCallbacks. Exposed so CrashDumpSubscription can hold an iterator into
101 // it as an O(1) registration handle.
102 using CrashDumpCallbackList = std::list<wil::com_ptr<ICrashDumpCallback>>;
103
104 // IWSLCSession - initialization methods
105 IFACEMETHOD(GetProcessHandle)(_Out_ HANDLE* ProcessHandle) override;
106 IFACEMETHOD(Initialize)(
107 _In_ const WSLCSessionInitSettings* Settings,
108 _In_ IWSLCVirtualMachineFactory* VmFactory,
109 _In_ IWSLCPluginNotifier* PluginNotifier,
110 _In_opt_ IWarningCallback* WarningCallback) override;
111
112 IFACEMETHOD(GetId)(_Out_ ULONG* Id) override;
113 IFACEMETHOD(GetDisplayName)(_Out_ LPWSTR* DisplayName) override;
114 IFACEMETHOD(GetState)(_Out_ WSLCSessionState* State) override;
115 IFACEMETHOD(GetTerminationEvent)(_Out_ HANDLE* Event) override;
116 IFACEMETHOD(GetTerminationReason)(_Out_ WSLCVirtualMachineTerminationReason* Reason, _Out_ LPWSTR* Details) override;
117
118 // Event streaming. Opens a stream object that yields matching events one at a time via
119 // IWSLCEventStream::GetNext.
120 IFACEMETHOD(GetEvents)(
121 _In_ LONGLONG SinceTime,
122 _In_ LONGLONG UntilTime,
123 _In_reads_opt_(FiltersCount) const WSLCFilter* Filters,
124 _In_ ULONG FiltersCount,
125 _Outptr_ IWSLCEventStream** Stream) override;
126
127 // Image management.
128 IFACEMETHOD(PullImage)(
129 _In_ LPCSTR Image,
130 _In_opt_ LPCSTR RegistryAuthenticationInformation,
131 _In_opt_ IProgressCallback* ProgressCallback,
132 _In_opt_ IWarningCallback* WarningCallback) override;
133 IFACEMETHOD(BuildImage)(_In_ const WSLCBuildImageOptions* Options, _In_opt_ IProgressCallback* ProgressCallback, _In_opt_ HANDLE CancelEvent) override;
134 IFACEMETHOD(LoadImage)(
135 _In_ const WSLCHandle ImageHandle,
136 _In_ ULONGLONG ContentLength,
137 _In_opt_ IWarningCallback* WarningCallback,
138 _In_opt_ IImageLoadCallback* LoadCallback) override;
139 IFACEMETHOD(ImportImage)(
140 _In_ const WSLCHandle ImageHandle,
141 _In_opt_ LPCSTR ImageName,
142 _In_ ULONGLONG ContentLength,
143 _In_opt_ IWarningCallback* WarningCallback,
144 _Out_ LPSTR* ImageId) override;
145 IFACEMETHOD(SaveImage)(_In_ WSLCHandle OutputHandle, _In_ LPCSTR ImageNameOrID, _In_ IProgressCallback* ProgressCallback, _In_opt_ HANDLE CancelEvent) override;
146 IFACEMETHOD(SaveImages)(_In_ WSLCHandle OutputHandle, _In_ const WSLCStringArray* ImageNames, _In_ IProgressCallback* ProgressCallback, _In_opt_ HANDLE CancelEvent) override;
147 IFACEMETHOD(ListImages)(_In_opt_ const WSLCListImagesOptions* Options, _Out_ WSLCImageInformation** Images, _Out_ ULONG* Count) override;
148 IFACEMETHOD(DeleteImage)(_In_ const WSLCDeleteImageOptions* Options, _Out_ WSLCDeletedImageInformation** DeletedImages, _Out_ ULONG* Count) override;
149 IFACEMETHOD(TagImage)(_In_ const WSLCTagImageOptions* Options) override;
150 IFACEMETHOD(PushImage)(
151 _In_ LPCSTR Image,
152 _In_ LPCSTR RegistryAuthenticationInformation,
153 _In_opt_ IProgressCallback* ProgressCallback,
154 _In_opt_ IWarningCallback* WarningCallback) override;
155 IFACEMETHOD(InspectImage)(_In_ LPCSTR ImageNameOrId, _Out_ LPSTR* Output) override;
156 IFACEMETHOD(Authenticate)(_In_ LPCSTR ServerAddress, _In_ LPCSTR Username, _In_ LPCSTR Password, _Out_ LPSTR* IdentityToken) override;
157 IFACEMETHOD(PruneImages)(
158 _In_opt_ const WSLCFilter* Filters,
159 _In_ ULONG FiltersCount,
160 _Out_ WSLCDeletedImageInformation** DeletedImages,
161 _Out_ ULONG* DeletedImagesCount,
162 _Out_ ULONGLONG* SpaceReclaimed) override;
163
164 // Container management.
165 IFACEMETHOD(CreateContainer)(_In_ const WSLCContainerOptions* Options, _In_opt_ IWarningCallback* WarningCallback, _Out_ IWSLCContainer** Container) override;
166 IFACEMETHOD(OpenContainer)(_In_ LPCSTR Id, _In_ IWSLCContainer** Container) override;
167 IFACEMETHOD(BeginContainerOperation)(_Outptr_ IUnknown** Operation) override;
168 IFACEMETHOD(ListContainers)(
169 _In_opt_ const WSLCListContainersOptions* Options,
170 _Out_ WSLCContainerEntry** Containers,
171 _Out_ ULONG* Count,
172 _Out_ WSLCContainerPortMapping** Ports,
173 _Out_ ULONG* PortsCount) override;
174 IFACEMETHOD(PruneContainers)(_In_opt_ const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCPruneContainersResults* Result) override;
175
176 // VM management.
177 IFACEMETHOD(CreateRootNamespaceProcess)(
178 _In_ LPCSTR Executable,
179 _In_ const WSLCProcessOptions* Options,
180 _In_ ULONG TtyRows,
181 _In_ ULONG TtyColumns,
182 _In_ BOOL AcquireVmLease,
183 _Out_ IWSLCProcess** VirtualMachine,
184 _Out_ int* Errno) override;
185
186 // Disk management.
187 IFACEMETHOD(FormatVirtualDisk)(_In_ LPCWSTR Path) override;
188
189 // Volume management.
190 IFACEMETHOD(CreateVolume)(_In_ const WSLCVolumeOptions* Options, _Out_ WSLCVolumeInformation* VolumeInfo) override;
191 IFACEMETHOD(DeleteVolume)(_In_ LPCSTR Name) override;
192 IFACEMETHOD(ListVolumes)
193 (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ LPSTR* Output) override;
194 IFACEMETHOD(InspectVolume)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override;
195 IFACEMETHOD(PruneVolumes)
196 (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters,
197 _In_ ULONG FiltersCount,
198 _In_opt_ IWarningCallback* WarningCallback,
199 _Out_ WSLCVolumeName** Volumes,
200 _Out_ ULONG* VolumesCount,
201 _Out_ ULONGLONG* SpaceReclaimed) override;
202
203 // Network management.
204 IFACEMETHOD(CreateNetwork)(_In_ const WSLCNetworkOptions* Options, _In_opt_ IWarningCallback* WarningCallback) override;
205 IFACEMETHOD(DeleteNetwork)(_In_ LPCSTR Name) override;
206 IFACEMETHOD(ListNetworks)
207 (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ LPSTR* Output) override;
208 IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override;
209 IFACEMETHOD(PruneNetworks)
210 (_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCNetworkName** Networks, _Out_ ULONG* NetworksCount)
211 override;
212
213 IFACEMETHOD(Terminate()) override;
214
215 IFACEMETHOD(RegisterCrashDumpCallback)(_In_ ICrashDumpCallback* Callback, _Out_ IUnknown** Subscription) override;
216
217 // Called by CrashDumpSubscription when its last reference is released. The iterator must
218 // have been returned by RegisterCrashDumpCallback against this session.
219 void RemoveCrashDumpCallback(CrashDumpCallbackList::iterator It) noexcept;
220
221 // ISupportErrorInfo
222 IFACEMETHOD(InterfaceSupportsErrorInfo)(_In_ REFIID riid) override;
223
224 // Testing.
225 IFACEMETHOD(MountWindowsFolder)(_In_ LPCWSTR WindowsPath, _In_ LPCSTR LinuxPath, _In_ BOOL ReadOnly, _In_ BOOL AcquireVmLease) override;
226 IFACEMETHOD(UnmountWindowsFolder)(_In_ LPCSTR LinuxPath, _In_ BOOL AcquireVmLease) override;
227 IFACEMETHOD(MapVmPort)(_In_ int Family, _In_ unsigned short WindowsPort, _In_ unsigned short LinuxPort) override;
228 IFACEMETHOD(UnmapVmPort)(_In_ int Family, _In_ unsigned short WindowsPort, _In_ unsigned short LinuxPort) override;
229 IFACEMETHOD(TriggerIdleTermination)(_Out_ BOOL* WasAlreadyIdle) override;
230
231 // IWSLCCompatSession - converts the WSLCCompat types to the wslc.idl types and forwards to the methods above.
232 // Methods that have an identical signature in both interfaces (Terminate, DeleteVolume, Authenticate,
233 // GetTerminationReason) are served by the single existing override and require no additional code here.
234 IFACEMETHOD(PullImage)(
235 _In_ LPCSTR Image,
236 _In_opt_ LPCSTR RegistryAuthenticationInformation,
237 _In_opt_ IWSLCCompatProgressCallback* ProgressCallback,
238 _In_opt_ IWSLCCompatWarningCallback* WarningCallback) override;
239 IFACEMETHOD(LoadImage)(
240 _In_ WSLCCompatHandle ImageHandle,
241 _In_opt_ IWSLCCompatProgressCallback* ProgressCallback,
242 _In_ ULONGLONG ContentLength,
243 _In_opt_ IWSLCCompatWarningCallback* WarningCallback) override;
244 IFACEMETHOD(ImportImage)(
245 _In_ WSLCCompatHandle ImageHandle,
246 _In_opt_ LPCSTR ImageName,
247 _In_opt_ IWSLCCompatProgressCallback* ProgressCallback,
248 _In_ ULONGLONG ContentLength,
249 _In_opt_ IWSLCCompatWarningCallback* WarningCallback,
250 _Out_ LPSTR* ImageId) override;
251 IFACEMETHOD(ListImages)(_In_opt_ const WSLCCompatListImagesOptions* Options, _Out_ WSLCCompatImageInformation** Images, _Out_ ULONG* Count) override;
252 IFACEMETHOD(DeleteImage)(_In_ const WSLCCompatDeleteImageOptions* Options, _Out_ WSLCCompatDeletedImageInformation** DeletedImages, _Out_ ULONG* Count) override;
253 IFACEMETHOD(TagImage)(_In_ const WSLCCompatTagImageOptions* Options) override;
254 IFACEMETHOD(PushImage)(
255 _In_ LPCSTR Image,
256 _In_ LPCSTR RegistryAuthenticationInformation,
257 _In_opt_ IWSLCCompatProgressCallback* ProgressCallback,
258 _In_opt_ IWSLCCompatWarningCallback* WarningCallback) override;
259 IFACEMETHOD(CreateContainer)(
260 _In_ const WSLCCompatContainerOptions* Options,
261 _In_opt_ IWSLCCompatWarningCallback* WarningCallback,
262 _Out_ IWSLCCompatContainer** Container) override;
263 IFACEMETHOD(OpenContainer)(_In_ LPCSTR NameOrId, _Out_ IWSLCCompatContainer** Container) override;
264 IFACEMETHOD(CreateVolume)(_In_ const WSLCCompatVolumeOptions* Options, _Out_ WSLCCompatVolumeInformation* VolumeInfo) override;
265 IFACEMETHOD(RegisterCrashDumpCallback)(_In_ IWSLCCompatCrashDumpCallback* Callback, _Out_ IUnknown** Subscription) override;
266
267 common::io::MultiHandleWait CreateIOContext(HANDLE CancelHandle = nullptr);
268
269 UserHandle OpenUserHandle(WSLCHandle Handle);
270 void ReleaseUserHandle(HANDLE Handle);
271
272 UserCOMCallback RegisterUserCOMCallback();
273 void UnregisterUserCOMCallback(DWORD ThreadId);
274
275 ULONG Id() const noexcept
276 {
277 return m_id;
278 }
279
280 bool WaitForEventOrSessionTerminating(HANDLE Event, std::chrono::milliseconds Timeout) const;
281
282 // Shared idle-termination state. Exposed so VM-scoped objects (e.g. running containers via
283 // WSLCContainerImpl's ActivityRef) can hold an activity reference for their lifetime without
284 // keeping the session object itself alive.
285 std::shared_ptr<IdleState> IdleStateShared() const noexcept
286 {
287 return m_runtime.IdleStateShared();
288 }
289
290 WSLCSessionRuntime& Runtime() noexcept
291 {
292 return m_runtime;
293 }
294
295 const WSLCSessionRuntime& Runtime() const noexcept
296 {
297 return m_runtime;
298 }
299
300 // Creates an opaque activity token that holds a reference on this session's activity count for
301 // its lifetime, deferring idle teardown of the VM until every outstanding token is released.
302 // Used both for transient client operations (BeginContainerOperation) and to keep the VM alive
303 // for the lifetime of a process whose wrapper a client may keep (root-namespace and exec'd
304 // processes).
305 Microsoft::WRL::ComPtr<IUnknown> CreateActivityToken();
306
307 private:
308 ULONG m_id = 0;
309 void PersistSettings(const WSLCSessionInitSettings& Settings, PSID UserSid);
310
311 using VmLease = WSLCSessionRuntime::VmLease;
312 [[nodiscard]] VmLease AcquireLease(WSLCSessionRuntime::VmLeasePolicy Policy = WSLCSessionRuntime::VmLeasePolicy::Acquire);
313
314 // Maps the AcquireVmLease flag that plugin-reachable methods carry onto the lease policy. The
315 // service passes FALSE for every plugin-originated call: a plugin is never a reason to create a
316 // VM, so it is served by the running one -- including one committed to stopping -- or rejected.
317 [[nodiscard]] static constexpr WSLCSessionRuntime::VmLeasePolicy LeasePolicyFor(BOOL AcquireVmLease) noexcept
318 {
319 return AcquireVmLease ? WSLCSessionRuntime::VmLeasePolicy::Acquire : WSLCSessionRuntime::VmLeasePolicy::ExistingOnly;
320 }
321
322 __requires_lock_held(m_userHandlesLock) void CancelUserHandleIO();
323 __requires_lock_held(m_userCOMCallbacksLock) void CancelUserCOMCallbacks();
324
325 void CreateContainerImpl(const WSLCContainerOptions* Options, IWSLCContainer** Container);
326
327 // A create RPC hands the container off to the Docker event stream thread here and waits, so the
328 // container is committed and its create event recorded from that thread. Recording it from the RPC
329 // thread instead would let another container's event be recorded first and regress the event
330 // stream's timestamps.
331 struct PendingContainerCreate
332 {
333 wil::unique_event Completed{wil::EventOptions::ManualReset};
334 std::shared_ptr<WSLCContainerImpl> Container;
335 std::exception_ptr Exception;
336 };
337
338 __requires_lock_held(m_containersLock) std::shared_ptr<PendingContainerCreate> StartPendingCreate(std::shared_ptr<WSLCContainerImpl> Container);
339
340 __requires_lock_held(m_containersLock) void CompletePendingCreate(
341 const std::shared_ptr<PendingContainerCreate>& PendingCreate, std::exception_ptr Exception) noexcept;
342
343 void WaitForPendingCreateCompletion(const std::shared_ptr<PendingContainerCreate>& PendingCreate);
344
345 // Returns with the lock held once no create is in flight. Only one fits: the slot is unkeyed,
346 // because the container ID isn't known until Docker assigns it.
347 void WaitForConflictingCreateToComplete(std::unique_lock<std::mutex>& ContainersLock);
348
349 void OnContainerCreated(const std::string& ContainerId, std::int64_t Time) noexcept;
350
351 void ConfigureStorage(const WSLCSessionInitSettings& Settings, PSID UserSid);
352
353 void Ext4Format(const std::string& Device);
354 std::string InspectImageLockHeld(const std::string& Id);
355 void OnContainerDeleted(const WSLCContainerImpl* Container);
356
357 void OnCrashDumpWritten(const std::wstring& DumpPath, const std::string& ProcessName, ULONG Pid, ULONG Signal, ULONGLONG Timestamp);
358
359 void OnImageCreated(const std::string& ImageNameOrId) noexcept;
360
361 void OnImageDeleted(const std::string& ImageId) noexcept;
362
363 void OnContainerdExited();
364 void OnDockerdExited();
365 ServiceRunningProcess StartProcess(
366 const std::string& Executable, const std::vector<std::string>& Args, PCSTR LogSource, std::function<void()>&& ExitCallback);
367 void InstallTrustedRootCertificates();
368 void StartContainerd();
369 void StartDockerd();
370 int StopProcess(ServiceRunningProcess& Process, DWORD TerminateTimeoutMs, DWORD KillTimeoutMs);
371 std::optional<std::string> ImportImageImpl(
372 DockerHTTPClient::HTTPRequestContext& Request, const WSLCHandle ImageHandle, IImageLoadCallback* LoadCallback = nullptr);
373 void RecoverExistingContainers();
374 void RecoverExistingNetworks();
375
376 void SaveImageImpl(std::pair<uint32_t, wil::unique_socket>& RequestCodePair, WSLCHandle OutputHandle, HANDLE CancelEvent);
377 void StreamImageOperation(DockerHTTPClient::HTTPRequestContext& requestContext, LPCSTR Image, LPCSTR OperationName, IProgressCallback* ProgressCallback);
378
379 // The VM factory is a cross-process proxy supplied by the SYSTEM service at Initialize() time
380 // but first used later (on demand) from a different thread/apartment. A directly stored proxy
381 // would fail with RPC_E_WRONG_THREAD, so it is parked in the process Global Interface Table and
382 // re-fetched (re-marshalled into the calling apartment) each time a VM is created.
383 wil::com_ptr<IGlobalInterfaceTable> m_git;
384 DWORD m_vmFactoryGitCookie{};
385
386 WSLCSessionRuntime m_runtime;
387 std::wstring m_displayName;
388 std::wstring m_creatorProcessName;
389 std::filesystem::path m_storageVhdPath;
390
391 // N.B. The runtime lock must be acquired before acquiring m_containersLock or m_networksLock.
392 // These locks protect m_containers without requiring an exclusive hold on the runtime lock.
393 // This allows independent operations to proceed while container bookkeeping remains synchronized.
394 // WSLCVolumes has its own internal srwlock and does not require the runtime lock.
395 std::mutex m_containersLock;
396 std::unordered_map<std::string, std::shared_ptr<WSLCContainerImpl>> m_containers;
397 std::mutex m_networksLock;
398 std::unordered_map<std::string, NetworkEntry> m_networks;
399 wil::shared_event m_sessionTerminatingEvent{wil::EventOptions::ManualReset};
400 wil::shared_event m_sessionTerminatedEvent{wil::EventOptions::ManualReset};
401
402 WSLCVirtualMachineTerminationReason m_terminationReason{WSLCVirtualMachineTerminationReasonUnknown};
403 std::wstring m_terminationDetails;
404
405 // Persisted settings required to (re)create the VM on demand. The string fields point
406 // into the owned storage members below (or m_displayName) so they remain valid for the
407 // lifetime of the session.
408 WSLCSessionInitSettings m_settings{};
409 std::optional<std::wstring> m_settingsCreatorProcessName;
410 std::optional<std::wstring> m_settingsStoragePath;
411 std::optional<std::string> m_settingsRootVhdTypeOverride;
412 std::vector<BYTE> m_userSid;
413
414 WSLCFeatureFlags m_featureFlags{};
415 std::function<void()> m_destructionCallback;
416 std::atomic<bool> m_terminating{false};
417
418 wil::com_ptr<IWSLCPluginNotifier> m_pluginNotifier;
419
420 // Bounded in-memory ring of recent lifecycle events, shared by all event-stream subscribers.
421 EventStore m_eventStore;
422
423 __guarded_by(m_containersLock) std::shared_ptr<PendingContainerCreate> m_pendingCreate;
424
425 // N.B. Declared after everything OnContainerCreated() touches so the callback is unregistered first.
426 DockerEventTracker::EventTrackingReference m_containerEventTracking;
427
428 // User-provided handles that the session is currently doing IO on.
429 std::mutex m_userHandlesLock;
430 __guarded_by(m_userHandlesLock) std::vector<HANDLE> m_userHandles;
431
432 // Threads currently inside an outgoing COM callback (e.g. IProgressCallback::OnProgress).
433 std::recursive_mutex m_userCOMCallbacksLock;
434 __guarded_by(m_userCOMCallbacksLock) std::map<DWORD, int> m_userCOMCallbackThreads;
435
436 // Callbacks registered via RegisterCrashDumpCallback. std::list gives stable iterators that
437 // survive insertions and unrelated erasures, so each CrashDumpSubscription stashes its own
438 // iterator and uses it as an O(1) removal handle when the last reference is released.
439 // The session's lifetime extends past Terminate() (the COM object outlives the VM), so this
440 // list may outlive the runtime VM instance; that's fine because dispatch only runs while the VM
441 // thread is alive.
442 mutable wil::srwlock m_crashDumpLock;
443 _Guarded_by_(m_crashDumpLock) CrashDumpCallbackList m_crashDumpCallbacks;
444
445 friend class WSLCSessionRuntime;
446 };
447
448 } // namespace wsl::windows::service::wslc