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    fn extension_engine_slot(
347        &self,
348        family_id: ExtensionFamilyId,
349        engine_id: &EngineId,
350    ) -> Option<&FrozenExtensionEngineSlot> {
351        self.by_family_engine
352            .get(&(family_id, engine_id.clone()))
353            .map(|&index| &self.engines[index])
354    }
355}
356
357impl fmt::Debug for FrozenExtensionSlots {
358    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
359        formatter
360            .debug_struct("FrozenExtensionSlots")
361            .field("module_count", &self.modules.len())
362            .field("engine_count", &self.engines.len())
363            .field("index_count", &self.by_family_engine.len())
364            .field("owner_count", &self.owners.len())
365            .finish_non_exhaustive()
366    }
367}
368
369pub(super) fn freeze_extension_slots(
370    modules: BTreeMap<ExtensionModuleId, BoundCandidateModuleRecord>,
371) -> Result<FrozenExtensionSlots, RuntimeConfigError> {
372    let mut frozen_modules = BTreeMap::new();
373    let mut engines = Vec::new();
374    let mut by_family_engine = BTreeMap::new();
375    let mut owners = BTreeMap::new();
376
377    for (module_id, module) in modules {
378        frozen_modules.insert(module_id.clone(), Arc::clone(&module.module));
379        for (owner_id, owner) in module.owners {
380            owners.insert((module_id.clone(), owner_id), owner);
381        }
382        for ((family_id, engine_id), record) in module.engines {
383            if by_family_engine
384                .insert((family_id, engine_id.clone()), engines.len())
385                .is_some()
386            {
387                return Err(RuntimeConfigError::ConflictingRegistration {
388                    key: RegistrationKey::ExtensionEngine {
389                        family: family_id,
390                        engine: engine_id,
391                    },
392                });
393            }
394            let config = module.configs.get(&engine_id).cloned().ok_or_else(|| {
395                RuntimeConfigError::ExtensionModule {
396                    source: ExtensionModuleError::MissingPlanningConfig {
397                        module_id: module_id.clone(),
398                        engine_id: engine_id.clone(),
399                    },
400                }
401            })?;
402            engines.push(FrozenExtensionEngineSlot {
403                module_id: module_id.clone(),
404                family_id,
405                engine_id: engine_id.clone(),
406                context_identity: record.engine.context_identity(),
407                registration_identity: record.identity,
408                engine: record.engine,
409                config,
410            });
411        }
412    }
413
414    Ok(FrozenExtensionSlots {
415        modules: frozen_modules,
416        engines: engines.into(),
417        by_family_engine,
418        owners,
419    })
420}
421
422struct ExtensionRegistrationTransaction {
423    module_id: ExtensionModuleId,
424    engines: BTreeMap<(ExtensionFamilyId, EngineId), Arc<dyn ExtensionEngine>>,
425    configs: BTreeMap<EngineId, Arc<dyn ExtensionPlanningConfig>>,
426    owners: BTreeMap<CacheOwnerId, Arc<dyn RuntimeCacheOwner>>,
427}
428
429impl ExtensionRegistrationTransaction {
430    fn new(module_id: ExtensionModuleId) -> Self {
431        Self {
432            module_id,
433            engines: BTreeMap::new(),
434            configs: BTreeMap::new(),
435            owners: BTreeMap::new(),
436        }
437    }
438
439    fn into_candidate(
440        self,
441        module: Arc<dyn ExtensionModule>,
442    ) -> Result<CandidateModuleRecord, ExtensionModuleError> {
443        self.validate()?;
444        let engines = self
445            .engines
446            .into_iter()
447            .map(|(key, engine)| {
448                (
449                    key,
450                    CandidateExtensionEngine {
451                        engine,
452                        identity: CandidateRegistrationIdentity::New,
453                    },
454                )
455            })
456            .collect();
457        Ok(CandidateModuleRecord {
458            module,
459            engines,
460            configs: self.configs,
461            owners: self.owners,
462        })
463    }
464
465    fn validate(&self) -> Result<(), ExtensionModuleError> {
466        for &(family_id, ref engine_id) in self.engines.keys() {
467            match self.configs.get(engine_id) {
468                Some(config) if config.family_id() == family_id => {}
469                Some(config) => {
470                    return Err(ExtensionModuleError::PlanningConfigFamilyMismatch {
471                        module_id: self.module_id.clone(),
472                        engine_id: engine_id.clone(),
473                        expected: family_id,
474                        actual: config.family_id(),
475                    });
476                }
477                None => {
478                    return Err(ExtensionModuleError::MissingPlanningConfig {
479                        module_id: self.module_id.clone(),
480                        engine_id: engine_id.clone(),
481                    });
482                }
483            }
484        }
485        for engine_id in self.configs.keys() {
486            if engine_match_count(&self.engines, engine_id) != 1 {
487                return Err(ExtensionModuleError::PlanningConfigWithoutEngine {
488                    module_id: self.module_id.clone(),
489                    engine_id: engine_id.clone(),
490                });
491            }
492        }
493        Ok(())
494    }
495}
496
497pub(super) fn bind_candidate_module(
498    module: CandidateModuleRecord,
499    allocate_identity: &mut impl FnMut() -> Result<RegistrationIdentity, RuntimeConfigError>,
500) -> Result<BoundCandidateModuleRecord, RuntimeConfigError> {
501    let engines = module
502        .engines
503        .into_iter()
504        .map(|(key, record)| {
505            let identity = match record.identity {
506                CandidateRegistrationIdentity::New => allocate_identity()?,
507                CandidateRegistrationIdentity::Preserved(identity) => identity,
508            };
509            Ok((
510                key,
511                BoundCandidateExtensionEngine {
512                    engine: record.engine,
513                    identity,
514                },
515            ))
516        })
517        .collect::<Result<BTreeMap<_, _>, RuntimeConfigError>>()?;
518    Ok(BoundCandidateModuleRecord {
519        module: module.module,
520        engines,
521        configs: module.configs,
522        owners: module.owners,
523    })
524}
525
526/// Borrowed registrar for one extension module transaction.
527pub struct ExtensionModuleRegistrar<'a> {
528    transaction: &'a mut ExtensionRegistrationTransaction,
529}
530
531impl ExtensionModuleRegistrar<'_> {
532    /// Register one extension preparation engine.
533    ///
534    /// # Errors
535    ///
536    /// Returns [`ExtensionModuleError::ConflictingEngine`] when a distinct
537    /// engine already occupies the same `(family, engine)` transaction key.
538    pub fn register_engine(
539        &mut self,
540        engine: Arc<dyn ExtensionEngine>,
541    ) -> Result<(), ExtensionModuleError> {
542        let key = (engine.family_id(), engine.engine_id().clone());
543        match self.transaction.engines.get(&key) {
544            Some(existing) if Arc::ptr_eq(existing, &engine) => Ok(()),
545            Some(_) => Err(ExtensionModuleError::ConflictingEngine {
546                module_id: self.transaction.module_id.clone(),
547                family_id: key.0,
548                engine_id: key.1,
549            }),
550            None => {
551                self.transaction.engines.insert(key, engine);
552                Ok(())
553            }
554        }
555    }
556
557    /// Register the planning config for one extension engine.
558    ///
559    /// # Errors
560    ///
561    /// Returns a typed [`ExtensionModuleError`] when the target engine is absent,
562    /// the config family is mismatched, or an unequal config is already present.
563    pub fn register_planning_config(
564        &mut self,
565        engine_id: EngineId,
566        config: Arc<dyn ExtensionPlanningConfig>,
567    ) -> Result<(), ExtensionModuleError> {
568        let Some((family_id, _)) = unique_engine_for_config(&self.transaction.engines, &engine_id)
569        else {
570            return Err(ExtensionModuleError::PlanningConfigWithoutEngine {
571                module_id: self.transaction.module_id.clone(),
572                engine_id,
573            });
574        };
575        if config.family_id() != family_id {
576            return Err(ExtensionModuleError::PlanningConfigFamilyMismatch {
577                module_id: self.transaction.module_id.clone(),
578                engine_id,
579                expected: family_id,
580                actual: config.family_id(),
581            });
582        }
583
584        match self.transaction.configs.get(&engine_id) {
585            Some(existing) if config_payloads_equal(existing.as_ref(), config.as_ref()) => Ok(()),
586            Some(_) => Err(ExtensionModuleError::ConflictingPlanningConfig {
587                module_id: self.transaction.module_id.clone(),
588                engine_id,
589            }),
590            None => {
591                self.transaction.configs.insert(engine_id, config);
592                Ok(())
593            }
594        }
595    }
596
597    /// Register a cache owner owned by this extension module.
598    ///
599    /// # Errors
600    ///
601    /// Returns [`ExtensionModuleError::ConflictingCacheOwner`] when a distinct
602    /// owner already occupies the same local owner ID.
603    pub fn register_cache_owner(
604        &mut self,
605        id: CacheOwnerId,
606        owner: Arc<dyn RuntimeCacheOwner>,
607    ) -> Result<(), ExtensionModuleError> {
608        match self.transaction.owners.get(&id) {
609            Some(existing) if Arc::ptr_eq(existing, &owner) => Ok(()),
610            Some(_) => Err(ExtensionModuleError::ConflictingCacheOwner {
611                module_id: self.transaction.module_id.clone(),
612                owner: id,
613            }),
614            None => {
615                self.transaction.owners.insert(id, owner);
616                Ok(())
617            }
618        }
619    }
620}
621
622impl fmt::Debug for ExtensionModuleRegistrar<'_> {
623    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
624        formatter
625            .debug_struct("ExtensionModuleRegistrar")
626            .field("module_id", &self.transaction.module_id)
627            .field("engine_count", &self.transaction.engines.len())
628            .field("config_count", &self.transaction.configs.len())
629            .field("owner_count", &self.transaction.owners.len())
630            .finish_non_exhaustive()
631    }
632}
633
634pub(super) fn configure_module(
635    module: Arc<dyn ExtensionModule>,
636) -> Result<CandidateModuleRecord, ExtensionModuleError> {
637    let module_id = module.module_id().clone();
638    let mut transaction = ExtensionRegistrationTransaction::new(module_id);
639    {
640        let mut registrar = ExtensionModuleRegistrar {
641            transaction: &mut transaction,
642        };
643        module.configure(&mut registrar)?;
644    }
645    transaction.into_candidate(module)
646}
647
648pub(super) fn extension_cache_owner_id(
649    module_id: &ExtensionModuleId,
650    local: &CacheOwnerId,
651) -> CacheOwnerId {
652    let module = module_id.as_str();
653    let local = local.as_str();
654    CacheOwnerId::from_canonical_owner_id(Arc::<str>::from(format!(
655        "extension[{}]:{module}[{}]:{local}",
656        module.len(),
657        local.len(),
658    )))
659}
660
661fn unique_engine_for_config(
662    engines: &BTreeMap<(ExtensionFamilyId, EngineId), Arc<dyn ExtensionEngine>>,
663    engine_id: &EngineId,
664) -> Option<(ExtensionFamilyId, EngineId)> {
665    let mut matches = engines
666        .keys()
667        .filter(|(_, candidate_engine)| candidate_engine == engine_id);
668    let first = matches.next()?;
669    matches.next().is_none().then(|| (first.0, first.1.clone()))
670}
671
672fn engine_match_count(
673    engines: &BTreeMap<(ExtensionFamilyId, EngineId), Arc<dyn ExtensionEngine>>,
674    engine_id: &EngineId,
675) -> usize {
676    engines
677        .keys()
678        .filter(|(_, candidate_engine)| candidate_engine == engine_id)
679        .count()
680}
681
682fn config_payloads_equal(
683    left: &dyn ExtensionPlanningConfig,
684    right: &dyn ExtensionPlanningConfig,
685) -> bool {
686    payload_hash(left) == payload_hash(right) && left.payload_eq(right)
687}
688
689fn payload_hash(config: &dyn ExtensionPlanningConfig) -> u64 {
690    let mut hasher = DefaultHasher::new();
691    config.payload_hash(&mut hasher);
692    hasher.finish()
693}