master
h 357 lines 16.1 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCContainer.h
8
9 Abstract:
10
11 Contains the definition for WSLCContainer.
12
13 --*/
14
15 #pragma once
16
17 #include "ServiceProcessLauncher.h"
18 #include "WSLCSession.h"
19 #include "WSLCIdleState.h"
20 #include "DockerEventTracker.h"
21 #include "DockerHTTPClient.h"
22 #include "WSLCProcessControl.h"
23 #include "IORelay.h"
24 #include "COMImplClass.h"
25 #include "wslc_schema.h"
26 #include "WSLCCompat.h"
27 #include "WSLCContainerMetadata.h"
28 #include "WSLCNetworkMetadata.h"
29 #include "WSLCVhdVolume.h"
30 #include <unordered_map>
31
32 namespace wsl::windows::service::wslc {
33
34 class WSLCContainer;
35 class WSLCSession;
36 class WSLCSessionRuntime;
37 class WSLCVolumes;
38 class EventStore;
39
40 class unique_com_disconnect
41 {
42 public:
43 NON_COPYABLE(unique_com_disconnect);
44 DEFAULT_MOVABLE(unique_com_disconnect);
45
46 unique_com_disconnect() = default;
47 unique_com_disconnect(Microsoft::WRL::ComPtr<WSLCContainer>&& wrapper) noexcept;
48 ~unique_com_disconnect() noexcept;
49
50 private:
51 Microsoft::WRL::ComPtr<WSLCContainer> m_wrapper;
52 };
53
54 struct ContainerPortMapping
55 {
56 NON_COPYABLE(ContainerPortMapping);
57
58 ContainerPortMapping(VMPortMapping&& VmMapping, uint16_t ContainerPort);
59 ContainerPortMapping(ContainerPortMapping&& Other);
60
61 ContainerPortMapping& operator=(ContainerPortMapping&& Other);
62 const char* ProtocolString() const;
63
64 WSLCPortMapping Serialize() const;
65
66 VMPortMapping VmMapping;
67 uint16_t ContainerPort{};
68 };
69
70 class WSLCContainerImpl : public std::enable_shared_from_this<WSLCContainerImpl>
71 {
72 public:
73 NON_COPYABLE(WSLCContainerImpl);
74 NON_MOVABLE(WSLCContainerImpl);
75
76 WSLCContainerImpl(
77 WSLCSession& wslcSession,
78 WSLCSessionRuntime& runtime,
79 IWSLCPluginNotifier* pluginNotifier,
80 std::string&& Id,
81 std::string&& Name,
82 std::string&& Image,
83 std::string NetworkMode,
84 std::vector<WSLCVolumeMount>&& volumes,
85 std::vector<std::string>&& namedVolumes,
86 std::vector<ContainerPortMapping>&& ports,
87 std::map<std::string, std::string>&& labels,
88 std::function<void(const WSLCContainerImpl*)>&& OnDeleted,
89 EventStore& eventStore,
90 WSLCContainerState InitialState,
91 std::int64_t CreatedAt,
92 WSLCProcessFlags InitProcessFlags,
93 WSLCContainerFlags ContainerFlags);
94
95 ~WSLCContainerImpl();
96
97 void Initialize();
98
99 void Start(WSLCContainerStartFlags Flags, const WSLCProcessStartOptions* StartOptions);
100 void Attach(LPCSTR DetachKeys, WSLCHandle* Stdin, WSLCHandle* Stdout, WSLCHandle* Stderr) const;
101 void Stop(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, bool Kill);
102 void Restart(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds);
103 void Delete(WSLCDeleteFlags Flags);
104 void Export(WSLCHandle TarHandle) const;
105 void UploadArchive(WSLCHandle TarHandle, LPCSTR DestPath, ULONGLONG ContentSize) const;
106 void DownloadArchive(LPCSTR SrcPath, WSLCHandle OutHandle) const;
107 void GetStateChangedAt(_Out_ LONGLONG* StateChangedAt);
108 void GetCreatedAt(_Out_ LONGLONG* CreatedAt);
109 void GetState(_Out_ WSLCContainerState* State);
110 void GetInitProcess(_Out_ IWSLCProcess** process) const;
111 void Exec(_In_ const WSLCProcessOptions* Options, const WSLCProcessStartOptions* StartOptions, _Out_ IWSLCProcess** Process);
112 void Inspect(BOOL Size, LPSTR* Output) const;
113 void Logs(WSLCLogsFlags Flags, WSLCHandle* Stdout, WSLCHandle* Stderr, LONGLONG Since, LONGLONG Until, ULONGLONG Tail) const;
114 void Stats(LPSTR* Output) const;
115 void GetLabels(WSLCLabelInformation** Labels, ULONG* Count) const;
116 void ConnectToNetwork(const WSLCNetworkConnectionOptions* Options);
117 void DisconnectFromNetwork(LPCSTR NetworkName);
118
119 void CopyTo(IWSLCContainer** Container) const;
120
121 const std::string& Image() const noexcept;
122 const std::string& Name() const noexcept;
123 WSLCContainerState State() const noexcept;
124 std::vector<WSLCPortMapping> GetPorts() const;
125
126 // Re-registers a stopped container's VM-scoped port allocations against the restarted VM.
127 void RecoverPorts(const common::docker_schema::ContainerInfo& dockerContainer);
128
129 __requires_lock_held(m_lock) void CommitState(WSLCContainerState State, std::int64_t Time, std::optional<int> ExitCode = std::nullopt) noexcept;
130
131 const std::string& ID() const noexcept;
132
133 // Returns the container flags used to decide whether to
134 // auto-delete the container on stop.
135 WSLCContainerFlags Flags() const noexcept
136 {
137 return m_containerFlags;
138 }
139
140 static std::shared_ptr<WSLCContainerImpl> Create(
141 const WSLCContainerOptions& Options,
142 const std::string& Name,
143 WSLCSession& wslcSession,
144 WSLCSessionRuntime& runtime,
145 IWSLCPluginNotifier* pluginNotifier,
146 const std::unordered_map<std::string, NetworkEntry>& SessionNetworks,
147 std::function<void(const WSLCContainerImpl*)>&& OnDeleted,
148 EventStore& eventStore);
149
150 static std::shared_ptr<WSLCContainerImpl> Open(
151 const common::docker_schema::ContainerInfo& DockerContainer,
152 WSLCSession& wslcSession,
153 WSLCSessionRuntime& runtime,
154 IWSLCPluginNotifier* pluginNotifier,
155 std::function<void(const WSLCContainerImpl*)>&& OnDeleted,
156 EventStore& eventStore);
157
158 // Appends an event for this container to the session's event stream. Must be called from the Docker
159 // event stream thread so that recorded events keep Docker's delivery order.
160 void RecordEvent(std::string&& Action, std::int64_t Time, std::optional<int> ExitCode = std::nullopt) noexcept;
161
162 private:
163 enum class TransitionKind
164 {
165 Start,
166 Stop,
167 Delete
168 };
169
170 struct StateTransition
171 {
172 StateTransition(TransitionKind kind, ContainerEvent expectedEvent) : Kind(kind), ExpectedEvent(expectedEvent)
173 {
174 }
175
176 const TransitionKind Kind;
177 wil::unique_event Completed{wil::EventOptions::ManualReset};
178 std::exception_ptr Exception;
179
180 // Access under WSLCContainerImpl::m_lock.
181 ContainerEvent ExpectedEvent;
182 unique_com_disconnect Wrapper;
183 };
184
185 // Restart() runs a stop phase followed by a start phase. This marks the pair as one transaction so
186 // that Start() and Stop() cannot land in between.
187 struct RestartTransaction
188 {
189 wil::unique_event Completed{wil::EventOptions::ManualReset};
190 };
191
192 __requires_exclusive_lock_held(m_lock) void RequestDeleteExclusiveLockHeld(WSLCDeleteFlags Flags);
193
194 void AllocateBridgedModePorts();
195 void OnEvent(ContainerEvent event, std::optional<int> exitCode, std::int64_t eventTime) noexcept;
196
197 __requires_exclusive_lock_held(m_lock) std::shared_ptr<StateTransition> StartTransition(TransitionKind kind, ContainerEvent expectedEvent);
198
199 // Returns with both locks held when no transition is active (or it matches kind) and, if waitForRestart,
200 // no restart is in flight either. Both conditions are re-checked every time the locks come back.
201 void WaitForConflictingTransitionToComplete(
202 wil::rwlock_release_exclusive_scope_exit& lock,
203 wil::rwlock_release_shared_scope_exit& lifecycleLock,
204 std::optional<TransitionKind> kind = std::nullopt,
205 bool waitForRestart = true);
206
207 // Phases of Restart(). Identical to Start() and Stop() except that they do not stand down for the
208 // restart they are part of.
209 void StartPhase(WSLCContainerStartFlags Flags, const WSLCProcessStartOptions* StartOptions, bool RestartPhase);
210 void StopPhase(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill, bool RestartPhase);
211
212 // Undoes what the phases left half-done: releases the resources the stop phase held back and
213 // requests the auto-delete OnStopped() deferred, returning that delete's transition.
214 __requires_exclusive_lock_held(m_lock) std::shared_ptr<StateTransition> OnFailedRestartExclusiveLockHeld();
215
216 void WaitForCompletionEvent(HANDLE Event) const;
217 void WaitForTransitionCompletion(const std::shared_ptr<StateTransition>& transition) const;
218 void AttachToTransition(const std::shared_ptr<StateTransition>& transition) const;
219
220 __requires_exclusive_lock_held(m_lock) void CompleteTransition(
221 const std::shared_ptr<StateTransition>& transition, std::exception_ptr exception = {}) noexcept;
222
223 __requires_exclusive_lock_held(m_lock) [[nodiscard]] unique_com_disconnect ReleaseResources();
224 __requires_exclusive_lock_held(m_lock) void ReleaseRuntimeResources();
225 __requires_exclusive_lock_held(m_lock) void ReleaseProcesses();
226 __requires_exclusive_lock_held(m_lock) [[nodiscard]] unique_com_disconnect PrepareDisconnectComWrapper();
227
228 __requires_exclusive_lock_held(m_lock) void OnStopped(int exitCode, std::int64_t stopTime);
229
230 void SetExitCode(int ExitCode) noexcept;
231 void SignalInitProcessExit() noexcept;
232
233 std::unique_ptr<RelayedProcessIO> CreateRelayedProcessIO(wil::shared_socket stream, WSLCProcessFlags flags);
234
235 wsl::windows::common::wslc_schema::InspectContainer BuildInspectContainer(const wsl::windows::common::docker_schema::InspectContainer& dockerInspect) const;
236
237 void MapPorts();
238 void UnmapPorts();
239
240 // Acquires or releases the activity hold so it is held exactly while the container is Running,
241 // keeping the session's VM alive across idle teardown.
242 __requires_lock_held(m_lock) void UpdateActivityHoldLockHeld() noexcept;
243
244 __requires_shared_lock_held(m_lock) std::string InspectLockHeld(bool Size = false) const;
245
246 // Lifecycle requests hold this shared until their transitions are published; event delivery holds it exclusively.
247 // N.B. Stop releases it across the docker request, which can block indefinitely, and re-checks m_stateGeneration instead.
248 wil::srwlock m_lifecycleLock;
249 mutable wil::srwlock m_lock;
250 std::string m_name;
251 std::string m_image;
252 std::string m_id;
253 WSLCProcessFlags m_initProcessFlags{};
254 WSLCContainerFlags m_containerFlags{};
255 mutable std::mutex m_processesLock;
256 __guarded_by(m_processesLock) std::vector<std::weak_ptr<DockerExecProcessControl>> m_processes;
257 __guarded_by(m_processesLock) Microsoft::WRL::ComPtr<IWSLCProcess> m_initProcess;
258 __guarded_by(m_processesLock) DockerContainerProcessControl* m_initProcessControl = nullptr;
259
260 _Guarded_by_(m_lock) std::shared_ptr<StateTransition> m_transition;
261
262 // Non-null from before Restart()'s stop phase until its start phase commits Running. Start() and
263 // Stop() stand down for that window, and OnStopped() keeps the container's runtime resources mapped
264 // and skips the auto-delete of an --rm container. Delete() does not stand down: a remove that lands
265 // between the two phases takes effect, and the restart's start phase fails.
266 _Guarded_by_(m_lock) std::shared_ptr<RestartTransaction> m_restart;
267
268 // True between a successful StartPhase() and the release of the container's ports and mounts. A
269 // restart leaves this set across the two phases, which is what tells the start phase they are still
270 // held and must not be re-acquired.
271 _Guarded_by_(m_lock) bool m_runtimeResourcesHeld = false;
272
273 // The container outlives any single VM: it survives idle-termination and is reused when the VM
274 // restarts. VM-scoped resources (Vm(), Docker(), Volumes(), Events(), Relay()) are therefore
275 // fetched from the (stable) runtime at each use rather than cached, since a cached reference
276 // would dangle across a restart. They are only valid while a VM lease is held.
277 WSLCSessionRuntime& m_runtime;
278 std::int64_t m_stateChangedAt{static_cast<std::int64_t>(std::time(nullptr))};
279 std::int64_t m_createdAt{};
280 WSLCContainerState m_state = WslcContainerStateInvalid;
281
282 // Bumped on every state change so a thread that released m_lock can detect a state cycle, not just a difference.
283 std::uint64_t m_stateGeneration{};
284 WSLCSession& m_wslcSession;
285 IWSLCPluginNotifier* m_pluginNotifier;
286 std::vector<ContainerPortMapping> m_mappedPorts;
287 std::vector<WSLCVolumeMount> m_mountedVolumes;
288
289 std::vector<std::string> m_namedVolumes;
290
291 std::map<std::string, std::string> m_labels;
292 Microsoft::WRL::ComPtr<WSLCContainer> m_comWrapper;
293 DockerEventTracker::EventTrackingReference m_containerEvents;
294 EventStore& m_eventStore;
295 std::string m_networkMode;
296
297 // Held (non-empty) exactly while the container is Running so the session's VM stays alive even
298 // when no client holds the wrapper (e.g. a detached `run -d` container). Maintained by
299 // UpdateActivityHoldLockHeld(); released automatically when the container is destroyed.
300 ActivityRef m_activityHold;
301 };
302
303 class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer
304 : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLCContainer, IWSLCCompatContainer, IFastRundown, ISupportErrorInfo>,
305 public COMImplClass<WSLCContainerImpl, std::weak_ptr<WSLCContainerImpl>>
306 {
307
308 public:
309 WSLCContainer(WSLCSession& session, std::function<void(const WSLCContainerImpl*)>&& OnDeleted);
310
311 IFACEMETHOD(Attach)(_In_opt_ LPCSTR DetachKeys, _Out_ WSLCHandle* Stdin, _Out_ WSLCHandle* Stdout, _Out_ WSLCHandle* Stderr) override;
312 IFACEMETHOD(Stop)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds) override;
313 IFACEMETHOD(Restart)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback) override;
314 IFACEMETHOD(Kill)(_In_ WSLCSignal Signal) override;
315 IFACEMETHOD(Delete)(WSLCDeleteFlags Flags) override;
316 IFACEMETHOD(Export)(_In_ WSLCHandle TarHandle) override;
317 IFACEMETHOD(UploadArchive)(_In_ WSLCHandle TarHandle, _In_ LPCSTR DestPath, _In_ ULONGLONG ContentSize) override;
318 IFACEMETHOD(DownloadArchive)(_In_ LPCSTR SrcPath, _In_ WSLCHandle OutHandle) override;
319 IFACEMETHOD(GetState)(_Out_ WSLCContainerState* State) override;
320 IFACEMETHOD(GetInitProcess)(_Out_ IWSLCProcess** process) override;
321 IFACEMETHOD(Exec)(_In_ const WSLCProcessOptions* Options, _In_opt_ const WSLCProcessStartOptions* StartOptions, _Out_ IWSLCProcess** Process) override;
322 IFACEMETHOD(Start)(WSLCContainerStartFlags Flags, _In_opt_ const WSLCProcessStartOptions* StartOptions, _In_opt_ IWarningCallback* WarningCallback) override;
323 IFACEMETHOD(Inspect)(_In_ BOOL Size, _Out_ LPSTR* Output) override;
324 IFACEMETHOD(Logs)(_In_ WSLCLogsFlags Flags, _Out_ WSLCHandle* Stdout, _Out_ WSLCHandle* Stderr, _In_ LONGLONG Since, _In_ LONGLONG Until, _In_ ULONGLONG Tail) override;
325 IFACEMETHOD(GetId)(_Out_ WSLCContainerId Id) override;
326 IFACEMETHOD(GetName)(_Out_ LPSTR* Name) override;
327 IFACEMETHOD(GetLabels)(_Out_ WSLCLabelInformation** Labels, _Out_ ULONG* Count) override;
328 IFACEMETHOD(Stats)(_Out_ LPSTR* Output) override;
329 IFACEMETHOD(ConnectToNetwork)(_In_ const WSLCNetworkConnectionOptions* Options) override;
330 IFACEMETHOD(DisconnectFromNetwork)(_In_ LPCSTR NetworkName) override;
331
332 // IWSLCCompatContainer.
333 IFACEMETHOD(Start)(_In_ WSLCContainerStartFlags Flags) override;
334 IFACEMETHOD(GetInitProcess)(_Out_ IWSLCCompatProcess** Process) override;
335 IFACEMETHOD(Exec)(_In_ const WSLCCompatProcessOptions* Options, _Out_ IWSLCCompatProcess** Process) override;
336 IFACEMETHOD(Inspect)(_Out_ LPSTR* Output) override;
337
338 IFACEMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
339
340 // Cache read-only properties so they remain accessible after the impl is disconnected.
341 // Called from WSLCContainerImpl::PrepareDisconnectComWrapper() while m_lock is held exclusively.
342 void CacheState(const std::string& id, const std::string& name, WSLCContainerState state, const Microsoft::WRL::ComPtr<IWSLCProcess>& initProcess) noexcept;
343
344 private:
345 WSLCSession& m_session;
346 std::function<void(const WSLCContainerImpl*)> m_onDeleted;
347
348 // Cached read-only properties populated by CacheState() so they remain
349 // accessible after the impl is disconnected.
350 mutable wil::srwlock m_cacheLock;
351 _Guarded_by_(m_cacheLock) std::optional<std::string> m_cachedId;
352 _Guarded_by_(m_cacheLock) std::optional<std::string> m_cachedName;
353 _Guarded_by_(m_cacheLock) std::optional<WSLCContainerState> m_cachedState;
354 _Guarded_by_(m_cacheLock) Microsoft::WRL::ComPtr<IWSLCProcess> m_cachedInitProcess;
355 };
356
357 } // namespace wsl::windows::service::wslc