1extern crate alloc;
23use alloc::boxed::Box;
24use alloc::string::ToString;
25use alloc::sync::Arc;
26use alloc::vec::Vec;
27use core::marker::PhantomData;
28
29#[cfg(feature = "std")]
30use std::sync::Mutex;
31
32use crate::dds_type::DdsType;
33use crate::entity::StatusMask;
34use crate::error::{DdsError, Result};
35#[cfg(feature = "std")]
36use crate::instance_handle::{HANDLE_NIL, InstanceHandle};
37#[cfg(feature = "std")]
38use crate::instance_tracker::InstanceTracker;
39use crate::listener::{ArcDataWriterListener, ArcPublisherListener};
40use crate::qos::{DataWriterQos, PublisherQos};
41#[cfg(feature = "std")]
42use crate::time::{Time, get_current_time};
43use crate::topic::Topic;
44
45#[cfg(feature = "std")]
46use crate::runtime::DcpsRuntime;
47#[cfg(feature = "std")]
48use zerodds_qos::ReliabilityKind;
49#[cfg(feature = "std")]
50use zerodds_rtps::wire_types::EntityId;
51
52#[derive(Debug)]
58pub struct Publisher {
59 pub(crate) inner: Arc<PublisherInner>,
60}
61
62pub(crate) struct PublisherInner {
63 #[cfg(feature = "std")]
66 pub(crate) qos: std::sync::Mutex<PublisherQos>,
67 #[cfg(not(feature = "std"))]
68 #[allow(dead_code)]
69 pub(crate) qos: PublisherQos,
70 pub(crate) entity_state: alloc::sync::Arc<crate::entity::EntityState>,
72 #[cfg(feature = "std")]
76 pub(crate) runtime: Option<Arc<DcpsRuntime>>,
77 #[cfg(feature = "std")]
80 pub(crate) listener: std::sync::Mutex<Option<(ArcPublisherListener, StatusMask)>>,
81 #[cfg(feature = "std")]
86 pub(crate) participant:
87 std::sync::Mutex<Option<alloc::sync::Weak<crate::participant::ParticipantInner>>>,
88 suspended: core::sync::atomic::AtomicBool,
94 #[cfg(feature = "std")]
97 pub(crate) datawriters:
98 std::sync::Mutex<alloc::vec::Vec<crate::instance_handle::InstanceHandle>>,
99}
100
101impl core::fmt::Debug for PublisherInner {
104 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 let listener_present = self.listener.lock().map(|s| s.is_some()).unwrap_or(false);
106 f.debug_struct("PublisherInner")
107 .field("entity_state", &self.entity_state)
108 .field("listener_present", &listener_present)
109 .finish_non_exhaustive()
110 }
111}
112
113impl Publisher {
114 #[cfg(feature = "std")]
115 pub(crate) fn new(qos: PublisherQos, runtime: Option<Arc<DcpsRuntime>>) -> Self {
116 Self {
117 inner: Arc::new(PublisherInner {
118 qos: std::sync::Mutex::new(qos),
119 entity_state: crate::entity::EntityState::new(),
120 runtime,
121 listener: std::sync::Mutex::new(None),
122 participant: std::sync::Mutex::new(None),
123 suspended: core::sync::atomic::AtomicBool::new(false),
124 datawriters: std::sync::Mutex::new(alloc::vec::Vec::new()),
125 }),
126 }
127 }
128
129 #[cfg(not(feature = "std"))]
130 pub(crate) fn new(qos: PublisherQos) -> Self {
131 Self {
132 inner: Arc::new(PublisherInner {
133 qos,
134 entity_state: crate::entity::EntityState::new(),
135 suspended: core::sync::atomic::AtomicBool::new(false),
136 }),
137 }
138 }
139
140 #[cfg(feature = "std")]
143 #[must_use]
144 pub fn contains_writer(&self, handle: crate::instance_handle::InstanceHandle) -> bool {
145 self.inner
146 .datawriters
147 .lock()
148 .map(|v| v.contains(&handle))
149 .unwrap_or(false)
150 }
151
152 #[cfg(feature = "std")]
155 pub fn set_listener(&self, listener: Option<ArcPublisherListener>, mask: StatusMask) {
156 if let Ok(mut slot) = self.inner.listener.lock() {
157 *slot = listener.map(|l| (l, mask));
158 }
159 self.inner.entity_state.set_listener_mask(mask);
160 }
161
162 #[cfg(feature = "std")]
164 #[must_use]
165 pub fn get_listener(&self) -> Option<ArcPublisherListener> {
166 self.inner
167 .listener
168 .lock()
169 .ok()
170 .and_then(|s| s.as_ref().map(|(l, _)| Arc::clone(l)))
171 }
172
173 #[cfg(feature = "std")]
176 pub(crate) fn attach_participant(
177 &self,
178 participant: alloc::sync::Weak<crate::participant::ParticipantInner>,
179 ) {
180 if let Ok(mut slot) = self.inner.participant.lock() {
181 *slot = Some(participant);
182 }
183 }
184
185 #[cfg(feature = "std")]
190 #[must_use]
191 pub(crate) fn snapshot_writer_chain(
192 &self,
193 writer_listener: Option<(ArcDataWriterListener, StatusMask)>,
194 ) -> crate::listener_dispatch::WriterListenerChain {
195 let publisher = self
196 .inner
197 .listener
198 .lock()
199 .ok()
200 .and_then(|s| s.as_ref().map(|(l, m)| (Arc::clone(l), *m)));
201 let participant = {
202 let weak = self.inner.participant.lock().ok().and_then(|s| s.clone());
203 weak.and_then(|w| w.upgrade()).and_then(|inner| {
204 inner
205 .listener
206 .lock()
207 .ok()
208 .and_then(|s| s.as_ref().map(|(l, m)| (Arc::clone(l), *m)))
209 })
210 };
211 crate::listener_dispatch::WriterListenerChain {
212 writer: writer_listener,
213 publisher,
214 participant,
215 }
216 }
217
218 pub fn suspend_publications(&self) {
226 self.inner
227 .suspended
228 .store(true, core::sync::atomic::Ordering::Release);
229 }
230
231 pub fn resume_publications(&self) {
236 self.inner
237 .suspended
238 .store(false, core::sync::atomic::Ordering::Release);
239 }
240
241 #[must_use]
245 pub fn is_suspended(&self) -> bool {
246 self.inner
247 .suspended
248 .load(core::sync::atomic::Ordering::Acquire)
249 }
250
251 pub fn copy_from_topic_qos(
263 dw_qos: &mut DataWriterQos,
264 topic_qos: &crate::qos::TopicQos,
265 ) -> Result<()> {
266 dw_qos.durability = topic_qos.durability;
274 dw_qos.reliability = topic_qos.reliability;
275 Ok(())
276 }
277
278 pub fn create_datawriter<T: DdsType + Send + 'static>(
285 &self,
286 topic: &Topic<T>,
287 qos: DataWriterQos,
288 ) -> Result<DataWriter<T>> {
289 if topic.type_name() != T::TYPE_NAME {
290 return Err(DdsError::BadParameter {
291 what: "topic.type_name mismatch",
292 });
293 }
294 #[cfg(feature = "std")]
295 if let Some(rt) = self.inner.runtime.as_ref() {
296 let reliable = qos.reliability.kind == ReliabilityKind::Reliable;
299 let eid = rt.register_user_writer_kind(
306 crate::runtime::UserWriterConfig {
307 topic_name: topic.name().into(),
308 type_name: T::TYPE_NAME.into(),
309 reliable,
310 durability: qos.durability.kind,
311 deadline: qos.deadline,
312 lifespan: qos.lifespan,
313 liveliness: qos.liveliness,
314 ownership: qos.ownership.kind,
315 ownership_strength: qos.ownership_strength.value,
316 partition: qos.partition.names.clone(),
317 user_data: qos.user_data.value.clone(),
318 topic_data: qos.topic_data.value.clone(),
319 group_data: qos.group_data.value.clone(),
320 type_identifier: T::TYPE_IDENTIFIER.clone(),
322 data_representation_offer: qos.data_representation.clone(),
325 },
326 T::HAS_KEY,
327 )?;
328 {
334 use crate::qos::HistoryKind;
335 let depth = match qos.history.kind {
336 HistoryKind::KeepLast => qos.history.depth.max(1) as usize,
337 HistoryKind::KeepAll => usize::MAX,
338 };
339 let _ = rt.set_user_writer_history_depth(eid, depth);
340 }
341 {
353 use crate::dds_type::Extensibility as E;
354 use zerodds_types::qos::ExtensibilityForRepr as Efr;
355 let efr = match T::EXTENSIBILITY {
356 E::Final => Efr::Final,
357 E::Appendable => Efr::Appendable,
358 E::Mutable => Efr::Mutable,
359 };
360 let _ = rt.set_user_writer_wire_extensibility(eid, efr);
361 }
362 let dw =
363 DataWriter::new_live(topic.clone(), qos, self.inner.clone(), Arc::clone(rt), eid);
364 if dw.big_endian {
368 let _ = rt.set_user_writer_byte_order_override(eid, true);
369 }
370 if let Some(backend) = dw.durability_backend() {
376 let _ = rt.attach_durability_backend(eid, backend);
377 }
378 self.track_writer(dw.entity_state.instance_handle());
379 return Ok(dw);
380 }
381 let dw = DataWriter::new_offline(topic.clone(), qos, self.inner.clone());
382 #[cfg(feature = "std")]
383 self.track_writer(dw.entity_state.instance_handle());
384 Ok(dw)
385 }
386
387 #[cfg(feature = "std")]
388 fn track_writer(&self, handle: crate::instance_handle::InstanceHandle) {
389 if let Ok(mut list) = self.inner.datawriters.lock() {
390 list.push(handle);
391 }
392 if let Ok(slot) = self.inner.participant.lock() {
394 if let Some(weak) = slot.as_ref() {
395 if let Some(p_inner) = weak.upgrade() {
396 if let Ok(mut dws) = p_inner.datawriters.lock() {
397 dws.push(handle);
398 }
399 }
400 }
401 }
402 }
403}
404
405#[cfg(feature = "std")]
410impl crate::entity::Entity for Publisher {
411 type Qos = PublisherQos;
412
413 fn get_qos(&self) -> Self::Qos {
414 self.inner.qos.lock().map(|q| q.clone()).unwrap_or_default()
415 }
416
417 fn set_qos(&self, qos: Self::Qos) -> Result<()> {
418 if let Ok(mut current) = self.inner.qos.lock() {
422 *current = qos;
423 }
424 Ok(())
425 }
426
427 fn enable(&self) -> Result<()> {
428 self.inner.entity_state.enable();
429 Ok(())
430 }
431
432 fn entity_state(&self) -> alloc::sync::Arc<crate::entity::EntityState> {
433 alloc::sync::Arc::clone(&self.inner.entity_state)
434 }
435}
436
437pub struct DataWriter<T: DdsType> {
445 topic: Topic<T>,
446 qos: Mutex<DataWriterQos>,
447 entity_state: Arc<crate::entity::EntityState>,
449 publisher: Arc<PublisherInner>,
452 #[cfg(feature = "std")]
454 listener: Mutex<Option<(ArcDataWriterListener, StatusMask)>>,
455 #[cfg(feature = "std")]
459 last_match_count: std::sync::atomic::AtomicI64,
460 #[cfg(feature = "std")]
462 last_offered_deadline_missed: std::sync::atomic::AtomicU64,
463 #[cfg(feature = "std")]
465 last_liveliness_lost: std::sync::atomic::AtomicU64,
466 #[cfg(feature = "std")]
468 last_offered_incompatible_qos: std::sync::atomic::AtomicI64,
469 queue: Arc<Mutex<Vec<Vec<u8>>>>,
471 #[cfg(feature = "std")]
476 drain_signal: Arc<std::sync::Condvar>,
477 #[cfg(feature = "std")]
478 runtime: Option<Arc<DcpsRuntime>>,
479 #[cfg(feature = "std")]
480 entity_id: Option<EntityId>,
481 #[cfg(feature = "std")]
483 instances: InstanceTracker,
484 #[cfg(feature = "std")]
488 publication_handle: InstanceHandle,
489 #[cfg(feature = "std")]
494 durability_backend: Option<Arc<dyn crate::durability_service::DurabilityBackend>>,
495 #[cfg(feature = "std")]
498 durability_seq: std::sync::atomic::AtomicU64,
499 #[cfg(all(feature = "std", feature = "flatdata-integration"))]
504 pub(crate) flat_backend: Mutex<
505 Option<(
506 Arc<dyn zerodds_flatdata::SlotBackend>,
507 u32, )>,
509 >,
510 #[cfg(feature = "std")]
517 big_endian: bool,
518 _t: PhantomData<fn() -> T>,
519}
520
521impl<T: DdsType> core::fmt::Debug for DataWriter<T> {
522 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
523 f.debug_struct("DataWriter")
524 .field("topic", &self.topic.name())
525 .field("type", &T::TYPE_NAME)
526 .field("qos", &self.qos)
527 .finish_non_exhaustive()
528 }
529}
530
531#[cfg(feature = "std")]
537impl<T: DdsType> Drop for DataWriter<T> {
538 fn drop(&mut self) {
539 if let (Some(rt), Some(eid)) = (self.runtime.as_ref(), self.entity_id) {
540 rt.remove_user_writer(eid);
541 }
542 }
543}
544
545impl<T: DdsType> DataWriter<T> {
546 #[cfg(feature = "std")]
547 fn new_offline(topic: Topic<T>, qos: DataWriterQos, publisher: Arc<PublisherInner>) -> Self {
548 let tracker = InstanceTracker::new();
549 let pub_handle = InstanceHandle::from_raw(0xFFFF_0000_0000_0001);
550 let backend = Self::build_durability_backend(&qos);
551 Self {
552 topic,
553 qos: Mutex::new(qos),
554 entity_state: crate::entity::EntityState::new(),
555 publisher,
556 listener: Mutex::new(None),
557 last_match_count: std::sync::atomic::AtomicI64::new(-1),
558 last_offered_deadline_missed: std::sync::atomic::AtomicU64::new(0),
559 last_liveliness_lost: std::sync::atomic::AtomicU64::new(0),
560 last_offered_incompatible_qos: std::sync::atomic::AtomicI64::new(-1),
561 queue: Arc::new(Mutex::new(Vec::new())),
562 #[cfg(feature = "std")]
563 drain_signal: Arc::new(std::sync::Condvar::new()),
564 runtime: None,
565 entity_id: None,
566 instances: tracker,
567 publication_handle: pub_handle,
568 durability_backend: backend,
569 durability_seq: std::sync::atomic::AtomicU64::new(1),
570 #[cfg(feature = "flatdata-integration")]
571 flat_backend: Mutex::new(None),
572 big_endian: std::env::var_os("ZERODDS_WIRE_BIG_ENDIAN").is_some(),
573 _t: PhantomData,
574 }
575 }
576
577 #[doc(hidden)]
580 #[cfg(feature = "std")]
581 #[must_use]
582 pub fn durability_backend(
583 &self,
584 ) -> Option<Arc<dyn crate::durability_service::DurabilityBackend>> {
585 self.durability_backend.clone()
586 }
587
588 #[cfg(feature = "std")]
596 fn build_durability_backend(
597 qos: &DataWriterQos,
598 ) -> Option<Arc<dyn crate::durability_service::DurabilityBackend>> {
599 match qos.durability.kind {
600 zerodds_qos::DurabilityKind::Transient => Some(Arc::new(
601 crate::durability_service::InMemoryDurabilityBackend::new(qos.durability_service),
602 )),
603 zerodds_qos::DurabilityKind::Persistent => {
604 let root = std::env::var_os("ZERODDS_DURABILITY_DIR")
605 .map(std::path::PathBuf::from)
606 .unwrap_or_else(|| std::env::temp_dir().join("zerodds-durability"));
607 match crate::durability_service::OnDiskDurabilityBackend::new(
608 root,
609 qos.durability_service,
610 ) {
611 Ok(b) => Some(Arc::new(b)),
612 Err(_) => None,
613 }
614 }
615 _ => None,
616 }
617 }
618
619 #[cfg(feature = "std")]
620 fn new_live(
621 topic: Topic<T>,
622 qos: DataWriterQos,
623 publisher: Arc<PublisherInner>,
624 runtime: Arc<DcpsRuntime>,
625 entity_id: EntityId,
626 ) -> Self {
627 let tracker = InstanceTracker::new();
628 let key = entity_id.entity_key;
632 let pub_handle = InstanceHandle::from_raw(
633 0xFFFF_0000_0000_0000
634 | (u64::from(key[0]) << 16)
635 | (u64::from(key[1]) << 8)
636 | u64::from(key[2]),
637 );
638 let backend = Self::build_durability_backend(&qos);
639 Self {
640 topic,
641 qos: Mutex::new(qos),
642 entity_state: crate::entity::EntityState::new(),
643 publisher,
644 listener: Mutex::new(None),
645 last_match_count: std::sync::atomic::AtomicI64::new(-1),
646 last_offered_deadline_missed: std::sync::atomic::AtomicU64::new(0),
647 last_liveliness_lost: std::sync::atomic::AtomicU64::new(0),
648 last_offered_incompatible_qos: std::sync::atomic::AtomicI64::new(-1),
649 queue: Arc::new(Mutex::new(Vec::new())),
650 #[cfg(feature = "std")]
651 drain_signal: Arc::new(std::sync::Condvar::new()),
652 runtime: Some(runtime),
653 entity_id: Some(entity_id),
654 instances: tracker,
655 publication_handle: pub_handle,
656 durability_backend: backend,
657 durability_seq: std::sync::atomic::AtomicU64::new(1),
658 #[cfg(feature = "flatdata-integration")]
659 flat_backend: Mutex::new(None),
660 big_endian: std::env::var_os("ZERODDS_WIRE_BIG_ENDIAN").is_some(),
661 _t: PhantomData,
662 }
663 }
664
665 #[cfg(not(feature = "std"))]
666 fn new(topic: Topic<T>, qos: DataWriterQos, publisher: Arc<PublisherInner>) -> Self {
667 Self {
668 topic,
669 qos,
670 publisher,
671 queue: Arc::new(Mutex::new(Vec::new())),
672 #[cfg(feature = "std")]
673 drain_signal: Arc::new(std::sync::Condvar::new()),
674 _t: PhantomData,
675 }
676 }
677
678 #[must_use]
680 pub fn topic(&self) -> &Topic<T> {
681 &self.topic
682 }
683
684 #[cfg(feature = "std")]
689 #[must_use]
690 pub fn rtps_guid(&self) -> Option<[u8; 16]> {
691 Some(self.runtime.as_ref()?.writer_guid(self.entity_id?))
692 }
693
694 #[cfg(feature = "std")]
697 pub fn set_listener(&self, listener: Option<ArcDataWriterListener>, mask: StatusMask) {
698 if let Ok(mut slot) = self.listener.lock() {
699 *slot = listener.map(|l| (l, mask));
700 }
701 self.entity_state.set_listener_mask(mask);
702 }
703
704 #[cfg(feature = "std")]
706 #[must_use]
707 pub fn get_listener(&self) -> Option<ArcDataWriterListener> {
708 self.listener
709 .lock()
710 .ok()
711 .and_then(|s| s.as_ref().map(|(l, _)| Arc::clone(l)))
712 }
713
714 #[cfg(feature = "std")]
717 #[must_use]
718 pub(crate) fn listener_chain(&self) -> crate::listener_dispatch::WriterListenerChain {
719 let writer = self
720 .listener
721 .lock()
722 .ok()
723 .and_then(|s| s.as_ref().map(|(l, m)| (Arc::clone(l), *m)));
724 let pub_handle = Publisher {
727 inner: Arc::clone(&self.publisher),
728 };
729 pub_handle.snapshot_writer_chain(writer)
730 }
731
732 #[must_use]
734 pub fn qos(&self) -> DataWriterQos {
735 self.qos.lock().map(|q| q.clone()).unwrap_or_default()
736 }
737
738 pub fn write(&self, sample: &T) -> Result<()> {
753 let mut buf = Vec::new();
754 let enc = if self.big_endian {
757 sample.encode_be(&mut buf)
758 } else {
759 sample.encode(&mut buf)
760 };
761 enc.map_err(|e| DdsError::WireError {
762 message: e.to_string(),
763 })?;
764 #[cfg(feature = "metrics")]
765 crate::metrics::inc_sample_written(self.topic.name());
766 #[cfg(feature = "metrics")]
767 crate::metrics::record_sample_size(self.topic.name(), buf.len());
768 #[cfg(all(feature = "std", feature = "cyclone-iox", target_os = "linux"))]
773 crate::cyclone_iox_integration::publish(self.topic.name(), sample);
774 #[cfg(feature = "std")]
778 if let Some(backend) = self.durability_backend.as_ref() {
779 let key_bytes = Self::keyhash_and_holder(sample)
780 .map(|(kh, _)| kh)
781 .unwrap_or([0u8; 16]);
782 let seq = self
785 .durability_seq
786 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
787 let _ = backend.store(crate::durability_service::DurabilitySample {
788 topic: self.topic.name().to_string(),
789 instance_key: key_bytes,
790 sequence: seq,
791 payload: buf.clone(),
792 created_at: std::time::SystemTime::now(),
793 });
794 }
795 #[cfg(feature = "std")]
797 if let (Some(rt), Some(eid)) = (&self.runtime, self.entity_id) {
798 return rt.write_user_sample(eid, buf);
799 }
800 #[cfg(feature = "std")]
802 {
803 let qos = self.qos.lock().map(|q| q.clone()).unwrap_or_default();
804 let max_samples = qos.resource_limits.max_samples;
805 let reliable = qos.reliability.kind == ReliabilityKind::Reliable;
806 let max_block = qos.reliability.max_blocking_time;
807 let max_block_dur = core::time::Duration::from_nanos(
808 u64::try_from(max_block.seconds).unwrap_or(0) * 1_000_000_000
809 + u64::from(max_block.fraction),
810 );
811
812 let mut q = self
813 .queue
814 .lock()
815 .map_err(|_| DdsError::PreconditionNotMet {
816 reason: "datawriter queue poisoned",
817 })?;
818 if max_samples > 0 && q.len() >= max_samples as usize {
819 if !reliable || max_block_dur.is_zero() {
820 return Err(DdsError::OutOfResources {
821 what: "datawriter queue full (best-effort or no max_blocking_time)",
822 });
823 }
824 let deadline = std::time::Instant::now() + max_block_dur;
826 loop {
827 let now = std::time::Instant::now();
828 if now >= deadline {
829 return Err(DdsError::Timeout);
830 }
831 let remaining = deadline - now;
832 let (g, _) = self.drain_signal.wait_timeout(q, remaining).map_err(|_| {
833 DdsError::PreconditionNotMet {
834 reason: "datawriter queue poisoned",
835 }
836 })?;
837 q = g;
838 if q.len() < max_samples as usize {
839 break;
840 }
841 }
843 }
844 q.push(buf);
845 Ok(())
846 }
847 #[cfg(not(feature = "std"))]
848 {
849 let mut q = self
850 .queue
851 .lock()
852 .map_err(|_| DdsError::PreconditionNotMet {
853 reason: "datawriter queue poisoned",
854 })?;
855 q.push(buf);
856 Ok(())
857 }
858 }
859
860 #[must_use]
863 pub fn samples_pending(&self) -> usize {
864 self.queue.lock().map(|q| q.len()).unwrap_or(0)
865 }
866
867 #[must_use]
877 pub fn matched_subscription_count(&self) -> usize {
878 #[cfg(feature = "std")]
879 if let (Some(rt), Some(eid)) = (&self.runtime, self.entity_id) {
880 let n = rt.user_writer_matched_count(eid);
881 self.poll_publication_matched(n);
882 return n;
883 }
884 0
885 }
886
887 #[cfg(feature = "std")]
892 pub(crate) fn poll_publication_matched(&self, current: usize) {
893 let curr = current as i64;
894 let prev = self
895 .last_match_count
896 .swap(curr, std::sync::atomic::Ordering::AcqRel);
897 if prev == curr {
898 return;
899 }
900 let total = if curr > prev.max(0) {
901 curr
902 } else {
903 prev.max(0)
904 };
905 let delta = curr - prev.max(0);
906 let status = crate::status::PublicationMatchedStatus {
907 total_count: total as i32,
908 total_count_change: delta.max(0) as i32,
909 current_count: curr as i32,
910 current_count_change: delta as i32,
911 last_subscription_handle: crate::instance_handle::HANDLE_NIL,
912 };
913 let chain = self.listener_chain();
914 crate::listener_dispatch::dispatch_publication_matched(
915 &chain,
916 self.entity_state.instance_handle(),
917 status,
918 );
919 }
920
921 #[cfg(feature = "std")]
925 pub(crate) fn poll_offered_deadline_missed(&self, current: u64) {
926 let prev = self
927 .last_offered_deadline_missed
928 .swap(current, std::sync::atomic::Ordering::AcqRel);
929 if current == prev {
930 return;
931 }
932 let total_change = current.saturating_sub(prev);
933 let status = crate::status::OfferedDeadlineMissedStatus {
934 total_count: current as i32,
935 total_count_change: total_change as i32,
936 last_instance_handle: crate::instance_handle::HANDLE_NIL,
937 };
938 let chain = self.listener_chain();
939 crate::listener_dispatch::dispatch_offered_deadline_missed(
940 &chain,
941 self.entity_state.instance_handle(),
942 status,
943 );
944 }
945
946 #[cfg(feature = "std")]
948 pub(crate) fn poll_liveliness_lost(&self, current: u64) {
949 let prev = self
950 .last_liveliness_lost
951 .swap(current, std::sync::atomic::Ordering::AcqRel);
952 if current == prev {
953 return;
954 }
955 let total_change = current.saturating_sub(prev);
956 let status = crate::status::LivelinessLostStatus {
957 total_count: current as i32,
958 total_count_change: total_change as i32,
959 };
960 let chain = self.listener_chain();
961 crate::listener_dispatch::dispatch_liveliness_lost(
962 &chain,
963 self.entity_state.instance_handle(),
964 status,
965 );
966 }
967
968 #[cfg(feature = "std")]
971 pub(crate) fn poll_offered_incompatible_qos(
972 &self,
973 snapshot: crate::status::OfferedIncompatibleQosStatus,
974 ) {
975 let curr = i64::from(snapshot.total_count);
976 let prev = self
977 .last_offered_incompatible_qos
978 .swap(curr, std::sync::atomic::Ordering::AcqRel);
979 if curr == prev {
980 return;
981 }
982 let delta = curr - prev.max(0);
983 let status = crate::status::OfferedIncompatibleQosStatus {
984 total_count: curr as i32,
985 total_count_change: delta.max(0) as i32,
986 last_policy_id: snapshot.last_policy_id,
987 policies: snapshot.policies,
988 };
989 let chain = self.listener_chain();
990 crate::listener_dispatch::dispatch_offered_incompatible_qos(
991 &chain,
992 self.entity_state.instance_handle(),
993 status,
994 );
995 }
996
997 #[cfg(feature = "std")]
1011 pub fn wait_for_matched_subscription(
1012 &self,
1013 min_count: usize,
1014 timeout: core::time::Duration,
1015 ) -> Result<()> {
1016 let deadline = std::time::Instant::now() + timeout;
1017 loop {
1018 if self.matched_subscription_count() >= min_count {
1019 return Ok(());
1020 }
1021 let now = std::time::Instant::now();
1022 if now >= deadline {
1023 return Err(DdsError::Timeout);
1024 }
1025 if let Some(rt) = self.runtime.as_ref() {
1026 let _ = rt.wait_match_event(deadline - now);
1027 } else {
1028 std::thread::sleep(core::time::Duration::from_millis(20));
1029 }
1030 }
1031 }
1032
1033 #[must_use]
1041 pub fn offered_deadline_missed_count(&self) -> u64 {
1042 #[cfg(feature = "std")]
1043 if let (Some(rt), Some(eid)) = (&self.runtime, self.entity_id) {
1044 let n = rt.user_writer_offered_deadline_missed(eid);
1045 self.poll_offered_deadline_missed(n);
1046 return n;
1047 }
1048 0
1049 }
1050
1051 #[must_use]
1054 pub fn liveliness_lost_count(&self) -> u64 {
1055 #[cfg(feature = "std")]
1056 if let (Some(rt), Some(eid)) = (&self.runtime, self.entity_id) {
1057 let n = rt.user_writer_liveliness_lost(eid);
1058 self.poll_liveliness_lost(n);
1059 return n;
1060 }
1061 0
1062 }
1063
1064 #[must_use]
1067 pub fn offered_incompatible_qos_status(&self) -> crate::status::OfferedIncompatibleQosStatus {
1068 #[cfg(feature = "std")]
1069 if let (Some(rt), Some(eid)) = (&self.runtime, self.entity_id) {
1070 let s = rt.user_writer_offered_incompatible_qos(eid);
1071 self.poll_offered_incompatible_qos(s.clone());
1072 return s;
1073 }
1074 crate::status::OfferedIncompatibleQosStatus::default()
1075 }
1076
1077 #[cfg(feature = "std")]
1080 pub fn drive_listeners(&self) {
1081 let _ = self.matched_subscription_count();
1082 let _ = self.offered_deadline_missed_count();
1083 let _ = self.liveliness_lost_count();
1084 let _ = self.offered_incompatible_qos_status();
1085 }
1086
1087 #[cfg(feature = "std")]
1091 pub fn assert_liveliness(&self) {
1092 if let (Some(rt), Some(eid)) = (&self.runtime, self.entity_id) {
1093 rt.assert_writer_liveliness_eid(eid);
1094 }
1095 }
1096
1097 #[cfg(feature = "std")]
1108 pub fn wait_for_acknowledgments(&self, timeout: core::time::Duration) -> Result<()> {
1109 let deadline = std::time::Instant::now() + timeout;
1110 loop {
1111 let all_acked = match (&self.runtime, self.entity_id) {
1112 (Some(rt), Some(eid)) => rt.user_writer_all_acknowledged(eid),
1113 _ => true, };
1115 if all_acked {
1116 return Ok(());
1117 }
1118 let now = std::time::Instant::now();
1119 if now >= deadline {
1120 return Err(DdsError::Timeout);
1121 }
1122 if let Some(rt) = self.runtime.as_ref() {
1124 let _ = rt.wait_ack_event(deadline - now);
1125 } else {
1126 std::thread::sleep(core::time::Duration::from_millis(20));
1127 }
1128 }
1129 }
1130
1131 #[doc(hidden)]
1134 pub fn __drain_pending(&self) -> Vec<Vec<u8>> {
1135 let drained = self
1136 .queue
1137 .lock()
1138 .map(|mut q| core::mem::take(&mut *q))
1139 .unwrap_or_default();
1140 #[cfg(feature = "std")]
1142 self.drain_signal.notify_all();
1143 drained
1144 }
1145
1146 #[cfg(feature = "std")]
1155 #[must_use]
1156 pub fn publication_handle(&self) -> InstanceHandle {
1157 self.publication_handle
1158 }
1159
1160 #[must_use]
1164 pub fn instance_handle(&self) -> InstanceHandle {
1165 self.entity_state.instance_handle()
1166 }
1167
1168 #[cfg(feature = "std")]
1171 #[must_use]
1172 #[doc(hidden)]
1176 #[cfg(feature = "std")]
1177 pub fn runtime_handle(&self) -> Option<(Arc<DcpsRuntime>, EntityId)> {
1178 match (&self.runtime, self.entity_id) {
1179 (Some(rt), Some(eid)) => Some((Arc::clone(rt), eid)),
1180 _ => None,
1181 }
1182 }
1183
1184 pub fn instance_tracker(&self) -> InstanceTracker {
1187 self.instances.clone()
1188 }
1189
1190 #[cfg(feature = "std")]
1193 fn keyhash_and_holder(sample: &T) -> Option<(crate::instance_tracker::KeyHash, Vec<u8>)> {
1194 if !T::HAS_KEY {
1195 return None;
1196 }
1197 let mut holder = crate::dds_type::PlainCdr2BeKeyHolder::new();
1198 sample.encode_key_holder_be(&mut holder);
1199 let bytes = holder.as_bytes().to_vec();
1200 let max = T::KEY_HOLDER_MAX_SIZE.unwrap_or(usize::MAX);
1201 let kh = crate::dds_type::compute_key_hash(&bytes, max);
1202 Some((kh, bytes))
1203 }
1204
1205 #[cfg(feature = "std")]
1216 pub fn register_instance(&self, instance: &T) -> Result<InstanceHandle> {
1217 self.register_instance_w_timestamp(instance, get_current_time())
1218 }
1219
1220 #[cfg(feature = "std")]
1223 pub fn register_instance_w_timestamp(
1224 &self,
1225 instance: &T,
1226 timestamp: Time,
1227 ) -> Result<InstanceHandle> {
1228 let Some((kh, holder)) = Self::keyhash_and_holder(instance) else {
1229 return Ok(HANDLE_NIL);
1230 };
1231 Ok(self.instances.register(kh, holder, Some(timestamp)))
1232 }
1233
1234 #[cfg(feature = "std")]
1238 #[must_use]
1239 pub fn lookup_instance(&self, instance: &T) -> InstanceHandle {
1240 let Some((kh, _)) = Self::keyhash_and_holder(instance) else {
1241 return HANDLE_NIL;
1242 };
1243 self.instances.lookup(&kh).unwrap_or(HANDLE_NIL)
1244 }
1245
1246 #[cfg(feature = "std")]
1255 pub fn unregister_instance(&self, instance: &T, handle: InstanceHandle) -> Result<()> {
1256 self.unregister_instance_w_timestamp(instance, handle, get_current_time())
1257 }
1258
1259 #[cfg(feature = "std")]
1266 pub fn unregister_instance_w_timestamp(
1267 &self,
1268 instance: &T,
1269 handle: InstanceHandle,
1270 timestamp: Time,
1271 ) -> Result<()> {
1272 let resolved = self.resolve_handle(instance, handle)?;
1273 let autodispose = self
1274 .qos
1275 .lock()
1276 .map(|q| q.writer_data_lifecycle.autodispose_unregistered_instances)
1277 .unwrap_or(true);
1278 if autodispose && !self.instances.dispose(resolved, Some(timestamp)) {
1279 return Err(DdsError::BadParameter {
1280 what: "unknown instance handle",
1281 });
1282 }
1283 if !self.instances.unregister(resolved, Some(timestamp)) {
1284 return Err(DdsError::BadParameter {
1285 what: "unknown instance handle",
1286 });
1287 }
1288 #[cfg(feature = "std")]
1292 if let (Some(rt), Some(eid), Some((kh, _))) = (
1293 &self.runtime,
1294 self.entity_id,
1295 Self::keyhash_and_holder(instance),
1296 ) {
1297 let mut bits = zerodds_rtps::inline_qos::status_info::UNREGISTERED;
1298 if autodispose {
1299 bits |= zerodds_rtps::inline_qos::status_info::DISPOSED;
1300 }
1301 let _ = rt.write_user_lifecycle(eid, kh, bits);
1302 }
1303 Ok(())
1304 }
1305
1306 #[cfg(feature = "std")]
1313 pub fn dispose(&self, instance: &T, handle: InstanceHandle) -> Result<()> {
1314 self.dispose_w_timestamp(instance, handle, get_current_time())
1315 }
1316
1317 #[cfg(feature = "std")]
1319 pub fn dispose_w_timestamp(
1320 &self,
1321 instance: &T,
1322 handle: InstanceHandle,
1323 timestamp: Time,
1324 ) -> Result<()> {
1325 let resolved = self.resolve_handle(instance, handle)?;
1326 if !self.instances.dispose(resolved, Some(timestamp)) {
1327 return Err(DdsError::BadParameter {
1328 what: "unknown instance handle",
1329 });
1330 }
1331 #[cfg(feature = "std")]
1333 if let (Some(rt), Some(eid), Some((kh, _))) = (
1334 &self.runtime,
1335 self.entity_id,
1336 Self::keyhash_and_holder(instance),
1337 ) {
1338 let _ =
1339 rt.write_user_lifecycle(eid, kh, zerodds_rtps::inline_qos::status_info::DISPOSED);
1340 }
1341 Ok(())
1342 }
1343
1344 #[cfg(feature = "std")]
1355 pub fn get_key_value(&self, handle: InstanceHandle) -> Result<T> {
1356 let Some(bytes) = self.instances.get_key_holder(handle) else {
1357 return Err(DdsError::BadParameter {
1358 what: "unknown instance handle",
1359 });
1360 };
1361 T::decode(&bytes).map_err(|e| DdsError::WireError {
1362 message: alloc::string::ToString::to_string(&e),
1363 })
1364 }
1365
1366 #[cfg(feature = "std")]
1369 fn resolve_handle(&self, instance: &T, handle: InstanceHandle) -> Result<InstanceHandle> {
1370 let derived = self.lookup_instance(instance);
1371 if handle.is_nil() {
1372 if derived.is_nil() {
1373 return Err(DdsError::BadParameter {
1374 what: "instance not registered",
1375 });
1376 }
1377 return Ok(derived);
1378 }
1379 if !derived.is_nil() && derived != handle {
1380 return Err(DdsError::BadParameter {
1381 what: "handle does not match instance key",
1382 });
1383 }
1384 Ok(handle)
1385 }
1386
1387 #[cfg(feature = "std")]
1393 pub fn write_w_timestamp(&self, sample: &T, timestamp: Time) -> Result<()> {
1394 if let Some((kh, holder)) = Self::keyhash_and_holder(sample) {
1397 if self.instances.lookup(&kh).is_none() {
1398 self.instances.register(kh, holder, Some(timestamp));
1399 } else {
1400 let prev = self.instances.get_by_keyhash(&kh);
1405 if let Some(state) = prev {
1406 if !matches!(state.kind, crate::sample_info::InstanceStateKind::Alive) {
1407 self.instances.register(kh, holder, Some(timestamp));
1408 self.instances.unregister(state.handle, Some(timestamp));
1409 }
1410 }
1411 }
1412 }
1413 self.write(sample)
1414 }
1415}
1416
1417#[cfg(feature = "std")]
1418impl<T: DdsType> crate::entity::Entity for DataWriter<T> {
1419 type Qos = DataWriterQos;
1420
1421 fn get_qos(&self) -> Self::Qos {
1422 self.qos.lock().map(|q| q.clone()).unwrap_or_default()
1423 }
1424
1425 fn set_qos(&self, qos: Self::Qos) -> Result<()> {
1428 let enabled = self.entity_state.is_enabled();
1429 if let Ok(mut current) = self.qos.lock() {
1430 if enabled {
1431 if current.durability != qos.durability {
1432 return Err(crate::entity::immutable_if_enabled("DURABILITY"));
1433 }
1434 if current.reliability != qos.reliability {
1435 return Err(crate::entity::immutable_if_enabled("RELIABILITY"));
1436 }
1437 if current.history != qos.history {
1438 return Err(crate::entity::immutable_if_enabled("HISTORY"));
1439 }
1440 if current.resource_limits != qos.resource_limits {
1441 return Err(crate::entity::immutable_if_enabled("RESOURCE_LIMITS"));
1442 }
1443 if current.ownership != qos.ownership {
1444 return Err(crate::entity::immutable_if_enabled("OWNERSHIP"));
1445 }
1446 if current.liveliness != qos.liveliness {
1447 return Err(crate::entity::immutable_if_enabled("LIVELINESS"));
1448 }
1449 }
1450 *current = qos;
1451 }
1452 Ok(())
1453 }
1454
1455 fn enable(&self) -> Result<()> {
1456 self.entity_state.enable();
1457 Ok(())
1458 }
1459
1460 fn entity_state(&self) -> Arc<crate::entity::EntityState> {
1461 Arc::clone(&self.entity_state)
1462 }
1463}
1464
1465#[allow(dead_code)]
1468pub(crate) trait AnyDataWriter: Send + Sync + core::fmt::Debug {
1469 fn topic_name(&self) -> &str;
1470 fn type_name(&self) -> &'static str;
1471}
1472
1473impl<T: DdsType + Send + 'static> AnyDataWriter for DataWriter<T>
1474where
1475 T: Send + Sync,
1476{
1477 fn topic_name(&self) -> &str {
1478 self.topic.name()
1479 }
1480 fn type_name(&self) -> &'static str {
1481 T::TYPE_NAME
1482 }
1483}
1484
1485#[allow(dead_code)]
1487pub(crate) fn boxed_any_writer<T: DdsType + Send + Sync + 'static>(
1488 w: DataWriter<T>,
1489) -> Box<dyn AnyDataWriter> {
1490 Box::new(w)
1491}
1492
1493#[cfg(test)]
1494#[allow(clippy::expect_used, clippy::unwrap_used)]
1495mod tests {
1496 use super::*;
1497 use crate::dds_type::RawBytes;
1498 use crate::factory::DomainParticipantFactory;
1499 use crate::qos::{DomainParticipantQos, TopicQos};
1500
1501 fn mk_topic() -> Topic<RawBytes> {
1502 let p = DomainParticipantFactory::instance()
1503 .create_participant_offline(0, DomainParticipantQos::default());
1504 Topic::new("Chatter".into(), TopicQos::default(), p)
1505 }
1506
1507 #[test]
1508 fn publisher_creates_datawriter_for_matching_type() {
1509 let p = Publisher::new(PublisherQos::default(), None);
1510 let w = p
1511 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1512 .unwrap();
1513 assert_eq!(w.topic().name(), "Chatter");
1514 }
1515
1516 #[test]
1523 fn live_datawriter_entity_kind_is_nokey_for_keyless_type() {
1524 use zerodds_rtps::wire_types::EntityKind;
1525 let participant = DomainParticipantFactory::instance()
1526 .create_participant(0, DomainParticipantQos::default())
1527 .expect("live participant");
1528 let topic = participant
1529 .create_topic::<RawBytes>("KindChatter", TopicQos::default())
1530 .expect("topic");
1531 let pubr = participant.create_publisher(PublisherQos::default());
1532 let w = pubr
1533 .create_datawriter::<RawBytes>(&topic, DataWriterQos::default())
1534 .expect("writer");
1535 assert_eq!(
1536 w.entity_id.expect("live writer has entity_id").entity_kind,
1537 EntityKind::UserWriterNoKey,
1538 "keyless type must yield a NoKey writer entityid"
1539 );
1540 }
1541
1542 #[test]
1550 fn create_datawriter_wires_history_keep_last_depth() {
1551 use crate::qos::{DurabilityKind, DurabilityQosPolicy, HistoryKind, HistoryQosPolicy};
1552 let participant = DomainParticipantFactory::instance()
1553 .create_participant(0, DomainParticipantQos::default())
1554 .expect("live participant");
1555 let topic = participant
1556 .create_topic::<RawBytes>("HistDepth", TopicQos::default())
1557 .expect("topic");
1558 let pubr = participant.create_publisher(PublisherQos::default());
1559 let qos = DataWriterQos {
1560 durability: DurabilityQosPolicy {
1561 kind: DurabilityKind::TransientLocal,
1562 },
1563 history: HistoryQosPolicy {
1564 kind: HistoryKind::KeepLast,
1565 depth: 2,
1566 },
1567 ..DataWriterQos::default()
1568 };
1569 let w = pubr
1570 .create_datawriter::<RawBytes>(&topic, qos)
1571 .expect("writer");
1572 let (rt, eid) = w.runtime_handle().expect("live writer");
1573 for i in 0u8..5 {
1574 w.write(&RawBytes::new(vec![i])).expect("write");
1575 }
1576 assert_eq!(
1577 rt.user_writer_retained_len(eid),
1578 2,
1579 "KeepLast(2) must cap the retained set at 2 for the default instance"
1580 );
1581 }
1582
1583 #[test]
1585 fn create_datawriter_keep_all_retains_every_sample() {
1586 use crate::qos::{DurabilityKind, DurabilityQosPolicy, HistoryKind, HistoryQosPolicy};
1587 let participant = DomainParticipantFactory::instance()
1588 .create_participant(0, DomainParticipantQos::default())
1589 .expect("live participant");
1590 let topic = participant
1591 .create_topic::<RawBytes>("HistAll", TopicQos::default())
1592 .expect("topic");
1593 let pubr = participant.create_publisher(PublisherQos::default());
1594 let qos = DataWriterQos {
1595 durability: DurabilityQosPolicy {
1596 kind: DurabilityKind::TransientLocal,
1597 },
1598 history: HistoryQosPolicy {
1599 kind: HistoryKind::KeepAll,
1600 depth: 1,
1601 },
1602 ..DataWriterQos::default()
1603 };
1604 let w = pubr
1605 .create_datawriter::<RawBytes>(&topic, qos)
1606 .expect("writer");
1607 let (rt, eid) = w.runtime_handle().expect("live writer");
1608 for i in 0u8..4 {
1609 w.write(&RawBytes::new(vec![i])).expect("write");
1610 }
1611 assert_eq!(
1612 rt.user_writer_retained_len(eid),
1613 4,
1614 "KeepAll must retain every sample of the default instance"
1615 );
1616 }
1617
1618 #[test]
1619 fn datawriter_write_queues_encoded_sample() {
1620 let p = Publisher::new(PublisherQos::default(), None);
1621 let w = p
1622 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1623 .unwrap();
1624 assert_eq!(w.samples_pending(), 0);
1625 w.write(&RawBytes::new(vec![1, 2, 3])).unwrap();
1626 assert_eq!(w.samples_pending(), 1);
1627 let drained = w.__drain_pending();
1628 assert_eq!(drained, vec![vec![1u8, 2, 3]]);
1629 }
1630
1631 use core::sync::atomic::{AtomicU32, Ordering};
1634
1635 #[test]
1636 fn datawriter_set_listener_stores_arc_and_mask() {
1637 struct L;
1638 impl crate::listener::DataWriterListener for L {}
1639 let p = Publisher::new(PublisherQos::default(), None);
1640 let w = p
1641 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1642 .unwrap();
1643 assert!(w.get_listener().is_none());
1644 w.set_listener(Some(Arc::new(L)), crate::psm_constants::status::ANY);
1645 assert!(w.get_listener().is_some());
1646 assert_eq!(
1648 w.entity_state.listener_mask(),
1649 crate::psm_constants::status::ANY
1650 );
1651 }
1652
1653 #[test]
1654 fn poll_publication_matched_fires_on_count_increase() {
1655 struct Cnt(AtomicU32);
1656 impl crate::listener::DataWriterListener for Cnt {
1657 fn on_publication_matched(
1658 &self,
1659 _w: crate::InstanceHandle,
1660 _s: crate::status::PublicationMatchedStatus,
1661 ) {
1662 self.0.fetch_add(1, Ordering::Relaxed);
1663 }
1664 }
1665 let p = Publisher::new(PublisherQos::default(), None);
1666 let w = p
1667 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1668 .unwrap();
1669 let cnt = Arc::new(Cnt(AtomicU32::new(0)));
1670 w.set_listener(Some(cnt.clone()), crate::psm_constants::status::ANY);
1671
1672 w.poll_publication_matched(0);
1674 assert_eq!(cnt.0.load(Ordering::Relaxed), 1);
1675 w.poll_publication_matched(1);
1677 assert_eq!(cnt.0.load(Ordering::Relaxed), 2);
1678 w.poll_publication_matched(1);
1680 assert_eq!(cnt.0.load(Ordering::Relaxed), 2);
1681 w.poll_publication_matched(2);
1683 assert_eq!(cnt.0.load(Ordering::Relaxed), 3);
1684 w.poll_publication_matched(1);
1686 assert_eq!(cnt.0.load(Ordering::Relaxed), 4);
1687 }
1688
1689 #[test]
1690 fn poll_publication_matched_with_no_listener_is_noop() {
1691 let p = Publisher::new(PublisherQos::default(), None);
1692 let w = p
1693 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1694 .unwrap();
1695 w.poll_publication_matched(0);
1698 w.poll_publication_matched(5);
1699 }
1700
1701 #[test]
1702 fn poll_publication_matched_bubbles_to_publisher() {
1703 struct PubL(AtomicU32);
1704 impl crate::listener::PublisherListener for PubL {
1705 fn on_publication_matched(
1706 &self,
1707 _w: crate::InstanceHandle,
1708 _s: crate::status::PublicationMatchedStatus,
1709 ) {
1710 self.0.fetch_add(1, Ordering::Relaxed);
1711 }
1712 }
1713 let p = Publisher::new(PublisherQos::default(), None);
1714 let pl = Arc::new(PubL(AtomicU32::new(0)));
1715 p.set_listener(Some(pl.clone()), crate::psm_constants::status::ANY);
1716 let w = p
1717 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1718 .unwrap();
1719 w.poll_publication_matched(1);
1721 assert_eq!(pl.0.load(Ordering::Relaxed), 1);
1722 }
1723
1724 #[test]
1727 fn suspend_publications_sets_flag() {
1728 let p = Publisher::new(PublisherQos::default(), None);
1729 assert!(!p.is_suspended());
1730 p.suspend_publications();
1731 assert!(p.is_suspended());
1732 }
1733
1734 #[test]
1735 fn resume_publications_clears_flag() {
1736 let p = Publisher::new(PublisherQos::default(), None);
1737 p.suspend_publications();
1738 p.resume_publications();
1739 assert!(!p.is_suspended());
1740 }
1741
1742 #[test]
1743 fn suspend_publications_is_idempotent() {
1744 let p = Publisher::new(PublisherQos::default(), None);
1745 p.suspend_publications();
1746 p.suspend_publications(); assert!(p.is_suspended());
1748 }
1749
1750 #[test]
1751 fn resume_without_suspend_is_noop() {
1752 let p = Publisher::new(PublisherQos::default(), None);
1753 p.resume_publications();
1755 assert!(!p.is_suspended());
1756 }
1757
1758 #[test]
1761 fn copy_from_topic_qos_copies_durability_and_reliability() {
1762 use crate::qos::{DurabilityKind, ReliabilityKind, TopicQos};
1763 let mut topic = TopicQos::default();
1764 topic.durability.kind = DurabilityKind::TransientLocal;
1765 topic.reliability.kind = ReliabilityKind::Reliable;
1766
1767 let mut dw = DataWriterQos::default();
1768 dw.durability.kind = DurabilityKind::Volatile;
1770 Publisher::copy_from_topic_qos(&mut dw, &topic).unwrap();
1771 assert_eq!(dw.durability.kind, DurabilityKind::TransientLocal);
1772 assert_eq!(dw.reliability.kind, ReliabilityKind::Reliable);
1773 }
1774
1775 #[test]
1778 fn write_blocks_until_drain_when_reliable_max_samples_reached() {
1779 use crate::qos::{HistoryQosPolicy, ResourceLimitsQosPolicy};
1780 let p = Publisher::new(PublisherQos::default(), None);
1781 let qos = DataWriterQos {
1782 resource_limits: ResourceLimitsQosPolicy {
1783 max_samples: 2,
1784 max_instances: -1,
1785 max_samples_per_instance: -1,
1786 },
1787 reliability: crate::qos::ReliabilityQosPolicy {
1788 kind: ReliabilityKind::Reliable,
1789 max_blocking_time: zerodds_qos::Duration::from_millis(500_i32),
1790 },
1791 ..DataWriterQos::default()
1792 };
1793 let _ = qos.history;
1794 let _ = HistoryQosPolicy::default();
1795 let w = p.create_datawriter::<RawBytes>(&mk_topic(), qos).unwrap();
1796 let s = RawBytes::new(b"x".to_vec());
1797 w.write(&s).unwrap();
1799 w.write(&s).unwrap();
1800 assert_eq!(w.samples_pending(), 2);
1801
1802 let w_clone_q = w.queue.clone();
1804 let w_clone_signal = w.drain_signal.clone();
1805 let drain_handle = std::thread::spawn(move || {
1806 std::thread::sleep(core::time::Duration::from_millis(50));
1807 if let Ok(mut q) = w_clone_q.lock() {
1808 let _ = core::mem::take(&mut *q);
1809 }
1810 w_clone_signal.notify_all();
1811 });
1812
1813 let start = std::time::Instant::now();
1814 let res = w.write(&s);
1815 let elapsed = start.elapsed();
1816 drain_handle.join().unwrap();
1817
1818 assert!(res.is_ok(), "write should succeed after drain, got {res:?}");
1819 assert!(
1820 elapsed >= core::time::Duration::from_millis(40)
1821 && elapsed < core::time::Duration::from_millis(450),
1822 "elapsed = {elapsed:?}, expected ~50ms"
1823 );
1824 }
1825
1826 #[test]
1827 fn write_returns_timeout_when_reliable_drain_too_slow() {
1828 use crate::qos::ResourceLimitsQosPolicy;
1829 let p = Publisher::new(PublisherQos::default(), None);
1830 let qos = DataWriterQos {
1831 resource_limits: ResourceLimitsQosPolicy {
1832 max_samples: 1,
1833 max_instances: -1,
1834 max_samples_per_instance: -1,
1835 },
1836 reliability: crate::qos::ReliabilityQosPolicy {
1837 kind: ReliabilityKind::Reliable,
1838 max_blocking_time: zerodds_qos::Duration::from_millis(50_i32),
1839 },
1840 ..DataWriterQos::default()
1841 };
1842 let w = p.create_datawriter::<RawBytes>(&mk_topic(), qos).unwrap();
1843 let s = RawBytes::new(b"x".to_vec());
1844 w.write(&s).unwrap();
1845 let res = w.write(&s);
1847 assert!(matches!(res, Err(DdsError::Timeout)));
1848 }
1849
1850 #[test]
1851 fn write_returns_oor_when_best_effort_queue_full() {
1852 use crate::qos::ResourceLimitsQosPolicy;
1853 let p = Publisher::new(PublisherQos::default(), None);
1854 let qos = DataWriterQos {
1855 resource_limits: ResourceLimitsQosPolicy {
1856 max_samples: 1,
1857 max_instances: -1,
1858 max_samples_per_instance: -1,
1859 },
1860 reliability: crate::qos::ReliabilityQosPolicy {
1861 kind: ReliabilityKind::BestEffort,
1862 max_blocking_time: zerodds_qos::Duration::from_millis(0_i32),
1863 },
1864 ..DataWriterQos::default()
1865 };
1866 let w = p.create_datawriter::<RawBytes>(&mk_topic(), qos).unwrap();
1867 let s = RawBytes::new(b"x".to_vec());
1868 w.write(&s).unwrap();
1869 let res = w.write(&s);
1870 assert!(matches!(res, Err(DdsError::OutOfResources { .. })));
1871 }
1872
1873 #[test]
1874 fn write_does_not_block_when_max_samples_unlimited() {
1875 let p = Publisher::new(PublisherQos::default(), None);
1877 let w = p
1878 .create_datawriter::<RawBytes>(&mk_topic(), DataWriterQos::default())
1879 .unwrap();
1880 let s = RawBytes::new(b"x".to_vec());
1881 for _ in 0..50 {
1882 w.write(&s).unwrap();
1883 }
1884 assert_eq!(w.samples_pending(), 50);
1885 }
1886
1887 #[test]
1888 fn copy_from_topic_qos_does_not_touch_writer_only_policies() {
1889 use crate::qos::TopicQos;
1890 let topic = TopicQos::default();
1891 let mut dw = DataWriterQos::default();
1892 dw.ownership_strength.value = 42;
1895 Publisher::copy_from_topic_qos(&mut dw, &topic).unwrap();
1896 assert_eq!(dw.ownership_strength.value, 42);
1897 }
1898}