1use std::collections::HashMap;
16use std::ffi::OsString;
17use std::fmt;
18use std::path::Path;
19use std::sync::atomic::AtomicU64;
20use std::sync::atomic::Ordering;
21use std::sync::Arc;
22
23use parking_lot::Mutex;
24
25use crate::error::RuntimeError;
26use crate::error::RuntimeResult;
27use crate::metadata_io::MetadataGeneration;
28use crate::RuntimeOperation;
29
30pub(crate) const DEFAULT_MAX_METADATA_TARGETS: usize = 4_096;
31
32#[derive(Clone, Debug)]
38pub struct MetadataTargetIdentity(Arc<()>);
39
40impl PartialEq for MetadataTargetIdentity {
41 fn eq(&self, other: &Self) -> bool {
42 Arc::ptr_eq(&self.0, &other.0)
43 }
44}
45
46impl Eq for MetadataTargetIdentity {}
47
48#[derive(Debug, Clone, Copy, serde::Serialize)]
50pub struct MetadataTargetRegistryStats {
51 pub capacity: usize,
53 pub retained_targets: usize,
55 pub live_owners: usize,
57 pub idle_histories: usize,
59 pub fenced_targets: usize,
61 pub remaining_capacity: usize,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum MetadataTargetRetirementOutcome {
68 Retired,
70 NotFound,
72 AdmissionOpen,
74 WorkInProgress,
76 ReconciliationRequired,
78 HistoryChanged,
80}
81
82#[derive(Clone)]
84pub(crate) struct MetadataTargetRegistry {
85 inner: Arc<RegistryInner>,
86}
87
88struct RegistryInner {
89 state: Mutex<RegistryState>,
90 next_owner: AtomicU64,
91 next_generation: AtomicU64,
92 max_entries: usize,
93}
94
95#[derive(Default)]
96struct RegistryState {
97 targets: HashMap<NormalizedMetadataTarget, TargetEntry>,
98}
99
100struct TargetEntry {
101 identity: MetadataTargetIdentity,
102 resource: Arc<str>,
103 owner: Option<u64>,
104 durable_generation: Option<MetadataGeneration>,
105 reconciliation_required: bool,
106}
107
108#[derive(Clone, PartialEq, Eq, Hash)]
109struct NormalizedMetadataTarget(OsString);
110
111#[derive(Clone)]
114pub(crate) struct MetadataTargetRegistration {
115 inner: Arc<RegistrationInner>,
116}
117
118struct RegistrationInner {
119 identity: MetadataTargetIdentity,
120 registry: MetadataTargetRegistry,
121 target: NormalizedMetadataTarget,
122 resource: Arc<str>,
123 owner: u64,
124 durable_generation: Option<MetadataGeneration>,
125}
126
127#[derive(Debug)]
128pub(crate) enum MetadataTargetRegistrationOutcome {
129 Registered(MetadataTargetRegistration),
130 Conflict,
131 ReconciliationRequired,
132}
133
134impl MetadataTargetRegistry {
135 #[cfg(test)]
136 pub(crate) fn new() -> Self {
137 Self::with_max_entries(DEFAULT_MAX_METADATA_TARGETS)
138 }
139
140 pub(crate) fn with_max_entries(max_entries: usize) -> Self {
141 Self {
142 inner: Arc::new(RegistryInner {
143 state: Mutex::new(RegistryState::default()),
144 next_owner: AtomicU64::new(1),
145 next_generation: AtomicU64::new(1),
146 max_entries,
147 }),
148 }
149 }
150
151 pub(crate) fn next_generation(&self) -> u64 {
161 let mut generation = self.inner.next_generation.load(Ordering::Relaxed);
162 loop {
163 let next = if generation == u64::MAX { 1 } else { generation + 1 };
164 match self.inner.next_generation.compare_exchange_weak(
165 generation,
166 next,
167 Ordering::Relaxed,
168 Ordering::Relaxed,
169 ) {
170 Ok(_) => return generation,
171 Err(observed) => generation = observed,
172 }
173 }
174 }
175
176 fn requires_reconciliation(&self, target: &NormalizedMetadataTarget) -> bool {
178 let state = self.inner.state.lock();
179 state
180 .targets
181 .get(target)
182 .is_some_and(|entry| entry.reconciliation_required)
183 }
184
185 #[cfg(test)]
186 pub(crate) fn register(
187 &self,
188 target: &Path,
189 resource: Arc<str>,
190 ) -> RuntimeResult<MetadataTargetRegistrationOutcome> {
191 self.register_with_identity(target, resource, None)
192 }
193
194 pub(crate) fn register_with_identity(
195 &self,
196 target: &Path,
197 resource: Arc<str>,
198 expected: Option<&MetadataTargetIdentity>,
199 ) -> RuntimeResult<MetadataTargetRegistrationOutcome> {
200 let target = normalize_target(target)?;
201 let mut state = self.inner.state.lock();
202 if expected.is_some_and(|identity| {
203 state
204 .targets
205 .get(&target)
206 .is_none_or(|entry| &entry.identity != identity)
207 }) {
208 return Err(RuntimeError::context_unavailable(
209 RuntimeOperation::MetadataResourceTarget,
210 ));
211 }
212
213 if let Some(entry) = state.targets.get(&target) {
214 if entry.reconciliation_required {
215 return Ok(MetadataTargetRegistrationOutcome::ReconciliationRequired);
216 }
217 if entry.resource != resource || entry.owner.is_some() {
218 return Ok(MetadataTargetRegistrationOutcome::Conflict);
219 }
220 } else if state.targets.len() >= self.inner.max_entries {
221 return Err(RuntimeError::capacity(RuntimeOperation::AdmitMetadataOperation));
222 }
223
224 let owner = self.inner.next_owner.fetch_add(1, Ordering::Relaxed);
225 let entry = state.targets.entry(target.clone()).or_insert_with(|| TargetEntry {
226 identity: MetadataTargetIdentity(Arc::new(())),
227 resource: Arc::clone(&resource),
228 owner: None,
229 durable_generation: None,
230 reconciliation_required: false,
231 });
232 entry.owner = Some(owner);
233 let durable_generation = entry.durable_generation;
234
235 Ok(MetadataTargetRegistrationOutcome::Registered(
236 MetadataTargetRegistration {
237 inner: Arc::new(RegistrationInner {
238 identity: entry.identity.clone(),
239 registry: self.clone(),
240 target,
241 resource,
242 owner,
243 durable_generation,
244 }),
245 },
246 ))
247 }
248
249 pub(crate) fn same_target(&self, left: &Path, right: &Path) -> RuntimeResult<bool> {
250 Ok(normalize_target(left)? == normalize_target(right)?)
251 }
252
253 pub(crate) fn capacity(&self) -> usize {
254 self.inner.max_entries
255 }
256
257 pub(crate) fn stats(&self) -> MetadataTargetRegistryStats {
258 let state = self.inner.state.lock();
259 let live_owners = state.targets.values().filter(|entry| entry.owner.is_some()).count();
260 MetadataTargetRegistryStats {
261 capacity: self.inner.max_entries,
262 retained_targets: state.targets.len(),
263 live_owners,
264 idle_histories: state.targets.len() - live_owners,
265 fenced_targets: state
266 .targets
267 .values()
268 .filter(|entry| entry.reconciliation_required)
269 .count(),
270 remaining_capacity: self.inner.max_entries.saturating_sub(state.targets.len()),
271 }
272 }
273
274 pub(crate) fn retire(
275 &self,
276 target: &Path,
277 resource: &str,
278 identity: &MetadataTargetIdentity,
279 durable_generation: Option<MetadataGeneration>,
280 ) -> RuntimeResult<MetadataTargetRetirementOutcome> {
281 let target = normalize_target(target)?;
282 let mut state = self.inner.state.lock();
283 let Some(entry) = state.targets.get(&target) else {
284 return Ok(MetadataTargetRetirementOutcome::HistoryChanged);
285 };
286 if &entry.identity != identity
287 || entry.resource.as_ref() != resource
288 || entry.durable_generation != durable_generation
289 {
290 return Ok(MetadataTargetRetirementOutcome::HistoryChanged);
291 }
292 if entry.reconciliation_required {
293 return Ok(MetadataTargetRetirementOutcome::ReconciliationRequired);
294 }
295 if entry.owner.is_some() {
296 return Ok(MetadataTargetRetirementOutcome::WorkInProgress);
297 }
298 state.targets.remove(&target);
299 Ok(MetadataTargetRetirementOutcome::Retired)
300 }
301
302 fn record_durable(
303 &self,
304 target: &NormalizedMetadataTarget,
305 resource: &Arc<str>,
306 owner: u64,
307 generation: MetadataGeneration,
308 ) -> bool {
309 let mut state = self.inner.state.lock();
310 let Some(entry) = state.targets.get_mut(target) else {
311 return false;
312 };
313 if entry.resource != *resource || entry.owner != Some(owner) {
314 return false;
315 }
316 entry.durable_generation = Some(
317 entry
318 .durable_generation
319 .map_or(generation, |durable| durable.max(generation)),
320 );
321 true
322 }
323
324 fn release(&self, target: &NormalizedMetadataTarget, owner: u64) {
325 let mut state = self.inner.state.lock();
326 let Some(entry) = state.targets.get_mut(target) else {
327 return;
328 };
329 if entry.owner == Some(owner) {
330 entry.owner = None;
331 }
332 }
333
334 fn record_reconciliation_required(
335 &self,
336 target: &NormalizedMetadataTarget,
337 resource: &Arc<str>,
338 owner: u64,
339 ) -> bool {
340 let mut state = self.inner.state.lock();
341 let Some(entry) = state.targets.get_mut(target) else {
342 return false;
343 };
344 if entry.resource != *resource || entry.owner != Some(owner) {
345 return false;
346 }
347 entry.reconciliation_required = true;
348 true
349 }
350}
351
352impl MetadataTargetRegistration {
353 pub(crate) fn identity(&self) -> MetadataTargetIdentity {
354 self.inner.identity.clone()
355 }
356 pub(crate) fn durable_generation(&self) -> Option<MetadataGeneration> {
357 self.inner.durable_generation
358 }
359
360 pub(crate) fn record_durable(&self, generation: MetadataGeneration) -> bool {
361 self.inner
362 .registry
363 .record_durable(&self.inner.target, &self.inner.resource, self.inner.owner, generation)
364 }
365
366 pub(crate) fn record_reconciliation_required(&self) -> bool {
367 self.inner
368 .registry
369 .record_reconciliation_required(&self.inner.target, &self.inner.resource, self.inner.owner)
370 }
371
372 pub(crate) fn reconciliation_required(&self) -> bool {
380 self.inner.registry.requires_reconciliation(&self.inner.target)
381 }
382}
383
384impl Drop for RegistrationInner {
385 fn drop(&mut self) {
386 self.registry.release(&self.target, self.owner);
387 }
388}
389
390impl fmt::Debug for MetadataTargetRegistry {
391 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
392 let entries = self.inner.state.lock().targets.len();
393 formatter
394 .debug_struct("MetadataTargetRegistry")
395 .field("entries", &entries)
396 .field("max_entries", &self.inner.max_entries)
397 .finish()
398 }
399}
400
401impl fmt::Debug for MetadataTargetRegistration {
402 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
403 formatter
404 .debug_struct("MetadataTargetRegistration")
405 .field("resource", &self.inner.resource)
406 .finish_non_exhaustive()
407 }
408}
409
410impl fmt::Debug for NormalizedMetadataTarget {
411 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
412 formatter.write_str("<metadata-target>")
413 }
414}
415
416fn normalize_target(target: &Path) -> RuntimeResult<NormalizedMetadataTarget> {
417 let absolute = std::path::absolute(target)
418 .map_err(|source| RuntimeError::io(RuntimeOperation::MetadataResourceTarget, source))?;
419
420 #[cfg(windows)]
421 {
422 let folded = absolute.as_os_str().to_string_lossy().to_lowercase();
423 Ok(NormalizedMetadataTarget(OsString::from(folded)))
424 }
425
426 #[cfg(not(windows))]
427 {
428 Ok(NormalizedMetadataTarget(absolute.into_os_string()))
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn target_generations_are_allocated_by_the_owner_scoped_registry() {
438 let registry = MetadataTargetRegistry::new();
439 assert_eq!(registry.next_generation(), 1);
440 assert_eq!(registry.next_generation(), 2);
441 assert_eq!(registry.next_generation(), 3);
442
443 let shared = registry.clone();
446 assert_eq!(shared.next_generation(), 4);
447 assert_eq!(registry.next_generation(), 5);
448 }
449
450 #[test]
451 fn registration_reports_the_target_reconciliation_fence() {
452 let registry = MetadataTargetRegistry::new();
453 let target = std::env::temp_dir().join("rocketmq-runtime-registration-fence");
454 let outcome = registry.register(&target, Arc::<str>::from("resource")).unwrap();
455 let MetadataTargetRegistrationOutcome::Registered(registration) = outcome else {
456 panic!("a free target should register");
457 };
458
459 assert!(!registration.reconciliation_required());
460 assert!(registration.record_reconciliation_required());
461 assert!(
462 registration.reconciliation_required(),
463 "a cached registration must observe an unconfirmed replacement"
464 );
465 }
466
467 #[test]
468 fn different_resources_cannot_bind_the_same_target() {
469 let registry = MetadataTargetRegistry::new();
470 let target = std::env::temp_dir().join("rocketmq-runtime-target-registry");
471
472 let first = registry.register(&target, Arc::<str>::from("first")).unwrap();
473 assert!(matches!(first, MetadataTargetRegistrationOutcome::Registered(_)));
474 assert!(matches!(
475 registry.register(&target, Arc::<str>::from("second")).unwrap(),
476 MetadataTargetRegistrationOutcome::Conflict
477 ));
478 }
479
480 #[test]
481 fn target_registry_capacity_is_bounded() {
482 let registry = MetadataTargetRegistry::with_max_entries(1);
483 let first = std::env::temp_dir().join("rocketmq-runtime-target-capacity-first");
484 let second = std::env::temp_dir().join("rocketmq-runtime-target-capacity-second");
485
486 assert!(matches!(
487 registry.register(&first, Arc::<str>::from("first")).unwrap(),
488 MetadataTargetRegistrationOutcome::Registered(_)
489 ));
490 let error = registry
491 .register(&second, Arc::<str>::from("second"))
492 .expect_err("target registration capacity must be bounded");
493 assert_eq!(error.condition(), rocketmq_error::CanonicalCondition::ResourceExhausted);
494 }
495
496 #[test]
497 fn registration_release_and_durable_generation_outlive_replacement() {
498 let registry = MetadataTargetRegistry::new();
499 let target = std::env::temp_dir().join("rocketmq-runtime-target-replacement");
500 let resource = Arc::<str>::from("resource");
501
502 let first = match registry.register(&target, resource.clone()).unwrap() {
503 MetadataTargetRegistrationOutcome::Registered(registration) => registration,
504 MetadataTargetRegistrationOutcome::Conflict => panic!("first registration must succeed"),
505 MetadataTargetRegistrationOutcome::ReconciliationRequired => {
506 panic!("first registration requires no reconciliation")
507 }
508 };
509 assert!(first.record_durable(MetadataGeneration::new(7)));
510 assert!(matches!(
511 registry.register(&target, resource.clone()).unwrap(),
512 MetadataTargetRegistrationOutcome::Conflict
513 ));
514
515 drop(first);
516 let replacement = match registry.register(&target, resource).unwrap() {
517 MetadataTargetRegistrationOutcome::Registered(registration) => registration,
518 MetadataTargetRegistrationOutcome::Conflict => panic!("replacement registration must succeed"),
519 MetadataTargetRegistrationOutcome::ReconciliationRequired => {
520 panic!("replacement registration requires no reconciliation")
521 }
522 };
523 assert_eq!(replacement.durable_generation(), Some(MetadataGeneration::new(7)));
524 }
525
526 #[test]
527 fn registration_owner_is_released_only_after_every_clone_is_dropped() {
528 let registry = MetadataTargetRegistry::new();
529 let target = std::env::temp_dir().join("rocketmq-runtime-target-clone");
530 let resource = Arc::<str>::from("resource");
531
532 let first = match registry.register(&target, resource.clone()).unwrap() {
533 MetadataTargetRegistrationOutcome::Registered(registration) => registration,
534 MetadataTargetRegistrationOutcome::Conflict => panic!("first registration must succeed"),
535 MetadataTargetRegistrationOutcome::ReconciliationRequired => {
536 panic!("first registration requires no reconciliation")
537 }
538 };
539 let cloned = first.clone();
540 drop(first);
541 assert!(matches!(
542 registry.register(&target, resource.clone()).unwrap(),
543 MetadataTargetRegistrationOutcome::Conflict
544 ));
545
546 drop(cloned);
547 assert!(matches!(
548 registry.register(&target, resource).unwrap(),
549 MetadataTargetRegistrationOutcome::Registered(_)
550 ));
551 }
552
553 #[test]
554 fn same_resource_can_switch_targets_after_the_writer_is_released() {
555 let registry = MetadataTargetRegistry::new();
556 let first_target = std::env::temp_dir().join("rocketmq-runtime-target-first");
557 let second_target = std::env::temp_dir().join("rocketmq-runtime-target-second");
558 let resource = Arc::<str>::from("resource");
559
560 let first = match registry.register(&first_target, resource.clone()).unwrap() {
561 MetadataTargetRegistrationOutcome::Registered(registration) => registration,
562 MetadataTargetRegistrationOutcome::Conflict => panic!("first registration must succeed"),
563 MetadataTargetRegistrationOutcome::ReconciliationRequired => {
564 panic!("first registration requires no reconciliation")
565 }
566 };
567 drop(first);
568
569 assert!(matches!(
570 registry.register(&second_target, resource).unwrap(),
571 MetadataTargetRegistrationOutcome::Registered(_)
572 ));
573 }
574
575 #[test]
576 fn unknown_commit_outcome_blocks_re_registration() {
577 let registry = MetadataTargetRegistry::new();
578 let target = std::env::temp_dir().join("rocketmq-runtime-target-reconcile");
579 let resource = Arc::<str>::from("resource");
580
581 let registration = match registry.register(&target, resource.clone()).unwrap() {
582 MetadataTargetRegistrationOutcome::Registered(registration) => registration,
583 MetadataTargetRegistrationOutcome::Conflict => panic!("registration must succeed"),
584 MetadataTargetRegistrationOutcome::ReconciliationRequired => {
585 panic!("a fresh target must not require reconciliation")
586 }
587 };
588 assert!(registration.record_reconciliation_required());
589 drop(registration);
590 assert!(matches!(
591 registry.register(&target, resource).unwrap(),
592 MetadataTargetRegistrationOutcome::ReconciliationRequired
593 ));
594 }
595
596 #[test]
597 fn retirement_checks_live_authority_and_history_before_reusing_capacity() {
598 let registry = MetadataTargetRegistry::with_max_entries(1);
599 let path = std::env::temp_dir().join("retirement-history");
600 let resource = Arc::<str>::from("resource");
601 let MetadataTargetRegistrationOutcome::Registered(first) = registry.register(&path, resource.clone()).unwrap()
602 else {
603 panic!("free target");
604 };
605 let identity = first.identity();
606 let target = first.inner.target.clone();
607 let old_owner = first.inner.owner;
608 let closure = first.clone();
609 assert!(first.record_durable(MetadataGeneration::new(1)));
610 drop(first);
611 assert_eq!(
612 registry.retire(&path, &resource, &identity, None).unwrap(),
613 MetadataTargetRetirementOutcome::HistoryChanged
614 );
615 assert_eq!(
616 registry
617 .retire(&path, &resource, &identity, Some(MetadataGeneration::new(1)))
618 .unwrap(),
619 MetadataTargetRetirementOutcome::WorkInProgress
620 );
621 drop(closure);
622 assert_eq!(
623 registry
624 .retire(&path, &resource, &identity, Some(MetadataGeneration::new(1)))
625 .unwrap(),
626 MetadataTargetRetirementOutcome::Retired
627 );
628 let MetadataTargetRegistrationOutcome::Registered(next) = registry.register(&path, resource.clone()).unwrap()
629 else {
630 panic!("retired capacity");
631 };
632 assert_ne!(identity, next.identity());
633 assert!(!registry.record_durable(&target, &resource, old_owner, MetadataGeneration::new(99)));
634 assert!(!registry.record_reconciliation_required(&target, &resource, old_owner));
635 assert_eq!(next.durable_generation(), None);
636 assert!(!next.reconciliation_required());
637 assert_eq!(registry.stats().retained_targets, 1);
638 }
639
640 #[cfg(windows)]
641 #[test]
642 fn windows_target_identity_is_case_insensitive() {
643 let registry = MetadataTargetRegistry::new();
644 let lower = std::env::temp_dir().join("rocketmq-runtime-target-case");
645 let upper = std::env::temp_dir().join("ROCKETMQ-RUNTIME-TARGET-CASE");
646
647 assert!(matches!(
648 registry.register(&lower, Arc::<str>::from("first")).unwrap(),
649 MetadataTargetRegistrationOutcome::Registered(_)
650 ));
651 assert!(matches!(
652 registry.register(&upper, Arc::<str>::from("second")).unwrap(),
653 MetadataTargetRegistrationOutcome::Conflict
654 ));
655 }
656}