libdd_common/
mutable_metadata.rs1use alloc::sync::Arc;
22
23use arc_swap::ArcSwap;
24
25#[derive(Clone, Default, Debug, PartialEq, Eq)]
31#[non_exhaustive]
32pub struct MutableMetadata {
33 pub runtime_id: String,
35 pub process_tags: String,
37}
38
39#[derive(Clone, Default, Debug)]
45pub struct MutableMetadataHandle(Arc<ArcSwap<MutableMetadata>>);
46
47impl MutableMetadataHandle {
48 pub fn load(&self) -> arc_swap::Guard<Arc<MutableMetadata>> {
50 self.0.load()
51 }
52
53 pub fn load_full(&self) -> Arc<MutableMetadata> {
55 self.0.load_full()
56 }
57
58 pub fn set_runtime_id(&self, runtime_id: String) {
60 self.update(|mut metadata| {
61 metadata.runtime_id = runtime_id.clone();
62 metadata
63 });
64 }
65
66 pub fn set_process_tags(&self, process_tags: String) {
68 self.update(|mut metadata| {
69 metadata.process_tags = process_tags.clone();
70 metadata
71 });
72 }
73
74 pub fn update<F>(&self, mut f: F)
80 where
81 F: FnMut(MutableMetadata) -> MutableMetadata,
82 {
83 self.0.rcu(|current| {
84 let next = (**current).clone();
85 f(next)
86 });
87 }
88}
89
90impl From<MutableMetadata> for MutableMetadataHandle {
91 fn from(value: MutableMetadata) -> Self {
92 MutableMetadataHandle(Arc::new(ArcSwap::from_pointee(value)))
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn initial_handle() -> MutableMetadataHandle {
101 let handle = MutableMetadataHandle::default();
102 handle.set_runtime_id("rt-1".into());
103 handle.set_process_tags("k1:v1".into());
104 handle
105 }
106
107 #[test]
108 fn handle_propagates_updates_to_all_clones() {
109 let handle = initial_handle();
110 let clone = handle.clone();
111
112 let snapshot = handle.load();
114 assert_eq!(snapshot.runtime_id, "rt-1");
115 assert_eq!(snapshot.process_tags, "k1:v1");
116 drop(snapshot);
117
118 clone.update(|mut metadata| {
120 metadata.runtime_id = "rt-2".into();
121 metadata.process_tags = "k2:v2".into();
122 metadata
123 });
124 let snapshot = handle.load();
125 assert_eq!(snapshot.runtime_id, "rt-2");
126 assert_eq!(snapshot.process_tags, "k2:v2");
127 }
128
129 #[test]
130 fn field_updates_keep_the_other_field() {
131 let handle = initial_handle();
132
133 handle.set_runtime_id("rt-2".into());
134 let snapshot = handle.load();
135 assert_eq!(snapshot.runtime_id, "rt-2");
136 assert_eq!(snapshot.process_tags, "k1:v1");
137 drop(snapshot);
138
139 handle.set_process_tags("k2:v2".into());
140 let snapshot = handle.load();
141 assert_eq!(snapshot.runtime_id, "rt-2");
142 assert_eq!(snapshot.process_tags, "k2:v2");
143 }
144
145 #[test]
146 fn default_handle_yields_empty_values() {
147 let handle = MutableMetadataHandle::default();
148 let snapshot = handle.load();
149 assert!(snapshot.runtime_id.is_empty());
150 assert!(snapshot.process_tags.is_empty());
151 }
152
153 #[test]
154 fn update_modifies_the_current_snapshot() {
155 let handle = initial_handle();
156
157 handle.update(|mut m| {
158 m.runtime_id = "rt-2".into();
159 m
160 });
161 let snapshot = handle.load();
162 assert_eq!(snapshot.runtime_id, "rt-2");
163 assert_eq!(snapshot.process_tags, "k1:v1");
164 }
165
166 #[test]
167 fn update_retries_without_losing_concurrent_changes() {
168 use core::sync::atomic::{AtomicBool, Ordering};
169 use std::sync::Barrier;
170
171 let handle = initial_handle();
172 let snapshot_loaded = Barrier::new(2);
173 let concurrent_update_done = Barrier::new(2);
174 let first_attempt = AtomicBool::new(true);
175
176 std::thread::scope(|scope| {
177 let writer = scope.spawn(|| {
178 handle.update(|mut metadata| {
179 if first_attempt.swap(false, Ordering::SeqCst) {
180 snapshot_loaded.wait();
181 concurrent_update_done.wait();
182 }
183 metadata.runtime_id = "rt-2".into();
184 metadata
185 });
186 });
187 snapshot_loaded.wait();
188 handle.set_process_tags("k2:v2".into());
189 concurrent_update_done.wait();
190 writer.join().unwrap();
191 });
192
193 let snapshot = handle.load();
194 assert_eq!(snapshot.runtime_id, "rt-2");
195 assert_eq!(snapshot.process_tags, "k2:v2");
196 }
197
198 #[test]
199 fn load_full_returns_an_owning_arc() {
200 let handle = initial_handle();
201 let snapshot = handle.load_full();
202 assert_eq!(snapshot.runtime_id, "rt-1");
203 assert_eq!(snapshot.process_tags, "k1:v1");
204 }
205}