harmos_runtime/records/
registration.rs1use std::collections::BTreeMap;
18use std::sync::{Arc, Mutex};
19use crate::schema::{Validator, graph::Schema};
23use crate::storage::{ErasedCatalog, SchemaKey, StorageManager};
24use crate::{Error, MemorySize, Result};
25
26const DEFINITIONS: usize = 64;
29const REGISTRY_BYTES: usize = 8 * 1024 * 1024;
30const NAME_BYTES: usize = 1024;
31
32type Key = (String, u32);
37
38#[derive(Debug)]
41pub struct Definition {
42 owner: String,
43 schema: SchemaKey,
44 graph: Arc<Schema>,
45 validation: Validator,
46}
47
48pub(crate) struct Registry {
49 pub(super) storage: Option<StorageManager<ErasedCatalog>>,
50 state: Mutex<State>,
51
52 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
53 pub(super) listeners: Mutex<BTreeMap<String, Arc<super::listeners::Listening>>>,
54}
55
56#[derive(Default)]
57struct State {
58 definitions: BTreeMap<Key, Entry>,
59 bytes: usize,
60 constructing_bytes: usize,
61 constructing: usize,
62}
63
64struct Entry {
65 definition: Arc<Definition>,
66 bytes: usize,
67 ready: bool,
68}
69
70struct Construction {
71 registry: Arc<Registry>,
72 bytes: usize,
73}
74
75pub(super) struct Pending {
76 registry: Arc<Registry>,
77 key: Key,
78 pub(super) definition: Arc<Definition>,
79 committed: bool,
80}
81
82impl Definition {
89 pub fn owner(&self) -> &str {
90 &self.owner
91 }
92
93 pub fn schema(&self) -> &SchemaKey {
94 &self.schema
95 }
96
97 pub fn graph(&self) -> &Schema {
98 &self.graph
99 }
100
101 pub(crate) fn graph_owner(&self) -> Arc<Schema> {
102 Arc::clone(&self.graph)
103 }
104
105 pub fn validate(&self, payload: &[u8]) -> Result<()> {
106 self.validation.validate(payload).map_err(|source| Error::RecordPayload { source })
107 }
108}
109
110impl Registry {
111 pub(crate) fn new(storage: Option<StorageManager<ErasedCatalog>>) -> Self {
112 Self {
113 storage,
114 state: Mutex::new(State::default()),
115 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
116 listeners: Mutex::new(BTreeMap::new()),
117 }
118 }
119
120 pub(super) fn reserve(self: &Arc<Self>, owner: &str, schema: SchemaKey, graph: Arc<Schema>) -> Result<Pending> {
121 schema.require_postcard()?;
122 if !harmos_domain::artifact_subset(owner) || owner.len() > NAME_BYTES || schema.id.as_str().len() > NAME_BYTES {
123 return Err(Error::InvalidInput { field: "record schema", reason: "invalid or oversized identity" });
124 }
125 let settings = harmos_domain::config::SchemaSettings::default();
126 graph.preparation(&settings).map_err(|_| limit())?;
127 let fields_bytes = graph.memory_size().checked_add(2 * std::mem::size_of::<usize>()).ok_or_else(limit)?;
128 {
129 let state = self.state.lock().expect("record schema registry poisoned");
130 if let Some((key, entry)) =
131 state.definitions.iter().find(|((id, version), _)| id == schema.id.as_str() && *version == schema.version.get())
132 {
133 if entry.ready && entry.definition.owner == owner && entry.definition.graph == graph {
134 return Ok(Pending {
135 registry: Arc::clone(self),
136 key: key.clone(),
137 definition: Arc::clone(&entry.definition),
138 committed: true,
139 });
140 }
141 return Err(Error::AlreadyRegistered { domain: "record schema", name: key.0.clone() });
142 }
143 if state.definitions.values().any(|entry| entry.definition.schema.id == schema.id && entry.definition.owner != owner) {
144 return Err(Error::AlreadyRegistered { domain: "record schema owner", name: schema.id.as_str().to_owned() });
145 }
146 }
147 let destination = [
148 std::mem::size_of::<Definition>(),
149 std::mem::size_of::<(Key, Entry)>(),
150 1024,
151 owner.len(),
152 schema.id.as_str().len(),
153 schema.id.as_str().len(),
154 fields_bytes,
155 ]
156 .into_iter()
157 .try_fold(0usize, usize::checked_add)
158 .ok_or_else(limit)?;
159 let compilation = Validator::construction_memory_bytes(&graph, &settings)
160 .map_err(|source| Error::RecordSchema { source: crate::schema::Error::Graph(source) })?;
161 let construction_bytes = destination.checked_add(compilation).ok_or_else(limit)?;
162 let mut construction = Construction::new(self, construction_bytes)?;
163 let validation = Validator::from_schema(&graph, &settings)
165 .map_err(|source| Error::RecordSchema { source: crate::schema::Error::Graph(source) })?;
166 let bytes = destination.checked_add(validation.memory_size()).ok_or_else(limit)?;
167 if bytes > construction_bytes {
168 return Err(limit());
169 }
170 let key = (schema.id.as_str().to_owned(), schema.version.get());
171 let definition = Arc::new(Definition { owner: owner.to_owned(), schema: schema.clone(), graph, validation });
172 let mut state = self.state.lock().expect("record schema registry poisoned");
173 if state.definitions.contains_key(&key) {
174 return Err(Error::AlreadyRegistered { domain: "record schema", name: key.0 });
175 }
176 if state.definitions.values().any(|entry| entry.definition.schema.id == schema.id && entry.definition.owner != owner) {
177 return Err(Error::AlreadyRegistered { domain: "record schema owner", name: key.0 });
178 }
179 construction.transfer(&mut state, bytes);
180 state.definitions.insert(key.clone(), Entry { definition: Arc::clone(&definition), bytes, ready: false });
181 Ok(Pending { registry: Arc::clone(self), key, definition, committed: false })
182 }
183
184 pub(super) fn definition(&self, schema: &SchemaKey) -> Option<Arc<Definition>> {
185 if schema.id.as_str().len() > NAME_BYTES || schema.require_postcard().is_err() {
186 return None;
187 }
188 let state = self.state.lock().expect("record schema registry poisoned");
189 state
190 .definitions
191 .get(&(schema.id.as_str().to_owned(), schema.version.get()))
192 .filter(|entry| entry.ready)
193 .map(|entry| Arc::clone(&entry.definition))
194 }
195
196 pub(super) fn definitions(&self) -> Vec<Arc<Definition>> {
197 self.state
198 .lock()
199 .expect("record schema registry poisoned")
200 .definitions
201 .values()
202 .filter(|entry| entry.ready)
203 .map(|entry| Arc::clone(&entry.definition))
204 .collect()
205 }
206}
207
208impl Construction {
209 fn new(registry: &Arc<Registry>, bytes: usize) -> Result<Self> {
210 let mut state = registry.state.lock().expect("record schema registry poisoned");
211 let available = REGISTRY_BYTES.saturating_sub(state.bytes).saturating_sub(state.constructing_bytes);
212 if state.definitions.len() + state.constructing >= DEFINITIONS || bytes > available {
213 return Err(limit());
214 }
215 state.constructing_bytes += bytes;
216 state.constructing += 1;
217 Ok(Self { registry: Arc::clone(registry), bytes })
218 }
219
220 fn transfer(&mut self, state: &mut State, bytes: usize) {
221 state.constructing_bytes -= self.bytes;
222 state.constructing -= 1;
223 state.bytes += bytes;
224 self.bytes = 0;
225 }
226}
227
228impl Drop for Construction {
229 fn drop(&mut self) {
230 if self.bytes == 0 {
231 return;
232 }
233 let mut state = self.registry.state.lock().expect("record schema registry poisoned");
234 state.constructing_bytes -= self.bytes;
235 state.constructing -= 1;
236 }
237}
238
239impl Pending {
240 pub(super) fn is_committed(&self) -> bool {
241 self.committed
242 }
243
244 pub(super) fn commit(mut self) -> Arc<Definition> {
245 if !self.committed {
246 let mut state = self.registry.state.lock().expect("record schema registry poisoned");
247 let entry = state.definitions.get_mut(&self.key).expect("reserved schema remains owned");
248 entry.ready = true;
249 self.committed = true;
250 }
251 Arc::clone(&self.definition)
252 }
253}
254
255impl Drop for Pending {
256 fn drop(&mut self) {
257 if self.committed {
258 return;
259 }
260 let removed = {
261 let mut state = self.registry.state.lock().expect("record schema registry poisoned");
262 let Some(entry) = state.definitions.get(&self.key) else {
263 return;
264 };
265 if !Arc::ptr_eq(&entry.definition, &self.definition) {
266 return;
267 }
268 let entry = state.definitions.remove(&self.key).expect("reserved schema exists");
269 state.bytes -= entry.bytes;
270 entry
271 };
272 drop(removed);
273 }
274}
275
276fn limit() -> Error {
281 Error::ResourceExhausted { resource: "record schema registry" }
282}
283
284