rocketmq_runtime/
owner.rs1use std::future::Future;
16
17use crate::config::RuntimeConfig;
18use crate::diagnostics::RuntimeDiagnostics;
19use crate::error::RuntimeError;
20use crate::error::RuntimeResult;
21use crate::handle::RuntimeHandle;
22use crate::resource_budget::ManagedMemoryBudget;
23use crate::resource_budget::ManagedMemoryPolicy;
24use crate::resource_budget::ProcessMemoryLimit;
25use crate::resources::RuntimeResources;
26use crate::service_context::RootServiceContext;
27use crate::shutdown_deadline::ShutdownDeadline;
28use crate::shutdown_report::ShutdownReport;
29use crate::task_group::TaskGroupLifecycleState;
30
31pub struct RuntimeOwner {
36 config: RuntimeConfig,
37 runtime: Option<tokio::runtime::Runtime>,
38 resources: RuntimeResources,
39 root_context: RootServiceContext,
40}
41
42#[derive(Debug)]
47pub struct RuntimeOwnerPlan {
48 config: RuntimeConfig,
49 memory_limit: Option<ProcessMemoryLimit>,
50 memory_policy: ManagedMemoryPolicy,
51 metadata_target_capacity: std::num::NonZeroUsize,
52}
53
54impl RuntimeOwnerPlan {
55 #[must_use]
63 pub fn with_metadata_target_capacity(mut self, capacity: std::num::NonZeroUsize) -> Self {
64 self.metadata_target_capacity = capacity;
65 self
66 }
67 #[must_use]
69 pub fn with_memory_limit(mut self, memory_limit: ProcessMemoryLimit) -> Self {
70 self.memory_limit = Some(memory_limit);
71 self
72 }
73
74 #[must_use]
80 pub fn with_memory_policy(mut self, memory_policy: ManagedMemoryPolicy) -> Self {
81 self.memory_policy = memory_policy;
82 self
83 }
84
85 pub fn build(self) -> RuntimeResult<RuntimeOwner> {
93 let Self {
94 config,
95 memory_limit,
96 memory_policy,
97 metadata_target_capacity,
98 } = self;
99 RuntimeOwner::build_validated(config, metadata_target_capacity, move || {
100 let memory_limit = memory_limit.map_or_else(ProcessMemoryLimit::detect, Ok)?;
101 ManagedMemoryBudget::resolve(memory_limit, None, memory_policy).map_err(|violation| {
102 RuntimeError::configuration_failure(crate::RuntimeOperation::ResolveManagedMemoryBudget, violation)
103 })
104 })
105 }
106}
107
108impl RuntimeOwner {
109 pub fn plan(config: RuntimeConfig) -> Result<RuntimeOwnerPlan, crate::RuntimeContractViolation> {
116 config.validate()?;
117 Ok(RuntimeOwnerPlan {
118 config,
119 memory_limit: None,
120 memory_policy: ManagedMemoryPolicy::default(),
121 metadata_target_capacity: std::num::NonZeroUsize::new(crate::metadata_target::DEFAULT_MAX_METADATA_TARGETS)
122 .expect("the static default metadata target capacity is positive"),
123 })
124 }
125
126 pub fn new() -> RuntimeResult<Self> {
134 Self::plan(RuntimeConfig::default())
135 .expect("RuntimeConfig::default always satisfies its validation contract")
136 .build()
137 }
138
139 fn build_validated<F>(
140 config: RuntimeConfig,
141 metadata_target_capacity: std::num::NonZeroUsize,
142 detector: F,
143 ) -> RuntimeResult<Self>
144 where
145 F: FnOnce() -> RuntimeResult<ManagedMemoryBudget>,
146 {
147 let memory_budget = detector()?;
148 let resources =
149 RuntimeResources::from_memory_budget_with_metadata_capacity(memory_budget, metadata_target_capacity.get());
150
151 let mut builder = tokio::runtime::Builder::new_multi_thread();
152 builder
153 .worker_threads(config.worker_threads)
154 .max_blocking_threads(config.tokio_blocking_threads())
155 .thread_name(config.thread_name.clone())
156 .thread_keep_alive(config.thread_keep_alive);
157 if let Some(thread_stack_size) = config.thread_stack_size {
158 builder.thread_stack_size(thread_stack_size);
159 }
160 if config.enable_io {
161 builder.enable_io();
162 }
163 if config.enable_time {
164 builder.enable_time();
165 }
166
167 let runtime = build_tokio_runtime(&mut builder)?;
168 let runtime_handle = RuntimeHandle::new(runtime.handle().clone());
169 let diagnostics = RuntimeDiagnostics::new();
170 let root_context = RootServiceContext::new(
171 config.thread_name.clone().into(),
172 runtime_handle,
173 config.blocking_lane_policies.clone(),
174 config.max_blocking_threads,
175 diagnostics,
176 resources.clone(),
177 );
178 Ok(Self {
179 config,
180 runtime: Some(runtime),
181 resources,
182 root_context,
183 })
184 }
185
186 pub fn root_context(&self) -> &RootServiceContext {
188 &self.root_context
189 }
190
191 pub fn config(&self) -> &RuntimeConfig {
193 &self.config
194 }
195
196 pub fn resources(&self) -> &RuntimeResources {
198 &self.resources
199 }
200
201 pub fn block_on<F>(&self, future: F) -> F::Output
206 where
207 F: Future,
208 {
209 let runtime = self.runtime.as_ref().expect("runtime owner must still own the runtime");
210 if std::mem::size_of::<F>() > crate::stack::MAX_INLINE_SIZE {
211 runtime.block_on(Box::pin(future))
212 } else {
213 runtime.block_on(future)
214 }
215 }
216
217 pub async fn shutdown_tasks(&self) -> ShutdownReport {
219 self.shutdown_tasks_until(ShutdownDeadline::after(self.config.shutdown_timeout))
220 .await
221 }
222
223 pub fn shutdown_runtime_blocking(self) -> RuntimeResult<ShutdownReport> {
229 let timeout = self.config.shutdown_timeout;
230 self.shutdown_runtime_blocking_with_timeout(timeout)
231 }
232
233 pub fn shutdown_background(mut self) -> ShutdownReport {
238 let report = self.shutdown_tasks_now();
239 report.log_if_unhealthy();
240 if let Some(runtime) = self.runtime.take() {
241 runtime.shutdown_background();
242 }
243 report
244 }
245
246 pub fn shutdown_runtime_blocking_with_timeout(self, timeout: std::time::Duration) -> RuntimeResult<ShutdownReport> {
252 self.shutdown_runtime_blocking_until(ShutdownDeadline::after(timeout))
253 }
254
255 pub fn shutdown_runtime_blocking_until(mut self, deadline: ShutdownDeadline) -> RuntimeResult<ShutdownReport> {
265 if tokio::runtime::Handle::try_current().is_ok() {
266 return Err(RuntimeError::unsupported(
267 crate::RuntimeOperation::ShutdownRuntimeBlocking,
268 ));
269 }
270
271 let runtime = self.runtime.take().expect("runtime owner must still own the runtime");
272 let report = runtime.block_on(self.shutdown_tasks_until(deadline));
273 report.log_if_unhealthy();
274 runtime.shutdown_timeout(deadline.remaining());
275 Ok(report)
276 }
277}
278
279fn build_tokio_runtime(builder: &mut tokio::runtime::Builder) -> RuntimeResult<tokio::runtime::Runtime> {
280 builder
281 .build()
282 .map_err(|source| RuntimeError::build(crate::RuntimeOperation::BuildTokioRuntime, source))
283}
284
285impl Drop for RuntimeOwner {
286 fn drop(&mut self) {
287 if let Some(runtime) = self.runtime.take() {
288 if self.root_context.task_group().lifecycle_state() != TaskGroupLifecycleState::ShutdownCompleted {
289 let report = self.shutdown_tasks_now();
290 tracing::warn!(
291 report = %report.to_json(),
292 "RuntimeOwner dropped before root TaskGroup shutdown completed"
293 );
294 }
295 runtime.shutdown_background();
296 }
297 }
298}
299
300impl RuntimeOwner {
301 pub async fn shutdown_tasks_until(&self, deadline: ShutdownDeadline) -> ShutdownReport {
306 let mut report = self.root_context.task_group().shutdown_until(deadline).await;
307 self.root_context.merge_blocking_into(&mut report);
308 report
309 }
310
311 fn shutdown_tasks_now(&self) -> ShutdownReport {
312 let mut report = self.root_context.task_group().shutdown_now();
313 self.root_context.merge_blocking_into(&mut report);
314 report
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn plan_rejects_invalid_configuration_before_resource_discovery() {
324 let invalid = RuntimeConfig {
325 worker_threads: 0,
326 ..RuntimeConfig::default()
327 };
328
329 assert!(matches!(
330 RuntimeOwner::plan(invalid),
331 Err(crate::RuntimeContractViolation::InvalidConfiguration {
332 policy: crate::RuntimeContractPolicy::WorkerThreadsPositive,
333 })
334 ));
335 }
336
337 #[test]
338 fn validated_plan_builds_with_a_supplied_memory_limit() {
339 let owner = RuntimeOwner::plan(RuntimeConfig::default())
340 .expect("default runtime configuration is valid")
341 .with_memory_limit(ProcessMemoryLimit::configured(4 * 1024 * 1024).expect("test limit is valid"))
342 .build()
343 .expect("runtime owner");
344
345 assert_eq!(owner.resources().memory_limit().bytes(), 4 * 1024 * 1024);
346 }
347
348 #[test]
349 fn tokio_builder_uses_the_production_build_helper() {
350 let mut builder = tokio::runtime::Builder::new_multi_thread();
351 builder.worker_threads(1).max_blocking_threads(1).enable_all();
352
353 let runtime = build_tokio_runtime(&mut builder).expect("valid Tokio builder should start");
354
355 runtime.block_on(async {});
356 }
357
358 #[test]
359 fn a_memory_policy_bounds_the_process_budget() {
360 let owner = RuntimeOwner::plan(RuntimeConfig::default())
361 .expect("default config is valid")
362 .with_memory_limit(ProcessMemoryLimit::configured(4 * 1024 * 1024).expect("test limit is valid"))
363 .with_memory_policy(ManagedMemoryPolicy::fraction(1, 2).expect("a half is bounded"))
364 .build()
365 .expect("the owner should start");
366
367 assert_eq!(owner.resources().memory_limit().bytes(), 4 * 1024 * 1024);
370 assert_eq!(owner.resources().memory_budget().managed_bytes(), 2 * 1024 * 1024);
371
372 let budget = owner.resources().process_budget();
373 assert_eq!(budget.limit().capacity.bytes, 2 * 1024 * 1024);
374 assert!(budget.try_acquire_data(2 * 1024 * 1024 + 1).is_err());
375 assert!(budget.try_acquire_data(2 * 1024 * 1024).is_ok());
376 }
377
378 #[test]
379 fn an_unresolvable_memory_policy_fails_the_owner_build() {
380 let error = RuntimeOwner::plan(RuntimeConfig::default())
381 .expect("default config is valid")
382 .with_memory_limit(ProcessMemoryLimit::configured(4 * 1024 * 1024).expect("test limit is valid"))
383 .with_memory_policy(ManagedMemoryPolicy::whole_limit().with_headroom(8 * 1024 * 1024))
384 .build()
385 .err()
386 .expect("headroom above the effective limit cannot resolve a budget");
387
388 assert_eq!(error.operation(), crate::RuntimeOperation::ResolveManagedMemoryBudget);
389 }
390}