iceoryx2/service/
mod.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! # Example
14//!
15//! ## Publish-Subscribe
16//!
17//! For a detailed documentation see the
18//! [`publish_subscribe::Builder`](crate::service::builder::publish_subscribe::Builder)
19//!
20//! ```
21//! use iceoryx2::prelude::*;
22//!
23//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
24//! let node = NodeBuilder::new().create::<ipc::Service>()?;
25//!
26//! let service = node.service_builder(&"My/Funk/ServiceName".try_into()?)
27//!     // define the messaging pattern
28//!     .publish_subscribe::<u64>()
29//!     // various QoS
30//!     .enable_safe_overflow(true)
31//!     .subscriber_max_borrowed_samples(1)
32//!     .history_size(2)
33//!     .subscriber_max_buffer_size(3)
34//!     .max_subscribers(4)
35//!     .max_publishers(5)
36//!     // increase the alignment of the payload to 512, interesting for SIMD operations
37//!     .payload_alignment(Alignment::new(512).unwrap())
38//!     // if the service already exists, open it, otherwise create it
39//!     .open_or_create()?;
40//!
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! ## Request-Response
46//!
47//! For a detailed documentation see the
48//! [`request_response::Builder`](crate::service::builder::request_response::Builder)
49//!
50//! ```
51//! use iceoryx2::prelude::*;
52//!
53//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
54//! let node = NodeBuilder::new().create::<ipc::Service>()?;
55//!
56//! let service = node.service_builder(&"ReqResQos".try_into()?)
57//!     .request_response::<u64, u64>()
58//!     // various QoS
59//!     .request_payload_alignment(Alignment::new(128).unwrap())
60//!     .response_payload_alignment(Alignment::new(128).unwrap())
61//!     .enable_safe_overflow_for_requests(true)
62//!     .enable_safe_overflow_for_responses(true)
63//!     .enable_fire_and_forget_requests(true)
64//!     .max_active_requests_per_client(2)
65//!     .max_loaned_requests(1)
66//!     .max_response_buffer_size(4)
67//!     .max_servers(2)
68//!     .max_clients(10)
69//!     // if the service already exists, open it, otherwise create it
70//!     .open_or_create()?;
71//!
72//! # Ok(())
73//! # }
74//! ```
75//!
76//! ## Event
77//!
78//! For a detailed documentation see the
79//! [`event::Builder`](crate::service::builder::event::Builder)
80//!
81//! ```
82//! use iceoryx2::prelude::*;
83//!
84//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
85//! let node = NodeBuilder::new().create::<ipc::Service>()?;
86//!
87//! let event = node.service_builder(&"MyEventName".try_into()?)
88//!     // define the messaging pattern
89//!     .event()
90//!     // various QoS
91//!     .max_notifiers(12)
92//!     .max_listeners(2)
93//!     .event_id_max_value(32)
94//!     .notifier_created_event(EventId::new(999))
95//!     .notifier_dropped_event(EventId::new(0))
96//!     .notifier_dead_event(EventId::new(2000))
97//!     // if the service already exists, open it, otherwise create it
98//!     .open_or_create()?;
99//!
100//! # Ok(())
101//! # }
102//! ```
103//!
104//! ## Service With Custom Configuration
105//!
106//! An individual [`Config`](crate::config::Config) can be attached when the
107//! [`Node`](crate::node::Node) is created and it will be used for every construct created using
108//! this [`Node`](crate::node::Node).
109//!
110//! ```
111//! use iceoryx2::prelude::*;
112//! use iceoryx2_bb_system_types::path::*;
113//!
114//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
115//! let mut custom_config = Config::default();
116//! // adjust the global root path under which every file/directory is stored
117//! custom_config.global.service.directory = "custom_path".try_into()?;
118//!
119//! let node = NodeBuilder::new()
120//!     .config(&custom_config)
121//!     .create::<ipc::Service>()?;
122//!
123//! let service = node.service_builder(&"My/Funk/ServiceName".try_into()?)
124//!     .publish_subscribe::<u64>()
125//!     .open_or_create()?;
126//!
127//! # Ok(())
128//! # }
129//! ```
130//!
131//! ## Service With Custom Service Attributes
132//!
133//! Every [`Service`](crate::service::Service) can be created with a set of attributes.
134//!
135//! ```
136//! use iceoryx2::prelude::*;
137//! use iceoryx2::config::Config;
138//!
139//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
140//! let node = NodeBuilder::new().create::<ipc::Service>()?;
141//!
142//! let service_creator = node.service_builder(&"My/Funk/ServiceName".try_into()?)
143//!     .publish_subscribe::<u64>()
144//!     .create_with_attributes(
145//!         // all attributes that are defined when creating a new service are stored in the
146//!         // static config of the service
147//!         &AttributeSpecifier::new()
148//!             .define(&"some attribute key".try_into()?, &"some attribute value".try_into()?)
149//!             .define(&"some attribute key".try_into()?, &"another attribute value for the same key".try_into()?)
150//!             .define(&"another key".try_into()?, &"another value".try_into()?)
151//!     )?;
152//!
153//! let service_open = node.service_builder(&"My/Funk/ServiceName".try_into()?)
154//!     .publish_subscribe::<u64>()
155//!     .open_with_attributes(
156//!         // All attributes that are defined when opening a new service interpreted as
157//!         // requirements.
158//!         // If a attribute key as either a different value or is not set at all, the service
159//!         // cannot be opened. If not specific attributes are required one can skip them completely.
160//!         &AttributeVerifier::new()
161//!             .require(&"another key".try_into()?, &"another value".try_into()?)
162//!             .require_key(&"some attribute key".try_into()?)
163//!     )?;
164//!
165//! # Ok(())
166//! # }
167//! ```
168//!
169//! ## Blackboard
170//!
171//! For a detailed documentation see the
172//! [`blackboard::Creator`](crate::service::builder::blackboard::Creator)
173//!
174//! ```
175//! use iceoryx2::prelude::*;
176//! use iceoryx2_bb_container::byte_string::FixedSizeByteString;
177//!
178//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
179//! let node = NodeBuilder::new().create::<ipc::Service>()?;
180//!
181//! type KeyType = u64;
182//! let service = node.service_builder(&"My/Funk/ServiceName".try_into()?)
183//!     // define the messaging pattern
184//!     .blackboard_creator::<KeyType>()
185//!     // QoS
186//!     .max_readers(4)
187//!     .max_nodes(5)
188//!     // add key-value pairs
189//!     .add::<i32>(0, -9)
190//!     .add::<bool>(5, true)
191//!     .add::<FixedSizeByteString<8>>(17, "Nalalala".try_into().unwrap())
192//!     .add_with_default::<u32>(2)
193//!     // create the service
194//!     .create()?;
195//!
196//! # Ok(())
197//! # }
198//! ```
199
200pub(crate) mod stale_resource_cleanup;
201
202/// The builder to create or open [`Service`]s
203pub mod builder;
204
205/// The dynamic configuration of a [`Service`]
206pub mod dynamic_config;
207
208/// Defines the sample headers for various
209/// [`MessagingPattern`]s
210pub mod header;
211
212/// The messaging patterns with their custom
213/// [`StaticConfig`]
214pub mod messaging_pattern;
215
216/// After the [`Service`] is created the user owns this factory to create the endpoints of the
217/// [`MessagingPattern`], also known as ports.
218pub mod port_factory;
219
220/// Represents the name of a [`Service`]
221pub mod service_name;
222
223/// Represents the unique id of a [`Service`]
224pub mod service_id;
225
226/// Represents the static configuration of a [`Service`]. These are the settings that never change
227/// during the runtime of a service, like:
228///
229///  * name
230///  * data type
231///  * QoS provided when the service was created
232pub mod static_config;
233
234/// Represents static features of a service that can be set when a [`Service`] is created.
235pub mod attribute;
236
237/// A configuration when communicating within a single process or single address space.
238pub mod local;
239
240/// A threadsafe configuration when communicating within a single process or single address space.
241/// All [`Service`] ports implement [`Send`] and [`Sync`], the payload constructs will implement
242/// [`Send`] but at the cost of an additional internal mutex.
243pub mod local_threadsafe;
244
245/// A configuration when communicating between different processes using posix mechanisms.
246pub mod ipc;
247
248/// A threadsafe configuration when communicating between different processes using posix mechanisms.
249/// All [`Service`] ports implement [`Send`] and [`Sync`], the payload constructs will implement
250/// [`Send`] but at the cost of an additional internal mutex.
251pub mod ipc_threadsafe;
252
253pub(crate) mod config_scheme;
254pub(crate) mod naming_scheme;
255
256use alloc::sync::Arc;
257use core::fmt::Debug;
258use core::time::Duration;
259
260use crate::config;
261use crate::constants::MAX_TYPE_NAME_LENGTH;
262use crate::node::{NodeId, NodeListFailure, NodeState, SharedNode};
263use crate::service::config_scheme::dynamic_config_storage_config;
264use crate::service::dynamic_config::DynamicConfig;
265use crate::service::static_config::*;
266use config_scheme::service_tag_config;
267use iceoryx2_bb_container::semantic_string::SemanticString;
268use iceoryx2_bb_elementary::CallbackProgression;
269use iceoryx2_bb_log::{debug, fail, trace, warn};
270use iceoryx2_cal::arc_sync_policy::ArcSyncPolicy;
271use iceoryx2_cal::dynamic_storage::{
272    DynamicStorage, DynamicStorageBuilder, DynamicStorageOpenError,
273};
274use iceoryx2_cal::event::Event;
275use iceoryx2_cal::hash::*;
276use iceoryx2_cal::monitoring::Monitoring;
277use iceoryx2_cal::named_concept::NamedConceptListError;
278use iceoryx2_cal::named_concept::*;
279use iceoryx2_cal::reactor::Reactor;
280use iceoryx2_cal::resizable_shared_memory::ResizableSharedMemoryForPoolAllocator;
281use iceoryx2_cal::serialize::Serialize;
282use iceoryx2_cal::shared_memory::{SharedMemory, SharedMemoryForPoolAllocator};
283use iceoryx2_cal::shm_allocator::bump_allocator::BumpAllocator;
284use iceoryx2_cal::static_storage::*;
285use iceoryx2_cal::zero_copy_connection::ZeroCopyConnection;
286use service_id::ServiceId;
287
288use self::dynamic_config::DeregisterNodeState;
289use self::messaging_pattern::MessagingPattern;
290use self::service_name::ServiceName;
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
293/// Error that can be reported when removing a [`Node`](crate::node::Node).
294pub enum ServiceRemoveNodeError {
295    /// The iceoryx2 version that created the [`Node`](crate::node::Node) does
296    /// not match this iceoryx2 version.
297    VersionMismatch,
298    /// Errors that indicate either an implementation issue or a wrongly configured system.
299    InternalError,
300    /// The [`Node`](crate::node::Node) has opened a [`Service`] that is in a
301    /// corrupted state and therefore it cannot be remove from it.
302    ServiceInCorruptedState,
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub(crate) enum ServiceRemoveTagError {
307    AlreadyRemoved,
308    InternalError,
309    InsufficientPermissions,
310}
311
312/// Failure that can be reported when the [`ServiceDetails`] are acquired with [`Service::details()`].
313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
314pub enum ServiceDetailsError {
315    /// The underlying static [`Service`] information could not be opened.
316    FailedToOpenStaticServiceInfo,
317    /// The underlying static [`Service`] information could not be read.
318    FailedToReadStaticServiceInfo,
319    /// The underlying static [`Service`] information could not be deserialized. Can be caused by
320    /// version mismatch or a corrupted file.
321    FailedToDeserializeStaticServiceInfo,
322    /// Required [`Service`] resources are not available or corrupted.
323    ServiceInInconsistentState,
324    /// The [`Service`] was created with a different iceoryx2 version.
325    VersionMismatch,
326    /// Errors that indicate either an implementation issue or a wrongly configured system.
327    InternalError,
328    /// The [`NodeState`] could not be acquired.
329    FailedToAcquireNodeState,
330}
331
332impl core::fmt::Display for ServiceDetailsError {
333    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
334        write!(f, "ServiceDetailsError::{self:?}")
335    }
336}
337
338impl core::error::Error for ServiceDetailsError {}
339
340/// Failure that can be reported by [`Service::list()`].
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum ServiceListError {
343    /// The process has insufficient permissions to list all [`Service`]s.
344    InsufficientPermissions,
345    /// Errors that indicate either an implementation issue or a wrongly configured system.
346    InternalError,
347}
348
349impl core::fmt::Display for ServiceListError {
350    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351        write!(f, "ServiceListError::{self:?}")
352    }
353}
354
355impl core::error::Error for ServiceListError {}
356
357/// Represents all the [`Service`] information that one can acquire with [`Service::list()`]
358/// when the [`Service`] is accessible by the current process.
359#[derive(Debug, Clone)]
360pub struct ServiceDynamicDetails<S: Service> {
361    /// A list of all [`Node`](crate::node::Node)s that are registered at the [`Service`]
362    pub nodes: Vec<NodeState<S>>,
363}
364
365/// Represents all the [`Service`] information that one can acquire with [`Service::list()`].
366#[derive(Debug)]
367pub struct ServiceDetails<S: Service> {
368    /// The static configuration of the [`Service`] that never changes during the [`Service`]
369    /// lifetime.
370    pub static_details: StaticConfig,
371    /// The dynamic configuration of the [`Service`] that can conaints runtime informations.
372    pub dynamic_details: Option<ServiceDynamicDetails<S>>,
373}
374
375/// Represents the [`Service`]s state.
376#[derive(Debug)]
377pub struct ServiceState<S: Service, R: ServiceResource> {
378    // For this struct it is important to know that Rust drops fields of a struct in declaration
379    // order - not in memory order!
380
381    // must be destructed first, to prevent services to open it
382    pub(crate) dynamic_storage: S::DynamicStorage,
383    // must be destructed after the dynamic resources
384    pub(crate) additional_resource: R,
385    pub(crate) static_config: StaticConfig,
386    pub(crate) shared_node: Arc<SharedNode<S>>,
387    // must be destructed last, otherwise other processes might create a new service with the same
388    // name and their resources are then removed by another process while they are creating them
389    // which would end up in a completely corrupted service
390    pub(crate) static_storage: S::StaticStorage,
391}
392
393impl<S: Service, R: ServiceResource> ServiceState<S, R> {
394    pub(crate) fn new(
395        static_config: StaticConfig,
396        shared_node: Arc<SharedNode<S>>,
397        dynamic_storage: S::DynamicStorage,
398        static_storage: S::StaticStorage,
399        additional_resource: R,
400    ) -> Self {
401        let new_self = Self {
402            static_config,
403            shared_node,
404            dynamic_storage,
405            static_storage,
406            additional_resource,
407        };
408        trace!(from "Service::open()", "open service: {} ({:?})",
409            new_self.static_config.name(), new_self.static_config.service_id());
410        new_self
411    }
412}
413
414impl<S: Service, R: ServiceResource> Drop for ServiceState<S, R> {
415    fn drop(&mut self) {
416        let origin = "ServiceState::drop()";
417        let id = self.static_config.service_id();
418        self.shared_node.registered_services().remove(id, |handle| {
419            if let Err(e) = remove_service_tag::<S>(self.shared_node.id(), id, self.shared_node.config())
420            {
421                debug!(from origin, "The service tag could not be removed from the node {:?} ({:?}).",
422                        self.shared_node.id(), e);
423            }
424
425            match self.dynamic_storage.get().deregister_node_id(handle) {
426                DeregisterNodeState::HasOwners => {
427                    trace!(from origin, "close service: {} ({:?})",
428                            self.static_config.name(), id);
429                }
430                DeregisterNodeState::NoMoreOwners => {
431                    self.static_storage.acquire_ownership();
432                    self.dynamic_storage.acquire_ownership();
433                    self.additional_resource.acquire_ownership();
434                    trace!(from origin, "close and remove service: {} ({:?})",
435                            self.static_config.name(), id);
436                }
437            }
438        });
439    }
440}
441
442#[doc(hidden)]
443pub mod internal {
444    use builder::event::EventOpenError;
445    use dynamic_config::{PortCleanupAction, RemoveDeadNodeResult};
446    use iceoryx2_bb_container::byte_string::FixedSizeByteString;
447    use iceoryx2_bb_log::error;
448    use port_factory::PortFactory;
449
450    use crate::{
451        node::{NodeBuilder, NodeId},
452        port::{
453            listener::remove_connection_of_listener, notifier::Notifier,
454            port_identifiers::UniquePortId,
455        },
456        prelude::EventId,
457        service::stale_resource_cleanup::{
458            remove_data_segment_of_port, remove_receiver_port_from_all_connections,
459            remove_sender_port_from_all_connections,
460        },
461    };
462
463    use super::*;
464
465    #[derive(Debug)]
466    struct CleanupFailure;
467
468    fn send_dead_node_signal<S: Service>(service_id: &ServiceId, config: &config::Config) {
469        let origin = "send_dead_node_signal()";
470
471        let service_details = match details::<S>(config, &service_id.0.clone().into()) {
472            Ok(Some(service_details)) => service_details,
473            Ok(None) => return,
474            Err(e) => {
475                warn!(from origin,
476                    "Unable to acquire service details to emit dead node signal to waiting listeners for the service id {:?} due to ({:?})",
477                    service_id, e);
478                return;
479            }
480        };
481
482        let service_name = service_details.static_details.name();
483
484        let mut config = config.clone();
485        config.global.node.cleanup_dead_nodes_on_creation = false;
486        config.global.node.cleanup_dead_nodes_on_destruction = false;
487
488        let node = match NodeBuilder::new().config(&config).create::<S>() {
489            Ok(node) => node,
490            Err(e) => {
491                warn!(from origin,
492                                "Unable to create node to emit dead node signal to waiting listeners on the service {} due to ({:?}).",
493                                service_name, e);
494                return;
495            }
496        };
497
498        let service = match node.service_builder(service_name).event().open() {
499            Ok(service) => service,
500            Err(EventOpenError::DoesNotExist) => return,
501            Err(e) => {
502                warn!(from origin,
503                                "Unable to open event service to emit dead node signal to waiting listeners on the service {} due to ({:?}).",
504                                service_name, e);
505                return;
506            }
507        };
508
509        if service.dynamic_config().number_of_listeners() == 0 {
510            return;
511        }
512
513        let event_id = match service.static_config().notifier_dead_event {
514            Some(event_id) => event_id,
515            None => return,
516        };
517
518        let notifier = match Notifier::new_without_auto_event_emission(
519            service.service,
520            EventId::new(0),
521        ) {
522            Ok(notifier) => notifier,
523            Err(e) => {
524                warn!(from origin,
525                                "Unable to create notifier to send dead node signal to waiting listeners on the service {} due to ({:?})",
526                                service_name, e);
527                return;
528            }
529        };
530
531        if let Err(e) = notifier.notify_with_custom_event_id(EventId::new(event_id)) {
532            warn!(from origin,
533                            "Unable to send dead node signal to waiting listeners on service {} due to ({:?})",
534                            service_name, e);
535        }
536
537        trace!(from origin, "Send dead node signal on service {}.", service_name);
538    }
539
540    fn remove_sender_connection_and_data_segment<S: Service>(
541        id: u128,
542        config: &config::Config,
543        origin: &str,
544        port_name: &str,
545    ) -> Result<(), CleanupFailure> {
546        unsafe { remove_sender_port_from_all_connections::<S>(id, config) }.map_err(|e| {
547            debug!(from origin,
548                "Failed to remove the {} ({:?}) from all of its connections ({:?}).",
549                port_name, id, e);
550            CleanupFailure
551        })?;
552
553        unsafe { remove_data_segment_of_port::<S>(id, config) }.map_err(|e| {
554            debug!(from origin,
555                "Failed to remove the {} ({:?}) data segment ({:?}).",
556                port_name, id, e);
557            CleanupFailure
558        })?;
559
560        Ok(())
561    }
562
563    fn remove_sender_and_receiver_connections_and_data_segment<S: Service>(
564        id: u128,
565        config: &config::Config,
566        origin: &str,
567        port_name: &str,
568    ) -> Result<(), CleanupFailure> {
569        remove_sender_connection_and_data_segment::<S>(id, config, origin, port_name)?;
570        unsafe { remove_receiver_port_from_all_connections::<S>(id, config) }.map_err(|e| {
571            debug!(from origin,
572                    "Failed to remove the {} ({:?}) from all of its incoming connections ({:?}).",
573                    port_name, id, e);
574            CleanupFailure
575        })?;
576
577        Ok(())
578    }
579
580    fn remove_additional_blackboard_resources<S: Service>(
581        config: &config::Config,
582        blackboard_name: &FileName,
583        blackboard_payload_config: &<S::BlackboardPayload as NamedConceptMgmt>::Configuration,
584        blackboard_mgmt_name: &FixedSizeByteString<MAX_TYPE_NAME_LENGTH>,
585        origin: &str,
586        msg: &str,
587    ) {
588        match unsafe {
589            <S::BlackboardPayload as NamedConceptMgmt>::remove_cfg(
590                blackboard_name,
591                blackboard_payload_config,
592            )
593        } {
594            Ok(true) => {
595                trace!(from origin, "Remove blackboard payload segment.");
596            }
597            _ => {
598                error!(from origin,
599                                  "{} since the blackboard payload segment cannot be removed - service seems to be in a corrupted state.", msg);
600            }
601        }
602
603        match blackboard_mgmt_name.as_str() {
604            Ok(s) => {
605                // u64 is just a placeholder needed for the DynamicStorageConfiguration; it is
606                // overwritten right below
607                let mut blackboard_mgmt_config =
608                    crate::service::config_scheme::blackboard_mgmt_config::<S, u64>(config);
609                // Safe since the same type name is set when creating the BlackboardMgmt in
610                // Creator::create_impl so we can safely remove the concept.
611                unsafe {
612                    <S::BlackboardMgmt<u64> as DynamicStorage::<u64>>::__internal_set_type_name_in_config(
613                                            &mut blackboard_mgmt_config,
614                                    s
615                                        )
616                };
617                match unsafe {
618                    <S::BlackboardMgmt<u64> as NamedConceptMgmt>::remove_cfg(
619                        blackboard_name,
620                        &blackboard_mgmt_config,
621                    )
622                } {
623                    Ok(true) => {
624                        trace!(from origin, "Remove blackboard mgmt segment.");
625                    }
626                    _ => {
627                        error!(from origin,
628                                            "{} since the blackboard mgmt segment cannot be removed - service seems to be in a corrupted state.", msg);
629                    }
630                }
631            }
632            Err(_) => {
633                error!(from origin, "{} since the blackboard mgmt segment name cannot be acquired.", msg);
634            }
635        }
636    }
637
638    pub trait ServiceInternal<S: Service> {
639        fn __internal_remove_node_from_service(
640            node_id: &NodeId,
641            service_id: &ServiceId,
642            config: &config::Config,
643        ) -> Result<(), ServiceRemoveNodeError> {
644            let origin = format!("Service::remove_node_from_service({node_id:?}, {service_id:?})");
645            let msg = "Unable to remove node from service";
646
647            let dynamic_config = match open_dynamic_config::<S>(config, service_id) {
648                Ok(Some(c)) => c,
649                Ok(None) => {
650                    fail!(from origin,
651                          with ServiceRemoveNodeError::ServiceInCorruptedState,
652                          "{} since the dynamic service segment is missing - service seems to be in a corrupted state.", msg);
653                }
654                Err(ServiceDetailsError::VersionMismatch) => {
655                    fail!(from origin, with ServiceRemoveNodeError::VersionMismatch,
656                        "{} since the service version does not match.", msg);
657                }
658                Err(e) => {
659                    fail!(from origin, with ServiceRemoveNodeError::InternalError,
660                        "{} due to an internal failure ({:?}).", msg, e);
661                }
662            };
663
664            let mut number_of_dead_node_notifications = 0;
665            let cleanup_port_resources = |port_id| {
666                match port_id {
667                    UniquePortId::Publisher(ref id) => {
668                        if remove_sender_connection_and_data_segment::<S>(
669                            id.value(),
670                            config,
671                            &origin,
672                            "publisher",
673                        )
674                        .is_err()
675                        {
676                            return PortCleanupAction::SkipPort;
677                        }
678                    }
679                    UniquePortId::Subscriber(ref id) => {
680                        if let Err(e) = unsafe {
681                            remove_receiver_port_from_all_connections::<S>(id.value(), config)
682                        } {
683                            debug!(from origin, "Failed to remove the subscriber ({:?}) from all of its connections ({:?}).", id, e);
684                            return PortCleanupAction::SkipPort;
685                        }
686                    }
687                    UniquePortId::Notifier(_) => {
688                        number_of_dead_node_notifications += 1;
689                    }
690                    UniquePortId::Listener(ref id) => {
691                        if let Err(e) = unsafe { remove_connection_of_listener::<S>(id, config) } {
692                            debug!(from origin, "Failed to remove the listeners ({:?}) connection ({:?}).", id, e);
693                            return PortCleanupAction::SkipPort;
694                        }
695                    }
696                    UniquePortId::Client(ref id) => {
697                        if remove_sender_and_receiver_connections_and_data_segment::<S>(
698                            id.value(),
699                            config,
700                            &origin,
701                            "client",
702                        )
703                        .is_err()
704                        {
705                            return PortCleanupAction::SkipPort;
706                        }
707                    }
708                    UniquePortId::Server(ref id) => {
709                        if remove_sender_and_receiver_connections_and_data_segment::<S>(
710                            id.value(),
711                            config,
712                            &origin,
713                            "server",
714                        )
715                        .is_err()
716                        {
717                            return PortCleanupAction::SkipPort;
718                        }
719                    }
720                    UniquePortId::Reader(ref _id) => {}
721                    UniquePortId::Writer(ref _id) => {}
722                };
723
724                trace!(from origin, "Remove port {:?} from service.", port_id);
725                PortCleanupAction::RemovePort
726            };
727
728            let remove_service = match unsafe {
729                dynamic_config
730                    .get()
731                    .remove_dead_node_id(node_id, cleanup_port_resources)
732            } {
733                Ok(DeregisterNodeState::HasOwners) => false,
734                Ok(DeregisterNodeState::NoMoreOwners) => true,
735                Err(RemoveDeadNodeResult::NodeNotRegistered) => {
736                    dynamic_config.get().is_marked_for_destruction()
737                }
738            };
739
740            if remove_service {
741                // check if service was a blackboard service to remove its additional resources
742                let blackboard_name =
743                    crate::service::naming_scheme::blackboard_name(service_id.as_str());
744                let blackboard_payload_config =
745                    crate::service::config_scheme::blackboard_data_config::<S>(config);
746                let blackboard_payload = <S::BlackboardPayload as NamedConceptMgmt>::does_exist_cfg(
747                    &blackboard_name,
748                    &blackboard_payload_config,
749                );
750                let mut is_blackboard = false;
751                let mut blackboard_mgmt_name = FixedSizeByteString::<MAX_TYPE_NAME_LENGTH>::new();
752                if let Ok(true) = blackboard_payload {
753                    is_blackboard = true;
754
755                    let details = match details::<S>(config, &service_id.0.clone().into()) {
756                        Ok(Some(d)) => d,
757                        _ => {
758                            fail!(from origin,
759                                  with ServiceRemoveNodeError::ServiceInCorruptedState,
760                                  "{} due to a failure while acquiring the service details.", msg);
761                        }
762                    };
763                    blackboard_mgmt_name =
764                        details.static_details.blackboard().type_details.type_name;
765                }
766
767                match unsafe {
768                    // IMPORTANT: The static service config must be removed first. If it cannot be
769                    // removed, the process may lack sufficient permissions and should not remove
770                    // any other resources.
771                    remove_static_service_config::<S>(config, &service_id.0.clone().into())
772                } {
773                    Ok(_) => {
774                        trace!(from origin, "Remove unused service.");
775
776                        // remove additional blackboard resources
777                        if is_blackboard {
778                            remove_additional_blackboard_resources::<S>(
779                                config,
780                                &blackboard_name,
781                                &blackboard_payload_config,
782                                &blackboard_mgmt_name,
783                                &origin,
784                                msg,
785                            );
786                        }
787
788                        dynamic_config.acquire_ownership()
789                    }
790                    Err(e) => {
791                        error!(from origin, "Unable to remove static config of unused service ({:?}).",
792                            e);
793                    }
794                }
795            } else if number_of_dead_node_notifications != 0 {
796                send_dead_node_signal::<S>(service_id, config);
797            }
798
799            Ok(())
800        }
801    }
802}
803
804/// Represents additional resources a service could use and have to be cleaned up when no owners
805/// are left
806pub trait ServiceResource {
807    /// Acquires the ownership of the additional resources. When the objects go out of scope the
808    /// underlying resources will be removed.
809    fn acquire_ownership(&self);
810}
811
812#[derive(Debug)]
813pub(crate) struct NoResource;
814impl ServiceResource for NoResource {
815    fn acquire_ownership(&self) {}
816}
817
818/// Represents a service. Used to create or open new services with the
819/// [`crate::node::Node::service_builder()`].
820/// Contains the building blocks a [`Service`] requires to create the underlying resources and
821/// establish communication.
822#[allow(private_bounds)]
823pub trait Service: Debug + Sized + internal::ServiceInternal<Self> + Clone {
824    /// Every service name will be hashed, to allow arbitrary [`ServiceName`]s with as less
825    /// restrictions as possible. The hash of the [`ServiceName`] is the [`Service`]s uuid.
826    type ServiceNameHasher: Hash;
827
828    /// Defines the construct that is used to store the [`StaticConfig`] of the [`Service`]
829    type StaticStorage: StaticStorage;
830
831    /// Sets the serializer that is used to serialize the [`StaticConfig`] into the [`StaticStorage`]
832    type ConfigSerializer: Serialize;
833
834    /// Defines the construct used to store the [`Service`]s dynamic configuration. This
835    /// contains for instance all endpoints and other dynamic details.
836    type DynamicStorage: DynamicStorage<DynamicConfig>;
837
838    /// The memory used to store the payload.
839    type SharedMemory: SharedMemoryForPoolAllocator;
840
841    /// The dynamic memory used to store dynamic payload
842    type ResizableSharedMemory: ResizableSharedMemoryForPoolAllocator<Self::SharedMemory>;
843
844    /// The connection used to exchange pointers to the payload
845    type Connection: ZeroCopyConnection;
846
847    /// The mechanism used to signal events between endpoints.
848    type Event: Event;
849
850    /// Monitoring mechanism to detect dead processes.
851    type Monitoring: Monitoring;
852
853    /// Event multiplexing mechanisms to wait on multiple events.
854    type Reactor: Reactor;
855
856    /// Defines the thread-safety policy of the service. If it is defined as
857    /// [`MutexProtected`](iceoryx2_cal::arc_sync_policy::mutex_protected::MutexProtected), the
858    /// [`Service`]s ports are threadsafe and the payload can be moved into threads. If it is set
859    /// to [`SingleThreaded`](iceoryx2_cal::arc_sync_policy::single_threaded::SingleThreaded),
860    /// the [`Service`]s ports and payload cannot be shared ([`Sync`]) between threads or moved
861    /// ([`Send`]) into other threads.
862    type ArcThreadSafetyPolicy<T: Send + Debug>: ArcSyncPolicy<T>;
863
864    /// Defines the construct used to store the management data of the blackboard service.
865    type BlackboardMgmt<T: Send + Sync + Debug + 'static>: DynamicStorage<T>;
866
867    /// Defines the construct used to store the payload data of the blackboard service.
868    type BlackboardPayload: SharedMemory<BumpAllocator>;
869
870    /// Checks if a service under a given [`config::Config`] does exist
871    ///
872    /// # Example
873    ///
874    /// ```
875    /// use iceoryx2::prelude::*;
876    /// use iceoryx2::config::Config;
877    ///
878    /// # fn main() -> Result<(), Box<dyn core::error::Error>> {
879    /// let name = ServiceName::new("Some/Name")?;
880    /// let does_name_exist =
881    ///     ipc::Service::does_exist(
882    ///                 &name,
883    ///                 Config::global_config(),
884    ///                 MessagingPattern::Event)?;
885    /// # Ok(())
886    /// # }
887    /// ```
888    fn does_exist(
889        service_name: &ServiceName,
890        config: &config::Config,
891        messaging_pattern: MessagingPattern,
892    ) -> Result<bool, ServiceDetailsError> {
893        Ok(Self::details(service_name, config, messaging_pattern)?.is_some())
894    }
895
896    /// Acquires the [`ServiceDetails`] of a [`Service`].
897    ///
898    /// # Example
899    ///
900    /// ```
901    /// use iceoryx2::prelude::*;
902    /// use iceoryx2::config::Config;
903    ///
904    /// # fn main() -> Result<(), Box<dyn core::error::Error>> {
905    /// let name = ServiceName::new("Some/Name")?;
906    /// let details =
907    ///     ipc::Service::details(
908    ///                 &name,
909    ///                 Config::global_config(),
910    ///                 MessagingPattern::Event)?;
911    ///
912    /// if let Some(details) = details {
913    ///     println!("Service details: {:?}", details);
914    /// }
915    /// # Ok(())
916    /// # }
917    /// ```
918    fn details(
919        service_name: &ServiceName,
920        config: &config::Config,
921        messaging_pattern: MessagingPattern,
922    ) -> Result<Option<ServiceDetails<Self>>, ServiceDetailsError> {
923        let service_id = ServiceId::new::<Self::ServiceNameHasher>(service_name, messaging_pattern);
924        details::<Self>(config, &service_id.0.into())
925    }
926
927    /// Returns a list of all services created under a given [`config::Config`].
928    ///
929    /// # Example
930    ///
931    /// ```
932    /// use iceoryx2::prelude::*;
933    /// use iceoryx2::config::Config;
934    ///
935    /// # fn main() -> Result<(), Box<dyn core::error::Error>> {
936    /// ipc::Service::list(Config::global_config(), |service| {
937    ///     println!("\n{:#?}", &service);
938    ///     CallbackProgression::Continue
939    /// })?;
940    /// # Ok(())
941    /// # }
942    /// ```
943    fn list<F: FnMut(ServiceDetails<Self>) -> CallbackProgression>(
944        config: &config::Config,
945        mut callback: F,
946    ) -> Result<(), ServiceListError> {
947        let msg = "Unable to list all services";
948        let origin = "Service::list_from_config()";
949        let static_storage_config = config_scheme::static_config_storage_config::<Self>(config);
950
951        let service_uuids = fail!(from origin,
952                when <Self::StaticStorage as NamedConceptMgmt>::list_cfg(&static_storage_config),
953                map NamedConceptListError::InsufficientPermissions => ServiceListError::InsufficientPermissions,
954                unmatched ServiceListError::InternalError,
955                "{} due to a failure while collecting all active services for config: {:?}", msg, config);
956
957        for uuid in &service_uuids {
958            if let Ok(Some(service_details)) = details::<Self>(config, uuid) {
959                if callback(service_details) == CallbackProgression::Stop {
960                    break;
961                }
962            }
963        }
964
965        Ok(())
966    }
967}
968
969pub(crate) unsafe fn remove_static_service_config<S: Service>(
970    config: &config::Config,
971    uuid: &FileName,
972) -> Result<bool, NamedConceptRemoveError> {
973    let msg = "Unable to remove static service config";
974    let origin = "Service::remove_static_service_config()";
975    let static_storage_config = config_scheme::static_config_storage_config::<S>(config);
976
977    match <S::StaticStorage as NamedConceptMgmt>::remove_cfg(uuid, &static_storage_config) {
978        Ok(v) => Ok(v),
979        Err(e) => {
980            fail!(from origin, with e, "{msg} due to ({:?}).", e);
981        }
982    }
983}
984
985fn details<S: Service>(
986    config: &config::Config,
987    uuid: &FileName,
988) -> Result<Option<ServiceDetails<S>>, ServiceDetailsError> {
989    let msg = "Unable to acquire service details";
990    let origin = "Service::details()";
991    let static_storage_config = config_scheme::static_config_storage_config::<S>(config);
992
993    let reader = match <<S::StaticStorage as StaticStorage>::Builder as NamedConceptBuilder<
994        S::StaticStorage,
995    >>::new(uuid)
996    .config(&static_storage_config.clone())
997    .has_ownership(false)
998    .open(Duration::ZERO)
999    {
1000        Ok(reader) => reader,
1001        Err(StaticStorageOpenError::DoesNotExist)
1002        | Err(StaticStorageOpenError::InitializationNotYetFinalized) => return Ok(None),
1003        Err(e) => {
1004            fail!(from origin, with ServiceDetailsError::FailedToOpenStaticServiceInfo,
1005                        "{} due to a failure while opening the static service info \"{}\" for reading ({:?})",
1006                        msg, uuid, e);
1007        }
1008    };
1009
1010    let mut content = String::from_utf8(vec![b' '; reader.len() as usize]).unwrap();
1011    if let Err(e) = reader.read(unsafe { content.as_mut_vec().as_mut_slice() }) {
1012        fail!(from origin, with ServiceDetailsError::FailedToReadStaticServiceInfo,
1013                "{} since the static service info \"{}\" could not be read ({:?}).",
1014                msg, uuid, e );
1015    }
1016
1017    let service_config =
1018        match S::ConfigSerializer::deserialize::<StaticConfig>(unsafe { content.as_mut_vec() }) {
1019            Ok(service_config) => service_config,
1020            Err(e) => {
1021                fail!(from origin, with ServiceDetailsError::FailedToDeserializeStaticServiceInfo,
1022                    "{} since the static service info \"{}\" could not be deserialized ({:?}).",
1023                       msg, uuid, e );
1024            }
1025        };
1026
1027    if uuid.as_bytes() != service_config.service_id().0.as_bytes() {
1028        fail!(from origin, with ServiceDetailsError::ServiceInInconsistentState,
1029                "{} since the service {:?} has an inconsistent hash of {} according to config {:?}",
1030                msg, service_config, uuid, config);
1031    }
1032
1033    let dynamic_config = open_dynamic_config::<S>(config, service_config.service_id())?;
1034    let dynamic_details = if let Some(d) = dynamic_config {
1035        let mut nodes = vec![];
1036        d.get().list_node_ids(|node_id| {
1037            match NodeState::new(node_id, config) {
1038                Ok(Some(state)) => nodes.push(state),
1039                Ok(None)
1040                | Err(NodeListFailure::InsufficientPermissions)
1041                | Err(NodeListFailure::Interrupt) => (),
1042                Err(NodeListFailure::InternalError) => {
1043                    debug!(from origin, "Unable to acquire NodeState for service \"{:?}\"", uuid);
1044                }
1045            };
1046            CallbackProgression::Continue
1047        });
1048        Some(ServiceDynamicDetails { nodes })
1049    } else {
1050        None
1051    };
1052
1053    Ok(Some(ServiceDetails {
1054        static_details: service_config,
1055        dynamic_details,
1056    }))
1057}
1058
1059fn open_dynamic_config<S: Service>(
1060    config: &config::Config,
1061    service_id: &ServiceId,
1062) -> Result<Option<S::DynamicStorage>, ServiceDetailsError> {
1063    let origin = format!(
1064        "Service::open_dynamic_details<{}>({:?})",
1065        core::any::type_name::<S>(),
1066        service_id
1067    );
1068    let msg = "Unable to open the services dynamic config";
1069    match
1070            <<S::DynamicStorage as DynamicStorage<
1071                    DynamicConfig,
1072                >>::Builder<'_> as NamedConceptBuilder<
1073                    S::DynamicStorage,
1074                >>::new(&service_id.0.clone().into())
1075                    .config(&dynamic_config_storage_config::<S>(config))
1076                .has_ownership(false)
1077                .open() {
1078            Ok(storage) => Ok(Some(storage)),
1079            Err(DynamicStorageOpenError::DoesNotExist) | Err(DynamicStorageOpenError::InitializationNotYetFinalized) => Ok(None),
1080            Err(DynamicStorageOpenError::VersionMismatch) => {
1081                fail!(from origin, with ServiceDetailsError::VersionMismatch,
1082                    "{} since there is a version mismatch. Please use the same iceoryx2 version for the whole system.", msg);
1083            }
1084            Err(DynamicStorageOpenError::InternalError) => {
1085                fail!(from origin, with ServiceDetailsError::InternalError,
1086                    "{} due to an internal failure while opening the services dynamic config.", msg);
1087            }
1088    }
1089}
1090
1091pub(crate) fn remove_service_tag<S: Service>(
1092    node_id: &NodeId,
1093    service_id: &ServiceId,
1094    config: &config::Config,
1095) -> Result<(), ServiceRemoveTagError> {
1096    let origin = format!(
1097        "remove_service_tag<{}>({:?}, service_id: {:?})",
1098        core::any::type_name::<S>(),
1099        node_id,
1100        service_id
1101    );
1102
1103    match unsafe {
1104        <S::StaticStorage as NamedConceptMgmt>::remove_cfg(
1105            &service_id.0.clone().into(),
1106            &service_tag_config::<S>(config, node_id),
1107        )
1108    } {
1109        Ok(true) => Ok(()),
1110        Ok(false) => {
1111            fail!(from origin, with ServiceRemoveTagError::AlreadyRemoved,
1112                    "The service's tag for the node was already removed. This may indicate a corrupted system!");
1113        }
1114        Err(NamedConceptRemoveError::InternalError) => {
1115            fail!(from origin, with ServiceRemoveTagError::InternalError,
1116                "Unable to remove the service's tag for the node due to an internal error.");
1117        }
1118        Err(NamedConceptRemoveError::InsufficientPermissions) => {
1119            fail!(from origin, with ServiceRemoveTagError::InsufficientPermissions,
1120                "Unable to remove the service's tag for the node due to insufficient permissions.");
1121        }
1122    }
1123}