1use crate::background;
2use crate::error_code::ErrorCode;
3use crate::util::FfiSafeStr;
4pub use abi_stable;
5use abi_stable::pmr::{RErr, RResult, RSlice};
6use abi_stable::std_types::{RBox, RHashMap, ROk, ROption, RString, RVec};
7use abi_stable::{sabi_trait, RTuple, StableAbi};
8use serde::de::DeserializeOwned;
9use smol_str::SmolStr;
10use std::collections::HashMap;
11use std::error::Error;
12use std::fmt::Display;
13use std::time::Duration;
14use tarantool::error::TarantoolErrorCode;
15use tarantool::error::{BoxError, IntoBoxError};
16
17#[repr(C)]
19#[derive(StableAbi, Debug)]
20pub struct PicoContext {
21 is_master: bool,
22 pub plugin_name: FfiSafeStr,
23 pub service_name: FfiSafeStr,
24 pub plugin_version: FfiSafeStr,
25 #[sabi(last_prefix_field)]
26 is_raft_leader: bool,
27}
28
29impl PicoContext {
30 #[inline]
31 pub fn new(is_master: bool) -> PicoContext {
32 Self {
33 is_master,
34 plugin_name: "<unset>".into(),
35 service_name: "<unset>".into(),
36 plugin_version: "<unset>".into(),
37 is_raft_leader: false,
38 }
39 }
40
41 #[inline]
42 pub fn with_is_raft_leader(is_master: bool, is_raft_leader: bool) -> PicoContext {
43 Self {
44 is_master,
45 plugin_name: "<unset>".into(),
46 service_name: "<unset>".into(),
47 plugin_version: "<unset>".into(),
48 is_raft_leader,
49 }
50 }
51
52 #[inline]
57 pub unsafe fn clone(&self) -> Self {
58 Self {
59 is_master: self.is_master,
60 plugin_name: self.plugin_name,
61 service_name: self.service_name,
62 plugin_version: self.plugin_version,
63 is_raft_leader: self.is_raft_leader,
64 }
65 }
66
67 #[inline]
69 pub fn is_master(&self) -> bool {
70 self.is_master
71 }
72
73 #[inline]
75 pub fn is_raft_leader(&self) -> bool {
76 self.is_raft_leader
77 }
78
79 #[inline(always)]
80 pub fn register_metrics_callback(&self, callback: impl Fn() -> String) -> Result<(), BoxError> {
81 crate::metrics::register_metrics_handler(self, callback)
82 }
83
84 #[track_caller]
116 #[inline(always)]
117 pub fn register_job<F>(&self, job: F) -> Result<(), BoxError>
118 where
119 F: FnOnce(background::CancellationToken) + 'static,
120 {
121 background::register_job(&self.make_service_id(), job)
122 }
123
124 #[inline(always)]
132 pub fn register_tagged_job<F>(&self, job: F, tag: &str) -> Result<(), BoxError>
133 where
134 F: FnOnce(background::CancellationToken) + 'static,
135 {
136 background::register_tagged_job(&self.make_service_id(), job, tag)
137 }
138
139 #[inline(always)]
151 pub fn cancel_tagged_jobs(&self, tag: &str, timeout: Duration) -> Result<(), BoxError> {
152 let res = background::cancel_jobs_by_tag(&self.make_service_id(), tag, timeout)?;
153 if res.n_timeouts != 0 {
154 #[rustfmt::skip]
155 return Err(BoxError::new(TarantoolErrorCode::Timeout, format!("some background jobs didn't finish in time (expected: {}, timed out: {})", res.n_total, res.n_timeouts)));
156 }
157
158 Ok(())
159 }
160
161 #[inline(always)]
167 pub fn set_jobs_shutdown_timeout(&self, timeout: Duration) {
168 crate::background::set_jobs_shutdown_timeout(
169 self.plugin_name(),
170 self.service_name(),
171 self.plugin_version(),
172 timeout,
173 )
174 }
175
176 #[inline(always)]
177 pub fn make_service_id(&self) -> ServiceId {
178 ServiceId::new(
179 self.plugin_name(),
180 self.service_name(),
181 self.plugin_version(),
182 )
183 }
184
185 #[inline]
186 pub fn plugin_name(&self) -> &str {
187 unsafe { self.plugin_name.as_str() }
189 }
190
191 #[inline]
192 pub fn service_name(&self) -> &str {
193 unsafe { self.service_name.as_str() }
195 }
196
197 #[inline]
198 pub fn plugin_version(&self) -> &str {
199 unsafe { self.plugin_version.as_str() }
201 }
202}
203
204#[derive(Debug, PartialEq, Eq, Hash, Clone)]
206pub struct ServiceId {
207 pub plugin: SmolStr,
208 pub service: SmolStr,
209 pub version: SmolStr,
210}
211
212impl std::fmt::Display for ServiceId {
213 #[inline(always)]
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 write!(f, "{}.{}:v{}", self.plugin, self.service, self.version,)
216 }
217}
218
219impl ServiceId {
220 #[inline(always)]
221 pub fn new(
222 plugin: impl Into<SmolStr>,
223 service: impl Into<SmolStr>,
224 version: impl Into<SmolStr>,
225 ) -> Self {
226 Self {
227 plugin: plugin.into(),
228 service: service.into(),
229 version: version.into(),
230 }
231 }
232
233 #[inline(always)]
234 pub fn plugin(&self) -> &str {
235 &self.plugin
236 }
237
238 #[inline(always)]
239 pub fn service(&self) -> &str {
240 &self.service
241 }
242
243 #[inline(always)]
244 pub fn version(&self) -> &str {
245 &self.version
246 }
247}
248
249pub type ErrorBox = Box<dyn Error>;
253
254pub type CallbackResult<T> = Result<T, ErrorBox>;
255
256pub trait Service {
258 type Config: DeserializeOwned;
260
261 fn on_config_change(
284 &mut self,
285 ctx: &PicoContext,
286 new_config: Self::Config,
287 old_config: Self::Config,
288 ) -> CallbackResult<()> {
289 _ = ctx;
290 _ = new_config;
291 _ = old_config;
292 Ok(())
293 }
294
295 fn on_start(&mut self, context: &PicoContext, config: Self::Config) -> CallbackResult<()> {
311 _ = context;
312 _ = config;
313 Ok(())
314 }
315
316 fn on_stop(&mut self, context: &PicoContext) -> CallbackResult<()> {
328 _ = context;
329 Ok(())
330 }
331
332 #[deprecated(
336 since = "26.2.1",
337 note = "this method gets deprecated, in favor of on_replicaset_leader_change for better naming. Both callbacks will be called until the next major release"
338 )]
339 fn on_leader_change(&mut self, context: &PicoContext) -> CallbackResult<()> {
340 _ = context;
341 Ok(())
342 }
343
344 fn on_replicaset_leader_change(&mut self, context: &PicoContext) -> CallbackResult<()> {
367 _ = context;
368 Ok(())
369 }
370
371 fn on_cluster_leader_change(&mut self, context: &PicoContext) -> CallbackResult<()> {
394 _ = context;
395 Ok(())
396 }
397
398 fn on_health_check(&self, context: &PicoContext) -> CallbackResult<()> {
408 _ = context;
409 Ok(())
410 }
411}
412
413#[sabi_trait]
418pub trait ServiceStable {
419 fn on_health_check(&self, context: &PicoContext) -> RResult<(), ()>;
420 fn on_start(&mut self, context: &PicoContext, configuration: RSlice<u8>) -> RResult<(), ()>;
421 fn on_stop(&mut self, context: &PicoContext) -> RResult<(), ()>;
422 fn on_leader_change(&mut self, context: &PicoContext) -> RResult<(), ()>;
423 #[sabi(last_prefix_field)]
425 fn on_config_change(
426 &mut self,
427 ctx: &PicoContext,
428 new_config: RSlice<u8>,
429 old_config: RSlice<u8>,
430 ) -> RResult<(), ()>;
431 fn on_replicaset_leader_change(&mut self, context: &PicoContext) -> RResult<(), ()> {
432 _ = context;
433 ROk(())
434 }
435 fn on_cluster_leader_change(&mut self, context: &PicoContext) -> RResult<(), ()> {
436 _ = context;
437 ROk(())
438 }
439}
440
441pub struct ServiceProxy<C: DeserializeOwned> {
443 service: Box<dyn Service<Config = C>>,
444}
445
446impl<C: DeserializeOwned> ServiceProxy<C> {
447 pub fn from_service(service: Box<dyn Service<Config = C>>) -> Self {
448 Self { service }
449 }
450}
451
452fn error_into_tt_error<T>(source: impl Display) -> RResult<T, ()> {
458 let tt_error = BoxError::new(ErrorCode::PluginError, source.to_string());
459 tt_error.set_last_error();
460 RErr(())
461}
462
463macro_rules! rtry {
464 ($expr: expr) => {
465 match $expr {
466 Ok(k) => k,
467 Err(e) => return error_into_tt_error(e),
468 }
469 };
470}
471
472impl<C: DeserializeOwned> ServiceStable for ServiceProxy<C> {
473 fn on_health_check(&self, context: &PicoContext) -> RResult<(), ()> {
474 match self.service.on_health_check(context) {
475 Ok(_) => ROk(()),
476 Err(e) => error_into_tt_error(e),
477 }
478 }
479
480 fn on_start(&mut self, context: &PicoContext, configuration: RSlice<u8>) -> RResult<(), ()> {
481 let configuration: C = rtry!(rmp_serde::from_slice(configuration.as_slice()));
482 match self.service.on_start(context, configuration) {
483 Ok(_) => ROk(()),
484 Err(e) => error_into_tt_error(e),
485 }
486 }
487
488 fn on_stop(&mut self, context: &PicoContext) -> RResult<(), ()> {
489 match self.service.on_stop(context) {
490 Ok(_) => ROk(()),
491 Err(e) => error_into_tt_error(e),
492 }
493 }
494
495 fn on_leader_change(&mut self, context: &PicoContext) -> RResult<(), ()> {
496 #[allow(deprecated)]
497 match self.service.on_leader_change(context) {
498 Ok(_) => ROk(()),
499 Err(e) => error_into_tt_error(e),
500 }
501 }
502
503 fn on_replicaset_leader_change(&mut self, context: &PicoContext) -> RResult<(), ()> where {
504 match self.service.on_replicaset_leader_change(context) {
505 Ok(_) => ROk(()),
506 Err(e) => error_into_tt_error(e),
507 }
508 }
509
510 fn on_cluster_leader_change(&mut self, context: &PicoContext) -> RResult<(), ()> {
511 match self.service.on_cluster_leader_change(context) {
512 Ok(_) => ROk(()),
513 Err(e) => error_into_tt_error(e),
514 }
515 }
516
517 fn on_config_change(
518 &mut self,
519 ctx: &PicoContext,
520 new_config: RSlice<u8>,
521 old_config: RSlice<u8>,
522 ) -> RResult<(), ()> {
523 let new_config: C = rtry!(rmp_serde::from_slice(new_config.as_slice()));
524 let old_config: C = rtry!(rmp_serde::from_slice(old_config.as_slice()));
525
526 let res = self.service.on_config_change(ctx, new_config, old_config);
527 match res {
528 Ok(_) => ROk(()),
529 Err(e) => error_into_tt_error(e),
530 }
531 }
532}
533
534pub type ServiceBox = ServiceStable_TO<'static, RBox<()>>;
536
537pub type FnServiceRegistrar = extern "C" fn(registry: &mut ServiceRegistry);
540
541#[sabi_trait]
544trait Factory {
545 fn make(&self) -> ServiceBox;
546}
547
548type FactoryBox = Factory_TO<'static, RBox<()>>;
549
550struct FactoryImpl<S: Service + 'static> {
553 factory_fn: fn() -> S,
554}
555
556impl<S: Service + 'static> Factory for FactoryImpl<S> {
557 fn make(&self) -> ServiceBox {
558 let boxed = Box::new((self.factory_fn)());
559 ServiceBox::from_value(ServiceProxy::from_service(boxed), sabi_trait::TD_Opaque)
560 }
561}
562
563type ServiceIdent = RTuple!(RString, RString);
565
566#[sabi_trait]
571pub trait Validator {
572 fn validate(&self, config: RSlice<u8>) -> RResult<(), ()>;
580}
581
582pub type ValidatorBox = Validator_TO<'static, RBox<()>>;
583
584struct ValidatorImpl<CONFIG: DeserializeOwned + 'static> {
587 func: fn(config: CONFIG) -> CallbackResult<()>,
588}
589
590impl<C: DeserializeOwned> Validator for ValidatorImpl<C> {
591 fn validate(&self, config: RSlice<u8>) -> RResult<(), ()> {
592 let config: C = rtry!(rmp_serde::from_slice(config.as_slice()));
593 let res = (self.func)(config);
594 match res {
595 Ok(_) => ROk(()),
596 Err(e) => error_into_tt_error(e),
597 }
598 }
599}
600
601#[repr(C)]
603#[derive(Default, StableAbi)]
604pub struct ServiceRegistry {
605 services: RHashMap<ServiceIdent, RVec<FactoryBox>>,
606 validators: RHashMap<ServiceIdent, ValidatorBox>,
607}
608
609impl ServiceRegistry {
610 pub fn add<S: Service + 'static>(
618 &mut self,
619 name: &str,
620 plugin_version: &str,
621 factory: fn() -> S,
622 ) {
623 let factory_inner = FactoryImpl {
624 factory_fn: factory,
625 };
626 let factory_inner =
627 FactoryBox::from_value(factory_inner, abi_stable::sabi_trait::TD_Opaque);
628
629 let ident = ServiceIdent::from((RString::from(name), RString::from(plugin_version)));
630
631 if self.validators.get(&ident).is_none() {
632 let validator = ValidatorImpl {
635 func: |_: S::Config| Ok(()),
636 };
637 let validator_stable = ValidatorBox::from_value(validator, sabi_trait::TD_Opaque);
638 self.validators.insert(ident.clone(), validator_stable);
639 }
640
641 let entry = self.services.entry(ident).or_default();
642 entry.push(factory_inner);
643 }
644
645 #[allow(clippy::result_unit_err)]
648 #[cfg(feature = "internal")]
649 pub fn make(&self, service_name: &str, version: &str) -> Result<Option<ServiceBox>, ()> {
650 let ident = ServiceIdent::from((RString::from(service_name), RString::from(version)));
651 let maybe_factories = self.services.get(&ident);
652
653 match maybe_factories {
654 None => Ok(None),
655 Some(factories) if factories.len() == 1 => {
656 Ok(factories.first().map(|factory| factory.make()))
657 }
658 Some(_) => Err(()),
659 }
660 }
661
662 #[allow(clippy::result_unit_err)]
665 #[cfg(feature = "internal")]
666 pub fn contains(&self, service_name: &str, version: &str) -> Result<bool, ()> {
667 let ident = ServiceIdent::from((RString::from(service_name), RString::from(version)));
668 match self.services.get(&ident) {
669 None => Ok(false),
670 Some(factories) if factories.len() == 1 => Ok(true),
671 Some(_) => Err(()),
672 }
673 }
674
675 pub fn add_config_validator<S: Service>(
684 &mut self,
685 service_name: &str,
686 plugin_version: &str,
687 validator: fn(S::Config) -> CallbackResult<()>,
688 ) where
689 S::Config: DeserializeOwned + 'static,
690 {
691 let ident =
692 ServiceIdent::from((RString::from(service_name), RString::from(plugin_version)));
693
694 let validator = ValidatorImpl { func: validator };
695 let validator_stable = ValidatorBox::from_value(validator, sabi_trait::TD_Opaque);
696 self.validators.insert(ident, validator_stable);
697 }
698
699 #[cfg(feature = "internal")]
701 pub fn remove_config_validator(
702 &mut self,
703 service_name: &str,
704 version: &str,
705 ) -> Option<ValidatorBox> {
706 let ident = ServiceIdent::from((RString::from(service_name), RString::from(version)));
707 self.validators.remove(&ident).into_option()
708 }
709
710 #[cfg(feature = "internal")]
712 pub fn dump(&self) -> Vec<(String, String)> {
713 self.services
714 .keys()
715 .map(|key| {
716 let service = key.0.to_string();
717 let version = key.1.to_string();
718 (service, version)
719 })
720 .collect()
721 }
722}
723
724#[sabi_trait]
729pub trait MigrationContextValidator {
730 fn validate(&self, context: RHashMap<RString, RString>) -> RResult<(), ()>;
737
738 fn validate_parameter(&self, name: RString, value: RString) -> RResult<(), ()>;
745}
746
747pub type MigrationContextValidatorBox = MigrationContextValidator_TO<'static, RBox<()>>;
748
749#[derive(Default)]
752struct MigrationContextValidatorImpl {
753 #[allow(clippy::type_complexity)]
754 context_validator: Option<fn(context: HashMap<String, String>) -> CallbackResult<()>>,
757 parameter_validator: Option<fn(name: String, value: String) -> CallbackResult<()>>,
760}
761
762impl MigrationContextValidator for MigrationContextValidatorImpl {
763 fn validate(&self, context: RHashMap<RString, RString>) -> RResult<(), ()> {
764 let Some(context_validator) = self.context_validator else {
765 return ROk(());
767 };
768 let context = context
769 .into_iter()
770 .map(|tuple| (tuple.0.into(), tuple.1.into()))
771 .collect();
772 match (context_validator)(context) {
773 Ok(_) => ROk(()),
774 Err(e) => error_into_tt_error(e),
775 }
776 }
777
778 fn validate_parameter(&self, name: RString, value: RString) -> RResult<(), ()> {
779 let Some(parameter_validator) = self.parameter_validator else {
780 return ROk(());
782 };
783 match (parameter_validator)(name.into(), value.into()) {
784 Ok(_) => ROk(()),
785 Err(e) => error_into_tt_error(e),
786 }
787 }
788}
789
790#[repr(C)]
793#[derive(StableAbi, Default)]
794pub struct MigrationValidator {
795 context_validator: ROption<MigrationContextValidatorBox>,
796}
797
798impl MigrationValidator {
799 pub fn set_context_validator(
804 &mut self,
805 context_validator_fn: fn(context: HashMap<String, String>) -> CallbackResult<()>,
806 ) {
807 match &mut self.context_validator {
808 ROption::RNone => {
809 let validator_inner = MigrationContextValidatorImpl {
810 context_validator: Some(context_validator_fn),
811 parameter_validator: None,
812 };
813 let validator_inner = MigrationContextValidatorBox::from_value(
814 validator_inner,
815 abi_stable::sabi_trait::TD_CanDowncast,
816 );
817
818 self.context_validator = ROption::RSome(validator_inner);
819 }
820 ROption::RSome(mcv) => {
821 let mcv_impl = mcv
822 .obj
823 .downcast_as_mut::<MigrationContextValidatorImpl>()
824 .expect(
825 "downcasting should not fail, because it uses the same struct for creating the obj",
826 );
827
828 mcv_impl.context_validator = Some(context_validator_fn);
829 }
830 }
831 }
832
833 pub fn set_context_parameter_validator(
838 &mut self,
839 parameter_validator_fn: fn(name: String, value: String) -> CallbackResult<()>,
840 ) {
841 match &mut self.context_validator {
842 ROption::RNone => {
843 let validator_inner = MigrationContextValidatorImpl {
844 context_validator: None,
845 parameter_validator: Some(parameter_validator_fn),
846 };
847 let validator_inner = MigrationContextValidatorBox::from_value(
848 validator_inner,
849 abi_stable::sabi_trait::TD_CanDowncast,
850 );
851
852 self.context_validator = ROption::RSome(validator_inner);
853 }
854 ROption::RSome(mcv) => {
855 let mcv_impl = mcv
856 .obj
857 .downcast_as_mut::<MigrationContextValidatorImpl>()
858 .expect(
859 "downcasting should not fail, because it uses the same struct for creating the obj",
860 );
861
862 mcv_impl.parameter_validator = Some(parameter_validator_fn);
863 }
864 }
865 }
866
867 #[cfg(feature = "internal")]
868 pub fn migration_context_validator(&mut self) -> MigrationContextValidatorBox {
869 if let ROption::RSome(mcv) = self.context_validator.take() {
870 return mcv;
871 }
872
873 let validator = MigrationContextValidatorImpl::default();
875 let validator_stable =
876 MigrationContextValidatorBox::from_value(validator, sabi_trait::TD_Opaque);
877 validator_stable
878 }
879}
880
881pub type FnMigrationValidator = extern "C" fn(validator: &mut MigrationValidator);