master
h 226 lines 6.68 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCIdleState.h
8
9 Abstract:
10
11 Shared idle-termination state for WSLC session VM lifecycle.
12
13 --*/
14 #pragma once
15
16 #include <atomic>
17 #include <chrono>
18 #include <functional>
19 #include <memory>
20 #include <utility>
21 #include <wil/resource.h>
22
23 namespace wsl::windows::service::wslc {
24
25 // Shared idle-termination state for a WSLC session.
26 //
27 // A single activity refcount is the only source of truth for "the VM is needed". Everything that
28 // requires the VM holds a reference for as long as it needs it:
29 // * in-flight operations (WSLCSession::VmLease),
30 // * running/created containers themselves (WSLCContainerImpl's ActivityRef),
31 // * client-held process wrappers (WSLCProcess keep-alive token),
32 // * multi-round-trip CLI operations (WSLCSession::BeginContainerOperation).
33 //
34 // When the count drops to zero a threadpool timer is armed for the idle grace period; if it
35 // elapses without new activity the session-supplied OnIdle callback tears the VM down. Any new
36 // activity before it fires cancels the timer.
37 //
38 // Held via shared_ptr so activity holders (container/process wrappers, operation tokens) can
39 // outlive the owning session and release activity without dereferencing it. The session clears the
40 // callback and drains the timer in Disarm() during teardown, after which a late release simply
41 // decrements the count and never re-enters the destroyed session.
42 class IdleState
43 {
44 public:
45 IdleState() = default;
46
47 IdleState(const IdleState&) = delete;
48 IdleState& operator=(const IdleState&) = delete;
49
50 // Installs the idle-teardown callback and grace period and creates the timer. Called once by
51 // the owning session after construction. OnIdle runs on a threadpool thread.
52 void Initialize(std::chrono::milliseconds GracePeriod, std::function<void()> OnIdle)
53 {
54 auto lock = m_lock.lock_exclusive();
55 m_gracePeriod = GracePeriod;
56 m_onIdle = std::move(OnIdle);
57 m_timer.reset(CreateThreadpoolTimer(&IdleState::TimerCallback, this, nullptr));
58 THROW_LAST_ERROR_IF(!m_timer);
59 }
60
61 // Permanently disables idle teardown: clears the callback so no further arm has any effect, and
62 // drains any pending/running timer callback. Must be called by the session (with its own lock
63 // released) during teardown, before the session object is destroyed, so no callback can
64 // reference it afterwards.
65 void Disarm() noexcept
66 {
67 PTP_TIMER timer = nullptr;
68 {
69 auto lock = m_lock.lock_exclusive();
70 m_onIdle = nullptr;
71 timer = m_timer.get();
72 if (timer != nullptr)
73 {
74 SetThreadpoolTimer(timer, nullptr, 0, 0);
75 }
76 }
77
78 // Drain any in-flight callback outside the lock; it may take the session lock.
79 if (timer != nullptr)
80 {
81 WaitForThreadpoolTimerCallbacks(timer, TRUE);
82 }
83 }
84
85 // Records the start of an activity; cancels any pending idle teardown on the 0->1 transition.
86 void AddActivity() noexcept
87 {
88 auto lock = m_lock.lock_exclusive();
89 if (m_activityCount.fetch_add(1) == 0)
90 {
91 CancelLockHeld();
92 }
93 }
94
95 // Records the end of an activity; arms the idle timer on the 1->0 transition.
96 void ReleaseActivity() noexcept
97 {
98 auto lock = m_lock.lock_exclusive();
99 const int previous = m_activityCount.fetch_sub(1);
100 FAIL_FAST_IF(previous <= 0); // Underflow is a fatal bug, not a recoverable condition.
101 if (previous == 1)
102 {
103 ArmLockHeld();
104 }
105 }
106
107 int ActivityCount() const noexcept
108 {
109 return m_activityCount.load();
110 }
111
112 private:
113 static void CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE, PVOID Context, PTP_TIMER) noexcept
114 try
115 {
116 auto* self = static_cast<IdleState*>(Context);
117
118 std::function<void()> onIdle;
119 {
120 auto lock = self->m_lock.lock_exclusive();
121
122 // Activity resumed (count != 0) or teardown raced us (callback cleared): nothing to do.
123 if (self->m_activityCount.load() != 0 || !self->m_onIdle)
124 {
125 return;
126 }
127
128 // Copy and invoke outside the lock: OnIdle takes the session lock, and holding this
129 // lock across that would invert the session-lock -> idle-lock ordering.
130 onIdle = self->m_onIdle;
131 }
132
133 onIdle();
134 }
135 CATCH_LOG()
136
137 void ArmLockHeld() noexcept
138 {
139 if (!m_timer || !m_onIdle)
140 {
141 return;
142 }
143
144 // Relative due time is expressed as a negative count of 100ns intervals.
145 const int64_t relative = -static_cast<int64_t>(m_gracePeriod.count()) * 10000;
146 FILETIME due{};
147 due.dwLowDateTime = static_cast<DWORD>(relative & 0xFFFFFFFF);
148 due.dwHighDateTime = static_cast<DWORD>((relative >> 32) & 0xFFFFFFFF);
149 SetThreadpoolTimer(m_timer.get(), &due, 0, 0);
150 }
151
152 void CancelLockHeld() noexcept
153 {
154 if (m_timer)
155 {
156 SetThreadpoolTimer(m_timer.get(), nullptr, 0, 0);
157 }
158 }
159
160 std::atomic<int> m_activityCount{0};
161 wil::srwlock m_lock;
162
163 _Guarded_by_(m_lock) std::function<void()> m_onIdle;
164 _Guarded_by_(m_lock) std::chrono::milliseconds m_gracePeriod { 0 };
165 _Guarded_by_(m_lock) wil::unique_threadpool_timer m_timer;
166 };
167
168 // RAII activity hold on an IdleState: increments on construction and decrements on destruction or
169 // reset(). Movable, non-copyable. Used by running/created containers to keep the VM alive without
170 // a client reference. Holds the IdleState via shared_ptr so it is safe even if it outlives the
171 // owning session.
172 class ActivityRef
173 {
174 public:
175 ActivityRef() = default;
176
177 explicit ActivityRef(std::shared_ptr<IdleState> State) noexcept : m_state(std::move(State))
178 {
179 if (m_state)
180 {
181 m_state->AddActivity();
182 }
183 }
184
185 ActivityRef(ActivityRef&& Other) noexcept : m_state(std::exchange(Other.m_state, nullptr))
186 {
187 }
188
189 ActivityRef& operator=(ActivityRef&& Other) noexcept
190 {
191 if (this != &Other)
192 {
193 reset();
194 m_state = std::exchange(Other.m_state, nullptr);
195 }
196
197 return *this;
198 }
199
200 ActivityRef(const ActivityRef&) = delete;
201 ActivityRef& operator=(const ActivityRef&) = delete;
202
203 ~ActivityRef()
204 {
205 reset();
206 }
207
208 void reset() noexcept
209 {
210 if (m_state)
211 {
212 m_state->ReleaseActivity();
213 m_state.reset();
214 }
215 }
216
217 explicit operator bool() const noexcept
218 {
219 return m_state != nullptr;
220 }
221
222 private:
223 std::shared_ptr<IdleState> m_state;
224 };
225
226 } // namespace wsl::windows::service::wslc