1use std::convert::Infallible;
64use std::os::fd::OwnedFd;
65use std::sync::atomic::{AtomicBool, Ordering};
66use std::sync::{Arc, Condvar, Mutex, MutexGuard};
67use std::time::{Duration, Instant};
68
69use melin_app::{AppEvent, unix_epoch_nanos};
70use melin_journal::{AckPolicyChange, JournalEvent};
71use melin_pipeline::ring;
72use melin_transport_core::lineage::LineagePolicy;
73use melin_transport_core::pipeline::InputSlot;
74use melin_transport_core::replica_presence::Observed;
75use tracing::{info, warn};
76
77use crate::ack_policy::AckPolicy;
78use crate::halt::{HaltGate, Journaled, Replication};
79
80const PUBLISH_WAIT: Duration = Duration::from_secs(2);
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub(crate) enum Origin {
90 Runtime,
93 Staged,
97}
98
99pub(crate) fn is_unwitnessed(policy: AckPolicy, origin: Origin, seen: Option<&Observed>) -> bool {
109 match seen {
110 None => true,
111 Some(Observed::NoneConnected(_)) => origin == Origin::Runtime && policy == AckPolicy::Disk,
112 Some(Observed::Connected(_)) => false,
113 }
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119enum BootEntry {
120 Differs,
123 TakesEffectUnwitnessed,
128}
129
130fn boot_entry(
150 lineage: Option<LineagePolicy>,
151 requested: AckPolicy,
152 replication: Replication,
153) -> Option<BootEntry> {
154 let Some(lineage) = lineage.filter(|l| l.policy() == requested.to_byte()) else {
155 return Some(BootEntry::Differs);
156 };
157 let may_await_witness = lineage.previous.is_some() && !lineage.change.unwitnessed;
158 match replication {
159 Replication::Disabled if may_await_witness => Some(BootEntry::TakesEffectUnwitnessed),
160 Replication::Disabled | Replication::Enabled => None,
161 }
162}
163
164fn change_slot<E: AppEvent>(change: AckPolicyChange) -> InputSlot<E> {
166 InputSlot::node_event(JournalEvent::AckPolicyChange(change), unix_epoch_nanos())
167}
168
169fn publish_staged<E: AppEvent>(
179 producer: &mut ring::Producer<InputSlot<E>>,
180 halt: &HaltGate,
181 policy: AckPolicy,
182) -> Journaled {
183 let Ok(journaled) = halt.journal(policy, Origin::Staged, |change| {
185 Ok::<_, Infallible>(producer.publish(change_slot(change)))
186 });
187 journaled
188}
189
190fn log_journaled(journaled: Journaled, origin: Origin) {
192 let policy = journaled.policy();
193 if journaled.is_forced() {
194 warn!(
195 policy = %policy,
196 input_seq = journaled.input_seq(),
197 "ack policy change forced with no replica connected: client writes are taken again \
198 until a replica streaming live acknowledges an entry, the change takes effect once on this node's disk, and a \
199 replica that was offline does not hold it — if this node fails before that replica \
200 rejoins, it may auto-promote over events acknowledged under the new policy"
201 );
202 } else {
203 info!(
204 policy = %policy,
205 unwitnessed = journaled.unwitnessed(),
206 origin = ?origin,
207 input_seq = journaled.input_seq(),
208 "ack policy change journaled"
209 );
210 }
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
215pub(crate) enum RequestOutcome {
216 Published,
218 Staged,
221 Refused(Refused),
223}
224
225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
228pub(crate) enum Refused {
229 RingFull,
232 ProducerStopped,
235 AnotherWaiting,
238 NoReplicaCanAttach,
241}
242
243#[derive(Clone, Copy, Debug)]
245struct Request {
246 policy: AckPolicy,
247 ticket: u64,
250}
251
252enum Producer {
254 Detached,
256 Attached { wake: Option<Arc<OwnedFd>> },
260}
261
262struct State {
267 producer: Producer,
269 request: Option<Request>,
271 issued: u64,
273 published: u64,
274 requested: AckPolicy,
277}
278
279pub struct AckPolicyControl {
283 state: Mutex<State>,
284 changed: Condvar,
287 pending: AtomicBool,
290 publish_wait: Duration,
291 replication: Replication,
294}
295
296impl AckPolicyControl {
297 pub(crate) fn new(configured: AckPolicy, replication: Replication) -> Arc<Self> {
301 Self::with_publish_wait(configured, replication, PUBLISH_WAIT)
302 }
303
304 fn with_publish_wait(
306 configured: AckPolicy,
307 replication: Replication,
308 publish_wait: Duration,
309 ) -> Arc<Self> {
310 Arc::new(Self {
311 state: Mutex::new(State {
312 producer: Producer::Detached,
313 request: None,
314 issued: 0,
315 published: 0,
316 requested: configured,
317 }),
318 changed: Condvar::new(),
319 pending: AtomicBool::new(false),
320 publish_wait,
321 replication,
322 })
323 }
324
325 pub(crate) fn requested(&self) -> AckPolicy {
330 self.lock().requested
331 }
332
333 fn lock(&self) -> MutexGuard<'_, State> {
334 self.state
338 .lock()
339 .unwrap_or_else(|poisoned| poisoned.into_inner())
340 }
341
342 pub(crate) fn request(&self, policy: AckPolicy) -> RequestOutcome {
352 if !self.replication.can_meet(policy) {
353 return RequestOutcome::Refused(Refused::NoReplicaCanAttach);
354 }
355 let deadline = Instant::now() + self.publish_wait;
356 let mut state = self.lock();
357 let Producer::Attached { wake } = &state.producer else {
358 state.requested = policy;
359 return RequestOutcome::Staged;
360 };
361 if state.request.is_some() {
362 return RequestOutcome::Refused(Refused::AnotherWaiting);
364 }
365 let wake = wake.clone();
366 state.issued += 1;
367 let ticket = state.issued;
368 state.request = Some(Request { policy, ticket });
369 self.pending.store(true, Ordering::Release);
370 if let Some(fd) = wake {
371 crate::reader::wake_reader(&fd);
372 }
373 loop {
374 if state.published >= ticket {
375 return RequestOutcome::Published;
376 }
377 if !state
378 .request
379 .is_some_and(|waiting| waiting.ticket == ticket)
380 {
381 return RequestOutcome::Refused(Refused::ProducerStopped);
384 }
385 let Some(left) = deadline.checked_duration_since(Instant::now()) else {
386 state.request = None;
390 self.pending.store(false, Ordering::Release);
391 self.changed.notify_all();
392 return RequestOutcome::Refused(Refused::RingFull);
393 };
394 state = self.wait(state, left);
395 }
396 }
397
398 fn wait<'a>(&self, state: MutexGuard<'a, State>, left: Duration) -> MutexGuard<'a, State> {
399 self.changed
400 .wait_timeout(state, left)
401 .unwrap_or_else(|poisoned| poisoned.into_inner())
402 .0
403 }
404
405 pub(crate) fn claim(self: &Arc<Self>) -> PublisherClaim {
409 PublisherClaim {
410 control: Arc::clone(self),
411 policy: self.requested(),
412 }
413 }
414}
415
416pub struct PublisherClaim {
427 control: Arc<AckPolicyControl>,
428 policy: AckPolicy,
429}
430
431impl PublisherClaim {
432 pub(crate) fn policy(&self) -> AckPolicy {
434 self.policy
435 }
436
437 #[must_use = "the booted claim is what attaches the producer"]
446 pub(crate) fn journal_boot<E: AppEvent>(
447 self,
448 lineage: Option<LineagePolicy>,
449 producer: &mut ring::Producer<InputSlot<E>>,
450 halt: &HaltGate,
451 ) -> BootedClaim {
452 if let Some(reason) = boot_entry(lineage, self.policy, halt.replication()) {
453 let journaled = publish_staged(producer, halt, self.policy);
454 info!(
455 policy = %self.policy,
456 unwitnessed = journaled.unwitnessed(),
457 input_seq = journaled.input_seq(),
458 lineage = ?lineage.map(|p| p.policy().0),
459 reason = ?reason,
460 "journaling the requested ack policy"
461 );
462 }
463 BootedClaim {
464 control: self.control,
465 policy: self.policy,
466 }
467 }
468}
469
470pub struct BootedClaim {
473 control: Arc<AckPolicyControl>,
474 policy: AckPolicy,
476}
477
478impl BootedClaim {
479 pub(crate) fn attach<E: AppEvent>(
490 self,
491 producer: &mut ring::Producer<InputSlot<E>>,
492 halt: &HaltGate,
493 wake: Option<Arc<OwnedFd>>,
494 ) -> AckPolicyPublisher {
495 let control = self.control;
496 let staged = {
497 let mut state = control.lock();
498 state.producer = Producer::Attached { wake };
499 state.requested
500 };
501 if staged != self.policy {
506 log_journaled(publish_staged(producer, halt, staged), Origin::Staged);
507 }
508 AckPolicyPublisher { control }
509 }
510}
511
512pub struct AckPolicyPublisher {
516 control: Arc<AckPolicyControl>,
517}
518
519impl AckPolicyPublisher {
520 #[inline]
530 pub(crate) fn poll<E: AppEvent>(
531 &mut self,
532 producer: &mut ring::Producer<InputSlot<E>>,
533 halt: &HaltGate,
534 ) {
535 if self.control.pending.load(Ordering::Acquire) {
536 self.publish(producer, halt);
537 }
538 }
539
540 #[cold]
541 fn publish<E: AppEvent>(
542 &mut self,
543 producer: &mut ring::Producer<InputSlot<E>>,
544 halt: &HaltGate,
545 ) {
546 let control = &*self.control;
547 let mut state = control.lock();
548 let Some(request) = state.request else {
549 return;
551 };
552 let Ok(journaled) = halt.journal(request.policy, Origin::Runtime, |change| {
556 producer.try_publish(change_slot(change))
557 }) else {
558 return;
560 };
561 state.request = None;
562 state.published = request.ticket;
563 state.requested = request.policy;
564 control.pending.store(false, Ordering::Release);
565 control.changed.notify_all();
566 drop(state);
567 log_journaled(journaled, Origin::Runtime);
568 }
569}
570
571impl Drop for AckPolicyPublisher {
572 fn drop(&mut self) {
573 let mut state = self.control.lock();
574 state.producer = Producer::Detached;
575 state.request = None;
578 self.control.pending.store(false, Ordering::Release);
579 self.control.changed.notify_all();
580 }
581}
582
583#[cfg(test)]
585pub(crate) mod test_support {
586 use super::*;
587 use melin_journal::AckPolicyByte;
588 use melin_pipeline::ring::{Consumer, DisruptorBuilder};
589 use melin_pipeline::wait::WaitStrategy;
590 use melin_transport_core::WireSeq;
591
592 pub(crate) fn control_with_publish_wait(
595 configured: AckPolicy,
596 publish_wait: Duration,
597 ) -> Arc<AckPolicyControl> {
598 AckPolicyControl::with_publish_wait(configured, Replication::Enabled, publish_wait)
599 }
600
601 pub(crate) fn replicated(configured: AckPolicy) -> Arc<AckPolicyControl> {
603 AckPolicyControl::new(configured, Replication::Enabled)
604 }
605
606 pub(crate) fn ring<E: AppEvent>(
608 capacity: usize,
609 ) -> (ring::Producer<InputSlot<E>>, Consumer<InputSlot<E>>) {
610 let (producer, mut consumers) = DisruptorBuilder::<InputSlot<E>>::new(capacity)
611 .add_consumer()
612 .build(WaitStrategy::SpinThenYield);
613 (producer, consumers.pop().expect("one consumer"))
614 }
615
616 pub(crate) fn lineage(
619 policy: AckPolicyByte,
620 unwitnessed: bool,
621 wire_seq: u64,
622 previous: Option<AckPolicyByte>,
623 ) -> Option<LineagePolicy> {
624 Some(LineagePolicy {
625 change: AckPolicyChange {
626 policy,
627 unwitnessed,
628 },
629 wire_seq: WireSeq::new(wire_seq),
630 previous,
631 })
632 }
633
634 pub(crate) fn first_change(policy: AckPolicy) -> Option<LineagePolicy> {
637 lineage(policy.to_byte(), false, 1, None)
638 }
639
640 pub(crate) fn booted(control: &Arc<AckPolicyControl>) -> BootedClaim {
644 let claim = control.claim();
645 let lineage = first_change(claim.policy());
646 let (mut producer, _consumer) = ring::<counter_server::CounterEvent>(2);
648 claim.journal_boot(
649 lineage,
650 &mut producer,
651 &crate::halt::test_support::gate_over(None),
652 )
653 }
654
655 pub(crate) fn attach<E: AppEvent>(
658 control: &Arc<AckPolicyControl>,
659 producer: &mut ring::Producer<InputSlot<E>>,
660 ) -> AckPolicyPublisher {
661 booted(control).attach(producer, &crate::halt::test_support::gate_over(None), None)
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use super::test_support::{
668 attach, booted, control_with_publish_wait, first_change, lineage, replicated, ring,
669 };
670 use super::*;
671 use crate::halt::Verdict;
672 use crate::halt::test_support::{gate_over, presence};
673 use counter_server::CounterEvent as TestEvent;
674 use melin_pipeline::ring::Consumer;
675 use melin_transport_core::WireSeq;
676 use melin_transport_core::replica_presence::Lowered;
677
678 const ALL: [AckPolicy; 4] = [
679 AckPolicy::Disk,
680 AckPolicy::Ram,
681 AckPolicy::DiskAndRam,
682 AckPolicy::TwoDisks,
683 ];
684
685 type Slot = InputSlot<TestEvent>;
686
687 fn changes(consumer: &mut Consumer<Slot>) -> Vec<AckPolicyChange> {
689 let mut out = Vec::new();
690 while let Some((_, slot)) = consumer.try_consume() {
691 match slot.event {
692 JournalEvent::AckPolicyChange(change) => {
693 assert_eq!(
694 (slot.connection_id, slot.key_hash, slot.sequence),
695 (0, 0, 0),
696 "a node event"
697 );
698 out.push(change);
699 }
700 other => panic!("not a policy change: {other:?}"),
701 }
702 }
703 out
704 }
705
706 fn change(policy: AckPolicy, unwitnessed: bool) -> AckPolicyChange {
707 AckPolicyChange {
708 policy: policy.to_byte(),
709 unwitnessed,
710 }
711 }
712
713 #[test]
717 fn the_forced_rule() {
718 let none_connected = presence(0).observe();
719 let connected = presence(1).observe();
720 for policy in ALL {
721 for origin in [Origin::Runtime, Origin::Staged] {
722 assert!(is_unwitnessed(policy, origin, None));
723 assert!(!is_unwitnessed(policy, origin, Some(&connected)));
724 assert_eq!(
725 is_unwitnessed(policy, origin, Some(&none_connected)),
726 origin == Origin::Runtime && policy == AckPolicy::Disk,
727 "{policy} from {origin:?}"
728 );
729 }
730 }
731 }
732
733 fn halted_gate() -> HaltGate {
736 gate_over(Some(presence(0)))
737 }
738
739 #[test]
746 fn the_boot_entry_when_the_lineage_differs() {
747 let disk = first_change(AckPolicy::Disk);
748 let (mut producer, mut consumer) = ring::<TestEvent>(8);
749 let halt = halted_gate();
750 let boot = |requested, lineage, producer: &mut ring::Producer<Slot>, halt: &HaltGate| {
751 drop(
752 replicated(requested)
753 .claim()
754 .journal_boot(lineage, producer, halt),
755 );
756 };
757
758 boot(AckPolicy::Disk, disk, &mut producer, &halt);
759 assert!(changes(&mut consumer).is_empty(), "nothing to change");
760 boot(AckPolicy::DiskAndRam, disk, &mut producer, &halt);
761 assert_eq!(
762 changes(&mut consumer),
763 [change(AckPolicy::DiskAndRam, false)]
764 );
765 boot(AckPolicy::Disk, None, &mut producer, &halt);
766 assert_eq!(
767 changes(&mut consumer),
768 [change(AckPolicy::Disk, false)],
769 "a lineage holding none gets one, never forced"
770 );
771 assert_ne!(halt.verdict(), Verdict::Take, "a boot `disk` is no consent");
772
773 let standalone = gate_over(None);
774 boot(
775 AckPolicy::Disk,
776 first_change(AckPolicy::DiskAndRam),
777 &mut producer,
778 &standalone,
779 );
780 assert_eq!(
781 changes(&mut consumer),
782 [change(AckPolicy::Disk, true)],
783 "standalone: unwitnessed"
784 );
785 }
786
787 #[test]
795 fn the_boot_decision() {
796 use Replication::{Disabled, Enabled};
797 let disk = AckPolicy::Disk.to_byte();
798 let disk_and_ram = Some(AckPolicy::DiskAndRam.to_byte());
799 let waiting = lineage(disk, false, 3, disk_and_ram);
800 let marked = lineage(disk, true, 3, disk_and_ram);
801 for replication in [Enabled, Disabled] {
802 for lineage in [None, first_change(AckPolicy::DiskAndRam), waiting, marked] {
803 assert_eq!(
804 boot_entry(lineage, AckPolicy::Ram, replication),
805 Some(BootEntry::Differs),
806 "{lineage:?} under {replication:?}"
807 );
808 }
809 assert_eq!(
810 boot_entry(first_change(AckPolicy::Disk), AckPolicy::Disk, replication),
811 None,
812 "the first change under {replication:?}"
813 );
814 assert_eq!(
815 boot_entry(marked, AckPolicy::Disk, replication),
816 None,
817 "already unwitnessed under {replication:?}"
818 );
819 }
820 assert_eq!(
821 boot_entry(waiting, AckPolicy::Disk, Enabled),
822 None,
823 "replicated: the gate re-applies it"
824 );
825 assert_eq!(
826 boot_entry(waiting, AckPolicy::Disk, Disabled),
827 Some(BootEntry::TakesEffectUnwitnessed)
828 );
829 let repeated =
832 LineagePolicy::after(waiting, WireSeq::new(4), change(AckPolicy::Disk, false));
833 assert_eq!(
834 boot_entry(Some(repeated), AckPolicy::Disk, Disabled),
835 Some(BootEntry::TakesEffectUnwitnessed)
836 );
837 }
838
839 #[test]
844 fn a_standalone_boot_marks_a_waiting_change_unwitnessed_once() {
845 let (mut producer, mut consumer) = ring::<TestEvent>(8);
846 let standalone = gate_over(None);
847 let waiting = lineage(
848 AckPolicy::Disk.to_byte(),
849 false,
850 3,
851 Some(AckPolicy::DiskAndRam.to_byte()),
852 );
853 drop(
854 AckPolicyControl::new(AckPolicy::Disk, Replication::Disabled)
855 .claim()
856 .journal_boot(waiting, &mut producer, &standalone),
857 );
858 let journaled = changes(&mut consumer);
859 assert_eq!(journaled, [change(AckPolicy::Disk, true)]);
860
861 let folded = LineagePolicy::after(waiting, WireSeq::new(5), journaled[0]);
862 assert_eq!(
863 (folded.wire_seq, folded.change.unwitnessed),
864 (WireSeq::new(3), true),
865 "the same transition, now marked"
866 );
867 drop(
868 AckPolicyControl::new(AckPolicy::Disk, Replication::Disabled)
869 .claim()
870 .journal_boot(Some(folded), &mut producer, &standalone),
871 );
872 assert!(changes(&mut consumer).is_empty(), "nothing more");
873
874 drop(replicated(AckPolicy::Disk).claim().journal_boot(
876 waiting,
877 &mut producer,
878 &halted_gate(),
879 ));
880 assert!(changes(&mut consumer).is_empty(), "the gate re-applies it");
881 }
882
883 #[test]
886 fn a_boot_entry_that_needs_a_replica_restores_the_halt() {
887 let control = replicated(AckPolicy::Disk);
888 let (mut producer, mut consumer) = ring::<TestEvent>(8);
889 let halt = halted_gate();
890 let mut publisher = booted(&control).attach(&mut producer, &halt, None);
891 let outcome = request_while_polling(
892 &control,
893 &mut publisher,
894 &mut producer,
895 AckPolicy::Disk,
896 &halt,
897 );
898 assert_eq!(outcome, RequestOutcome::Published);
899 assert_eq!(halt.verdict(), Verdict::Take, "forced: lifted");
900 drop(publisher);
901
902 drop(replicated(AckPolicy::TwoDisks).claim().journal_boot(
903 first_change(AckPolicy::Disk),
904 &mut producer,
905 &halt,
906 ));
907 assert_ne!(halt.verdict(), Verdict::Take, "restored");
908 assert_eq!(
909 changes(&mut consumer),
910 [
911 change(AckPolicy::Disk, true),
912 change(AckPolicy::TwoDisks, false)
913 ]
914 );
915 }
916
917 #[test]
920 fn a_request_with_no_producer_is_staged_for_the_next_claim() {
921 let control = replicated(AckPolicy::DiskAndRam);
922 assert_eq!(control.claim().policy(), AckPolicy::DiskAndRam);
923 assert_eq!(control.request(AckPolicy::Ram), RequestOutcome::Staged);
924 assert_eq!(control.requested(), AckPolicy::Ram);
925 assert_eq!(control.claim().policy(), AckPolicy::Ram);
926 }
927
928 #[test]
934 fn a_node_without_replication_refuses_a_policy_that_needs_a_replica() {
935 const REFUSED: RequestOutcome = RequestOutcome::Refused(Refused::NoReplicaCanAttach);
936 let control = AckPolicyControl::new(AckPolicy::Disk, Replication::Disabled);
937 for policy in [AckPolicy::Ram, AckPolicy::DiskAndRam, AckPolicy::TwoDisks] {
938 assert_eq!(control.request(policy), REFUSED, "staging {policy}");
939 }
940 assert_eq!(control.requested(), AckPolicy::Disk, "nothing staged");
941 assert_eq!(control.request(AckPolicy::Disk), RequestOutcome::Staged);
942
943 let (mut producer, mut consumer) = ring::<TestEvent>(8);
944 let standalone = gate_over(None);
945 let mut publisher = booted(&control).attach(&mut producer, &standalone, None);
946 for policy in [AckPolicy::Ram, AckPolicy::DiskAndRam, AckPolicy::TwoDisks] {
947 let outcome =
948 request_while_polling(&control, &mut publisher, &mut producer, policy, &standalone);
949 assert_eq!(outcome, REFUSED, "journaling {policy}");
950 assert!(!control.pending.load(Ordering::Acquire), "nothing waits");
951 }
952 assert!(changes(&mut consumer).is_empty(), "nothing journaled");
953 assert_eq!(control.requested(), AckPolicy::Disk);
954 let outcome = request_while_polling(
955 &control,
956 &mut publisher,
957 &mut producer,
958 AckPolicy::Disk,
959 &standalone,
960 );
961 assert_eq!(outcome, RequestOutcome::Published);
962 assert_eq!(changes(&mut consumer), [change(AckPolicy::Disk, true)]);
963 }
964
965 fn request_while_polling(
968 control: &Arc<AckPolicyControl>,
969 publisher: &mut AckPolicyPublisher,
970 producer: &mut ring::Producer<Slot>,
971 policy: AckPolicy,
972 halt: &HaltGate,
973 ) -> RequestOutcome {
974 std::thread::scope(|s| {
975 let requesting = s.spawn(|| control.request(policy));
976 while !requesting.is_finished() {
977 publisher.poll(producer, halt);
978 std::thread::yield_now();
979 }
980 requesting.join().expect("the request panicked")
981 })
982 }
983
984 #[test]
989 fn an_attached_producer_journals_every_request() {
990 let control = replicated(AckPolicy::DiskAndRam);
991 let (mut producer, mut consumer) = ring(8);
992 let replicas = presence(1);
993 let halt = gate_over(Some(Arc::clone(&replicas)));
994 let mut publisher = booted(&control).attach(&mut producer, &halt, None);
995 let mut journal = |policy| {
996 let outcome =
997 request_while_polling(&control, &mut publisher, &mut producer, policy, &halt);
998 assert_eq!(outcome, RequestOutcome::Published);
999 };
1000
1001 journal(AckPolicy::Disk);
1002 journal(AckPolicy::Disk);
1003 assert_eq!(replicas.lower(), Lowered::LastHalted);
1004 journal(AckPolicy::Disk);
1005 journal(AckPolicy::TwoDisks);
1006 assert_eq!(
1007 changes(&mut consumer),
1008 [
1009 change(AckPolicy::Disk, false),
1010 change(AckPolicy::Disk, false),
1011 change(AckPolicy::Disk, true),
1012 change(AckPolicy::TwoDisks, false),
1013 ]
1014 );
1015 assert_eq!(control.claim().policy(), AckPolicy::TwoDisks);
1016 }
1017
1018 #[test]
1020 fn polling_with_nothing_requested_publishes_nothing() {
1021 let control = replicated(AckPolicy::Disk);
1022 let (mut producer, mut consumer) = ring(8);
1023 let halt = halted_gate();
1024 let mut publisher = booted(&control).attach(&mut producer, &halt, None);
1025 publisher.poll(&mut producer, &halt);
1026 assert!(changes(&mut consumer).is_empty());
1027 assert_ne!(halt.verdict(), Verdict::Take);
1028 }
1029
1030 #[test]
1034 fn a_full_ring_refuses_the_request_and_withdraws_it() {
1035 let control = control_with_publish_wait(AckPolicy::DiskAndRam, Duration::from_millis(50));
1036 let (mut producer, mut consumer) = ring(2);
1037 for _ in 0..2 {
1038 producer
1039 .try_publish(change_slot(change(AckPolicy::Ram, false)))
1040 .expect("room");
1041 }
1042 let halt = halted_gate();
1044 let mut publisher = booted(&control).attach(&mut producer, &halt, None);
1045 let outcome = request_while_polling(
1046 &control,
1047 &mut publisher,
1048 &mut producer,
1049 AckPolicy::Disk,
1050 &halt,
1051 );
1052 assert_eq!(outcome, RequestOutcome::Refused(Refused::RingFull));
1053 assert_eq!(changes(&mut consumer).len(), 2, "only what filled the ring");
1054 publisher.poll(&mut producer, &halt);
1055 assert!(changes(&mut consumer).is_empty(), "withdrawn");
1056 assert_eq!(control.requested(), AckPolicy::DiskAndRam);
1057 assert_ne!(halt.verdict(), Verdict::Take, "still halted");
1058 }
1059
1060 #[test]
1063 fn a_detached_producer_refuses_the_waiting_request() {
1064 let control = control_with_publish_wait(AckPolicy::Disk, Duration::from_secs(60));
1065 let (mut producer, _consumer) = ring::<TestEvent>(8);
1066 let publisher = attach(&control, &mut producer);
1067 let started = Instant::now();
1068 let outcome = std::thread::scope(|s| {
1069 let requesting = s.spawn(|| control.request(AckPolicy::Ram));
1070 while !control.pending.load(Ordering::Acquire) {
1071 std::thread::yield_now();
1072 }
1073 drop(publisher);
1074 requesting.join().expect("the request panicked")
1075 });
1076 assert_eq!(outcome, RequestOutcome::Refused(Refused::ProducerStopped));
1077 assert!(started.elapsed() < Duration::from_secs(30), "not the bound");
1078 assert!(!control.pending.load(Ordering::Acquire));
1079 assert_eq!(control.request(AckPolicy::Ram), RequestOutcome::Staged);
1080 }
1081
1082 #[test]
1086 fn a_producer_replaced_while_a_request_waits_refuses_it() {
1087 let control = control_with_publish_wait(AckPolicy::Disk, Duration::from_secs(60));
1088 let (mut producer, mut consumer) = ring::<TestEvent>(8);
1089 let publisher = attach(&control, &mut producer);
1090 let started = Instant::now();
1091 let outcome = std::thread::scope(|s| {
1092 let requesting = s.spawn(|| control.request(AckPolicy::Ram));
1093 while !control.pending.load(Ordering::Acquire) {
1094 std::thread::yield_now();
1095 }
1096 drop(publisher);
1097 let mut next = attach(&control, &mut producer);
1098 let outcome = requesting.join().expect("the request panicked");
1099 next.poll(&mut producer, &gate_over(None));
1100 outcome
1101 });
1102 assert_eq!(outcome, RequestOutcome::Refused(Refused::ProducerStopped));
1103 assert!(started.elapsed() < Duration::from_secs(30), "not the bound");
1104 assert!(changes(&mut consumer).is_empty(), "never published");
1105 }
1106
1107 #[test]
1110 fn a_second_waiting_request_is_refused() {
1111 let control = control_with_publish_wait(AckPolicy::DiskAndRam, Duration::from_secs(60));
1112 let (mut producer, mut consumer) = ring(8);
1113 let halt = halted_gate();
1114 let mut publisher = booted(&control).attach(&mut producer, &halt, None);
1115 std::thread::scope(|s| {
1116 let first = s.spawn(|| control.request(AckPolicy::TwoDisks));
1117 while !control.pending.load(Ordering::Acquire) {
1118 std::thread::yield_now();
1119 }
1120 assert_eq!(
1121 control.request(AckPolicy::Disk),
1122 RequestOutcome::Refused(Refused::AnotherWaiting)
1123 );
1124 publisher.poll(&mut producer, &halt);
1125 assert_eq!(first.join().expect("panicked"), RequestOutcome::Published);
1126 });
1127 assert_eq!(changes(&mut consumer), [change(AckPolicy::TwoDisks, false)]);
1128 }
1129
1130 #[test]
1135 fn a_request_staged_during_bring_up_is_journaled_at_attach_in_order() {
1136 let control = replicated(AckPolicy::DiskAndRam);
1137 assert_eq!(control.request(AckPolicy::TwoDisks), RequestOutcome::Staged);
1138 let claim = control.claim();
1139 let (mut producer, mut consumer) = ring(8);
1140 let halt = halted_gate();
1141 let booted = claim.journal_boot(first_change(AckPolicy::DiskAndRam), &mut producer, &halt);
1142 assert_eq!(control.request(AckPolicy::Disk), RequestOutcome::Staged);
1143 let mut publisher = booted.attach(&mut producer, &halt, None);
1144 assert_ne!(
1145 halt.verdict(),
1146 Verdict::Take,
1147 "a staged `disk` is no consent"
1148 );
1149 let outcome = request_while_polling(
1150 &control,
1151 &mut publisher,
1152 &mut producer,
1153 AckPolicy::Disk,
1154 &halt,
1155 );
1156 assert_eq!(outcome, RequestOutcome::Published);
1157 assert_eq!(
1158 changes(&mut consumer),
1159 [
1160 change(AckPolicy::TwoDisks, false),
1161 change(AckPolicy::Disk, false),
1162 change(AckPolicy::Disk, true),
1163 ]
1164 );
1165 assert_eq!(halt.verdict(), Verdict::Take, "the forced swap came last");
1166 }
1167
1168 #[test]
1172 fn a_staged_policy_equal_to_the_claims_is_not_journaled_at_attach() {
1173 let control = replicated(AckPolicy::DiskAndRam);
1174 let claim = control.claim();
1175 assert_eq!(control.request(AckPolicy::TwoDisks), RequestOutcome::Staged);
1176 assert_eq!(
1177 control.request(AckPolicy::DiskAndRam),
1178 RequestOutcome::Staged
1179 );
1180 let (mut producer, mut consumer) = ring::<TestEvent>(8);
1181 let halt = halted_gate();
1182 let _publisher = claim
1183 .journal_boot(first_change(AckPolicy::DiskAndRam), &mut producer, &halt)
1184 .attach(&mut producer, &halt, None);
1185 assert!(changes(&mut consumer).is_empty());
1186 }
1187
1188 #[test]
1191 fn a_request_wakes_a_parked_producer() {
1192 use std::os::fd::{AsRawFd, FromRawFd};
1193 let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK) };
1195 assert!(fd >= 0);
1196 let fd = Arc::new(unsafe { OwnedFd::from_raw_fd(fd) });
1198 let control = control_with_publish_wait(AckPolicy::DiskAndRam, Duration::from_millis(10));
1199 let (mut producer, _consumer) = ring::<TestEvent>(8);
1200 let _publisher =
1201 booted(&control).attach(&mut producer, &gate_over(None), Some(Arc::clone(&fd)));
1202 assert_eq!(
1203 control.request(AckPolicy::Disk),
1204 RequestOutcome::Refused(Refused::RingFull),
1205 "never polled, so the bound runs out"
1206 );
1207 let mut count = 0u64;
1208 let read = unsafe {
1210 libc::read(
1211 fd.as_raw_fd(),
1212 (&mut count as *mut u64).cast(),
1213 std::mem::size_of::<u64>(),
1214 )
1215 };
1216 assert_eq!((read, count), (8, 1), "the eventfd was written once");
1217 }
1218}