Skip to main content

tenferro_runtime/runtime/
extension.rs

1use std::collections::{hash_map::DefaultHasher, BTreeMap};
2use std::fmt;
3use std::hash::Hasher;
4use std::sync::Arc;
5
6use super::identity::validate_identifier;
7use super::{
8    CacheOwnerId, EngineId, ExtensionEngine, ExtensionPlanningConfig, RegistrationIdentity,
9    RuntimeCacheOwner,
10};
11use super::{
12    ExecutionContextIdentity, ExtensionModuleError, IdentityError, IdentityKind, RegistrationKey,
13    RuntimeConfigError,
14};
15
16pub(super) type ExtensionFamilyId = &'static str;
17
18/// Validated extension module identifier.
19///
20/// # Examples
21///
22/// ```
23/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
24/// use tenferro_runtime::ExtensionModuleId;
25///
26/// assert_eq!(ExtensionModuleId::new("tenferro.module.test")?.as_str(), "tenferro.module.test");
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
31pub struct ExtensionModuleId(Arc<str>);
32
33impl ExtensionModuleId {
34    /// Validate a lowercase ASCII namespaced extension module identifier.
35    ///
36    /// # Errors
37    ///
38    /// Returns [`IdentityError`] when `value` does not match the runtime
39    /// identifier grammar.
40    pub fn new(value: impl Into<Arc<str>>) -> Result<Self, IdentityError> {
41        validate_identifier(value.into(), IdentityKind::ExtensionModule).map(Self)
42    }
43
44    /// Borrow the validated identifier text.
45    pub fn as_str(&self) -> &str {
46        &self.0
47    }
48}
49
50/// Transactional extension module.
51pub trait ExtensionModule: fmt::Debug + Send + Sync + 'static {
52    /// Return this module's validated ID.
53    fn module_id(&self) -> &ExtensionModuleId;
54
55    /// Register extension engines, planning configs, and cache owners.
56    ///
57    /// # Errors
58    ///
59    /// Returns [`ExtensionModuleError`] when this module's transaction is
60    /// internally inconsistent.
61    fn configure(
62        &self,
63        registrar: &mut ExtensionModuleRegistrar<'_>,
64    ) -> Result<(), ExtensionModuleError>;
65}
66
67pub(super) struct CandidateModuleRecord {
68    pub(super) module: Arc<dyn ExtensionModule>,
69    pub(super) engines: BTreeMap<(ExtensionFamilyId, EngineId), CandidateExtensionEngine>,
70    pub(super) configs: BTreeMap<EngineId, Arc<dyn ExtensionPlanningConfig>>,
71    pub(super) owners: BTreeMap<CacheOwnerId, Arc<dyn RuntimeCacheOwner>>,
72}
73
74impl fmt::Debug for CandidateModuleRecord {
75    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76        formatter
77            .debug_struct("CandidateModuleRecord")
78            .field("module_id", self.module.module_id())
79            .field("engine_count", &self.engines.len())
80            .field("config_count", &self.configs.len())
81            .field("owner_count", &self.owners.len())
82            .finish_non_exhaustive()
83    }
84}
85
86impl Clone for CandidateModuleRecord {
87    fn clone(&self) -> Self {
88        Self {
89            module: Arc::clone(&self.module),
90            engines: self.engines.clone(),
91            configs: self.configs.clone(),
92            owners: self.owners.clone(),
93        }
94    }
95}
96
97impl CandidateModuleRecord {
98    pub(super) fn module_identical(&self, module: &Arc<dyn ExtensionModule>) -> bool {
99        Arc::ptr_eq(&self.module, module)
100    }
101}
102
103pub(super) struct CandidateExtensionEngine {
104    pub(super) engine: Arc<dyn ExtensionEngine>,
105    pub(super) identity: CandidateRegistrationIdentity,
106}
107
108#[derive(Clone, Debug)]
109pub(super) enum CandidateRegistrationIdentity {
110    New,
111    Preserved(RegistrationIdentity),
112}
113
114impl Clone for CandidateExtensionEngine {
115    fn clone(&self) -> Self {
116        Self {
117            engine: Arc::clone(&self.engine),
118            identity: self.identity.clone(),
119        }
120    }
121}
122
123impl fmt::Debug for CandidateExtensionEngine {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        formatter
126            .debug_struct("CandidateExtensionEngine")
127            .field("family_id", &self.engine.family_id())
128            .field("engine_id", self.engine.engine_id())
129            .field("context_identity", &self.engine.context_identity())
130            .field("identity", &self.identity)
131            .finish_non_exhaustive()
132    }
133}
134
135pub(super) struct BoundCandidateExtensionEngine {
136    pub(super) engine: Arc<dyn ExtensionEngine>,
137    pub(super) identity: RegistrationIdentity,
138}
139
140pub(super) struct BoundCandidateModuleRecord {
141    pub(super) module: Arc<dyn ExtensionModule>,
142    pub(super) engines: BTreeMap<(ExtensionFamilyId, EngineId), BoundCandidateExtensionEngine>,
143    pub(super) configs: BTreeMap<EngineId, Arc<dyn ExtensionPlanningConfig>>,
144    pub(super) owners: BTreeMap<CacheOwnerId, Arc<dyn RuntimeCacheOwner>>,
145}
146
147#[derive(Clone)]
148pub(super) struct FrozenExtensionEngineSlot {
149    module_id: ExtensionModuleId,
150    family_id: ExtensionFamilyId,
151    engine_id: EngineId,
152    context_identity: ExecutionContextIdentity,
153    registration_identity: RegistrationIdentity,
154    engine: Arc<dyn ExtensionEngine>,
155    config: Arc<dyn ExtensionPlanningConfig>,
156}
157
158impl fmt::Debug for FrozenExtensionEngineSlot {
159    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
160        formatter
161            .debug_struct("FrozenExtensionEngineSlot")
162            .field("module_id", &self.module_id)
163            .field("family_id", &self.family_id)
164            .field("engine_id", &self.engine_id)
165            .field("context_identity", &self.context_identity)
166            .field("registration_identity", &self.registration_identity)
167            .field("config_retained_bytes", &self.config.retained_bytes())
168            .finish_non_exhaustive()
169    }
170}
171
172#[derive(Clone)]
173pub(super) struct FrozenExtensionSlots {
174    modules: BTreeMap<ExtensionModuleId, Arc<dyn ExtensionModule>>,
175    engines: Arc<[FrozenExtensionEngineSlot]>,
176    by_family_engine: BTreeMap<(ExtensionFamilyId, EngineId), usize>,
177    owners: BTreeMap<(ExtensionModuleId, CacheOwnerId), Arc<dyn RuntimeCacheOwner>>,
178}
179
180pub(super) struct ExtensionEngineSnapshotView<'a> {
181    slot: &'a FrozenExtensionEngineSlot,
182}
183
184impl<'a> ExtensionEngineSnapshotView<'a> {
185    pub(super) fn module_id(&self) -> &'a ExtensionModuleId {
186        &self.slot.module_id
187    }
188
189    pub(super) fn family_id(&self) -> ExtensionFamilyId {
190        self.slot.family_id
191    }
192
193    pub(super) fn engine_id(&self) -> &'a EngineId {
194        &self.slot.engine_id
195    }
196
197    pub(super) fn context_identity(&self) -> ExecutionContextIdentity {
198        self.slot.context_identity
199    }
200
201    pub(super) fn registration_identity(&self) -> RegistrationIdentity {
202        self.slot.registration_identity
203    }
204
205    pub(super) fn engine(&self) -> &'a Arc<dyn ExtensionEngine> {
206        &self.slot.engine
207    }
208
209    pub(super) fn config(&self) -> &'a Arc<dyn ExtensionPlanningConfig> {
210        &self.slot.config
211    }
212}
213
214#[cfg(test)]
215pub(super) type ExtensionSlotFullForTest<'a> = (
216    &'a ExtensionModuleId,
217    ExtensionFamilyId,
218    &'a EngineId,
219    RegistrationIdentity,
220    &'a Arc<dyn ExtensionEngine>,
221    &'a Arc<dyn ExtensionPlanningConfig>,
222);
223
224impl FrozenExtensionSlots {
225    pub(super) fn module_count(&self) -> usize {
226        self.modules.len()
227    }
228
229    pub(super) fn engine_count(&self) -> usize {
230        self.engines.len()
231    }
232
233    pub(super) fn has_family(&self, family_id: ExtensionFamilyId) -> bool {
234        self.engines.iter().any(|slot| slot.family_id == family_id)
235    }
236
237    pub(super) fn to_candidate_modules(
238        &self,
239    ) -> BTreeMap<ExtensionModuleId, CandidateModuleRecord> {
240        let mut modules = BTreeMap::new();
241        for (module_id, module) in &self.modules {
242            modules.insert(
243                module_id.clone(),
244                CandidateModuleRecord {
245                    module: Arc::clone(module),
246                    engines: BTreeMap::new(),
247                    configs: BTreeMap::new(),
248                    owners: BTreeMap::new(),
249                },
250            );
251        }
252        for slot in self.engines.iter() {
253            if let Some(record) = modules.get_mut(&slot.module_id) {
254                record.engines.insert(
255                    (slot.family_id, slot.engine_id.clone()),
256                    CandidateExtensionEngine {
257                        engine: Arc::clone(&slot.engine),
258                        identity: CandidateRegistrationIdentity::Preserved(
259                            slot.registration_identity,
260                        ),
261                    },
262                );
263                record
264                    .configs
265                    .insert(slot.engine_id.clone(), Arc::clone(&slot.config));
266            }
267        }
268        for ((module_id, owner_id), owner) in &self.owners {
269            if let Some(record) = modules.get_mut(module_id) {
270                record.owners.insert(owner_id.clone(), Arc::clone(owner));
271            }
272        }
273        modules
274    }
275
276    pub(super) fn cache_owner_records(
277        &self,
278    ) -> impl Iterator<Item = (CacheOwnerId, Arc<dyn RuntimeCacheOwner>)> + '_ {
279        self.owners.iter().map(|((module_id, owner_id), owner)| {
280            (
281                extension_cache_owner_id(module_id, owner_id),
282                Arc::clone(owner),
283            )
284        })
285    }
286
287    #[cfg(test)]
288    pub(super) fn slots_for_test(
289        &self,
290    ) -> impl Iterator<
291        Item = (
292            &ExtensionModuleId,
293            ExtensionFamilyId,
294            &EngineId,
295            RegistrationIdentity,
296        ),
297    > {
298        self.engines.iter().map(|slot| {
299            (
300                &slot.module_id,
301                slot.family_id,
302                &slot.engine_id,
303                slot.registration_identity,
304            )
305        })
306    }
307
308    #[cfg(test)]
309    pub(super) fn slot_identity_for_test(
310        &self,
311        family_id: ExtensionFamilyId,
312        engine_id: &EngineId,
313    ) -> Option<RegistrationIdentity> {
314        self.extension_engine_slot(family_id, engine_id)
315            .map(|slot| slot.registration_identity)
316    }
317
318    #[cfg(test)]
319    pub(super) fn slot_full_for_test(
320        &self,
321        family_id: ExtensionFamilyId,
322        engine_id: &EngineId,
323    ) -> Option<ExtensionSlotFullForTest<'_>> {
324        self.extension_engine_slot(family_id, engine_id)
325            .map(|slot| {
326                (
327                    &slot.module_id,
328                    slot.family_id,
329                    &slot.engine_id,
330                    slot.registration_identity,
331                    &slot.engine,
332                    &slot.config,
333                )
334            })
335    }
336
337    pub(super) fn slot_for_preparation(
338        &self,
339        family_id: ExtensionFamilyId,
340        engine_id: &EngineId,
341    ) -> Option<ExtensionEngineSnapshotView<'_>> {
342        self.extension_engine_slot(family_id, engine_id)
343            .map(|slot| ExtensionEngineSnapshotView { slot })
344    }
345
346    pub(super) fn has_engine(&self, family_id: ExtensionFamilyId, engine_id: &EngineId) -> bool {
347        self.by_family_engine
348            .contains_key(&(family_id, engine_id.clone()))
349    }
350
351    fn extension_engine_slot(
352        &self,
353        family_id: ExtensionFamilyId,
354        engine_id: &EngineId,
355    ) -> Option<&FrozenExtensionEngineSlot> {
356        self.by_family_engine
357            .get(&(family_id, engine_id.clone()))
358            .map(|&index| &self.engines[index])
359    }
360}
361
362impl fmt::Debug for FrozenExtensionSlots {
363    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
364        formatter
365            .debug_struct("FrozenExtensionSlots")
366            .field("module_count", &self.modules.len())
367            .field("engine_count", &self.engines.len())
368            .field("index_count", &self.by_family_engine.len())
369            .field("owner_count", &self.owners.len())
370            .finish_non_exhaustive()
371    }
372}
373
374pub(super) fn freeze_extension_slots(
375    modules: BTreeMap<ExtensionModuleId, BoundCandidateModuleRecord>,
376) -> Result<FrozenExtensionSlots, RuntimeConfigError> {
377    let mut frozen_modules = BTreeMap::new();
378    let mut engines = Vec::new();
379    let mut by_family_engine = BTreeMap::new();
380    let mut owners = BTreeMap::new();
381
382    for (module_id, module) in modules {
383        frozen_modules.insert(module_id.clone(), Arc::clone(&module.module));
384        for (owner_id, owner) in module.owners {
385            owners.insert((module_id.clone(), owner_id), owner);
386        }
387        for ((family_id, engine_id), record) in module.engines {
388            if by_family_engine
389                .insert((family_id, engine_id.clone()), engines.len())
390                .is_some()
391            {
392                return Err(RuntimeConfigError::ConflictingRegistration {
393                    key: RegistrationKey::ExtensionEngine {
394                        family: family_id,
395                        engine: engine_id,
396                    },
397                });
398            }
399            let config = module.configs.get(&engine_id).cloned().ok_or_else(|| {
400                RuntimeConfigError::ExtensionModule {
401                    source: ExtensionModuleError::MissingPlanningConfig {
402                        module_id: module_id.clone(),
403                        engine_id: engine_id.clone(),
404                    },
405                }
406            })?;
407            engines.push(FrozenExtensionEngineSlot {
408                module_id: module_id.clone(),
409                family_id,
410                engine_id: engine_id.clone(),
411                context_identity: record.engine.context_identity(),
412                registration_identity: record.identity,
413                engine: record.engine,
414                config,
415            });
416        }
417    }
418
419    Ok(FrozenExtensionSlots {
420        modules: frozen_modules,
421        engines: engines.into(),
422        by_family_engine,
423        owners,
424    })
425}
426
427struct ExtensionRegistrationTransaction {
428    module_id: ExtensionModuleId,
429    engines: BTreeMap<(ExtensionFamilyId, EngineId), Arc<dyn ExtensionEngine>>,
430    configs: BTreeMap<EngineId, Arc<dyn ExtensionPlanningConfig>>,
431    owners: BTreeMap<CacheOwnerId, Arc<dyn RuntimeCacheOwner>>,
432}
433
434impl ExtensionRegistrationTransaction {
435    fn new(module_id: ExtensionModuleId) -> Self {
436        Self {
437            module_id,
438            engines: BTreeMap::new(),
439            configs: BTreeMap::new(),
440            owners: BTreeMap::new(),
441        }
442    }
443
444    fn into_candidate(
445        self,
446        module: Arc<dyn ExtensionModule>,
447    ) -> Result<CandidateModuleRecord, ExtensionModuleError> {
448        self.validate()?;
449        let engines = self
450            .engines
451            .into_iter()
452            .map(|(key, engine)| {
453                (
454                    key,
455                    CandidateExtensionEngine {
456                        engine,
457                        identity: CandidateRegistrationIdentity::New,
458                    },
459                )
460            })
461            .collect();
462        Ok(CandidateModuleRecord {
463            module,
464            engines,
465            configs: self.configs,
466            owners: self.owners,
467        })
468    }
469
470    fn validate(&self) -> Result<(), ExtensionModuleError> {
471        for &(family_id, ref engine_id) in self.engines.keys() {
472            match self.configs.get(engine_id) {
473                Some(config) if config.family_id() == family_id => {}
474                Some(config) => {
475                    return Err(ExtensionModuleError::PlanningConfigFamilyMismatch {
476                        module_id: self.module_id.clone(),
477                        engine_id: engine_id.clone(),
478                        expected: family_id,
479                        actual: config.family_id(),
480                    });
481                }
482                None => {
483                    return Err(ExtensionModuleError::MissingPlanningConfig {
484                        module_id: self.module_id.clone(),
485                        engine_id: engine_id.clone(),
486                    });
487                }
488            }
489        }
490        for engine_id in self.configs.keys() {
491            if engine_match_count(&self.engines, engine_id) != 1 {
492                return Err(ExtensionModuleError::PlanningConfigWithoutEngine {
493                    module_id: self.module_id.clone(),
494                    engine_id: engine_id.clone(),
495                });
496            }
497        }
498        Ok(())
499    }
500}
501
502pub(super) fn bind_candidate_module(
503    module: CandidateModuleRecord,
504    allocate_identity: &mut impl FnMut() -> Result<RegistrationIdentity, RuntimeConfigError>,
505) -> Result<BoundCandidateModuleRecord, RuntimeConfigError> {
506    let engines = module
507        .engines
508        .into_iter()
509        .map(|(key, record)| {
510            let identity = match record.identity {
511                CandidateRegistrationIdentity::New => allocate_identity()?,
512                CandidateRegistrationIdentity::Preserved(identity) => identity,
513            };
514            Ok((
515                key,
516                BoundCandidateExtensionEngine {
517                    engine: record.engine,
518                    identity,
519                },
520            ))
521        })
522        .collect::<Result<BTreeMap<_, _>, RuntimeConfigError>>()?;
523    Ok(BoundCandidateModuleRecord {
524        module: module.module,
525        engines,
526        configs: module.configs,
527        owners: module.owners,
528    })
529}
530
531/// Borrowed registrar for one extension module transaction.
532pub struct ExtensionModuleRegistrar<'a> {
533    transaction: &'a mut ExtensionRegistrationTransaction,
534}
535
536impl ExtensionModuleRegistrar<'_> {
537    /// Register one extension preparation engine.
538    ///
539    /// # Errors
540    ///
541    /// Returns [`ExtensionModuleError::ConflictingEngine`] when a distinct
542    /// engine already occupies the same `(family, engine)` transaction key.
543    pub fn register_engine(
544        &mut self,
545        engine: Arc<dyn ExtensionEngine>,
546    ) -> Result<(), ExtensionModuleError> {
547        let key = (engine.family_id(), engine.engine_id().clone());
548        match self.transaction.engines.get(&key) {
549            Some(existing) if Arc::ptr_eq(existing, &engine) => Ok(()),
550            Some(_) => Err(ExtensionModuleError::ConflictingEngine {
551                module_id: self.transaction.module_id.clone(),
552                family_id: key.0,
553                engine_id: key.1,
554            }),
555            None => {
556                self.transaction.engines.insert(key, engine);
557                Ok(())
558            }
559        }
560    }
561
562    /// Register the planning config for one extension engine.
563    ///
564    /// # Errors
565    ///
566    /// Returns a typed [`ExtensionModuleError`] when the target engine is absent,
567    /// the config family is mismatched, or an unequal config is already present.
568    pub fn register_planning_config(
569        &mut self,
570        engine_id: EngineId,
571        config: Arc<dyn ExtensionPlanningConfig>,
572    ) -> Result<(), ExtensionModuleError> {
573        let Some((family_id, _)) = unique_engine_for_config(&self.transaction.engines, &engine_id)
574        else {
575            return Err(ExtensionModuleError::PlanningConfigWithoutEngine {
576                module_id: self.transaction.module_id.clone(),
577                engine_id,
578            });
579        };
580        if config.family_id() != family_id {
581            return Err(ExtensionModuleError::PlanningConfigFamilyMismatch {
582                module_id: self.transaction.module_id.clone(),
583                engine_id,
584                expected: family_id,
585                actual: config.family_id(),
586            });
587        }
588
589        match self.transaction.configs.get(&engine_id) {
590            Some(existing) if config_payloads_equal(existing.as_ref(), config.as_ref()) => Ok(()),
591            Some(_) => Err(ExtensionModuleError::ConflictingPlanningConfig {
592                module_id: self.transaction.module_id.clone(),
593                engine_id,
594            }),
595            None => {
596                self.transaction.configs.insert(engine_id, config);
597                Ok(())
598            }
599        }
600    }
601
602    /// Register a cache owner owned by this extension module.
603    ///
604    /// # Errors
605    ///
606    /// Returns [`ExtensionModuleError::ConflictingCacheOwner`] when a distinct
607    /// owner already occupies the same local owner ID.
608    pub fn register_cache_owner(
609        &mut self,
610        id: CacheOwnerId,
611        owner: Arc<dyn RuntimeCacheOwner>,
612    ) -> Result<(), ExtensionModuleError> {
613        match self.transaction.owners.get(&id) {
614            Some(existing) if Arc::ptr_eq(existing, &owner) => Ok(()),
615            Some(_) => Err(ExtensionModuleError::ConflictingCacheOwner {
616                module_id: self.transaction.module_id.clone(),
617                owner: id,
618            }),
619            None => {
620                self.transaction.owners.insert(id, owner);
621                Ok(())
622            }
623        }
624    }
625}
626
627impl fmt::Debug for ExtensionModuleRegistrar<'_> {
628    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
629        formatter
630            .debug_struct("ExtensionModuleRegistrar")
631            .field("module_id", &self.transaction.module_id)
632            .field("engine_count", &self.transaction.engines.len())
633            .field("config_count", &self.transaction.configs.len())
634            .field("owner_count", &self.transaction.owners.len())
635            .finish_non_exhaustive()
636    }
637}
638
639pub(super) fn configure_module(
640    module: Arc<dyn ExtensionModule>,
641) -> Result<CandidateModuleRecord, ExtensionModuleError> {
642    let module_id = module.module_id().clone();
643    let mut transaction = ExtensionRegistrationTransaction::new(module_id);
644    {
645        let mut registrar = ExtensionModuleRegistrar {
646            transaction: &mut transaction,
647        };
648        module.configure(&mut registrar)?;
649    }
650    transaction.into_candidate(module)
651}
652
653pub(super) fn extension_cache_owner_id(
654    module_id: &ExtensionModuleId,
655    local: &CacheOwnerId,
656) -> CacheOwnerId {
657    let module = module_id.as_str();
658    let local = local.as_str();
659    CacheOwnerId::from_canonical_owner_id(Arc::<str>::from(format!(
660        "extension[{}]:{module}[{}]:{local}",
661        module.len(),
662        local.len(),
663    )))
664}
665
666fn unique_engine_for_config(
667    engines: &BTreeMap<(ExtensionFamilyId, EngineId), Arc<dyn ExtensionEngine>>,
668    engine_id: &EngineId,
669) -> Option<(ExtensionFamilyId, EngineId)> {
670    let mut matches = engines
671        .keys()
672        .filter(|(_, candidate_engine)| candidate_engine == engine_id);
673    let first = matches.next()?;
674    matches.next().is_none().then(|| (first.0, first.1.clone()))
675}
676
677fn engine_match_count(
678    engines: &BTreeMap<(ExtensionFamilyId, EngineId), Arc<dyn ExtensionEngine>>,
679    engine_id: &EngineId,
680) -> usize {
681    engines
682        .keys()
683        .filter(|(_, candidate_engine)| candidate_engine == engine_id)
684        .count()
685}
686
687fn config_payloads_equal(
688    left: &dyn ExtensionPlanningConfig,
689    right: &dyn ExtensionPlanningConfig,
690) -> bool {
691    payload_hash(left) == payload_hash(right) && left.payload_eq(right)
692}
693
694fn payload_hash(config: &dyn ExtensionPlanningConfig) -> u64 {
695    let mut hasher = DefaultHasher::new();
696    config.payload_hash(&mut hasher);
697    hasher.finish()
698}