rx_rust/operators/utility/
timeout.rs1use crate::disposable::{
2 Disposable, bound_drop_disposal::BoundDropDisposal, option_disposal::OptionDisposal,
3};
4use crate::observable::{Observable, Subscription};
5use crate::observer::{Flow, Observer, Termination};
6use crate::scheduler::{RecursionAction, Scheduler};
7use crate::utils::serialized_delivery::UpdateOutcome;
8use crate::utils::subscribe_with_context::{
9 self, SubscriptionContext, subscribe_with_context_owning_source,
10};
11use crate::utils::types::{MarkerType, MaybeSend};
12use educe::Educe;
13use std::time::{Duration, Instant};
14
15#[derive(Educe)]
16#[educe(Debug, Clone, PartialEq, Eq)]
17pub enum Error<E> {
18 Timeout,
19 SourceError(E),
20}
21
22#[derive(Educe)]
69#[educe(Debug, Clone)]
70pub struct Timeout<'or, OE, S> {
71 source: OE,
72 duration: Duration,
73 scheduler: S,
74 _marker: MarkerType<&'or ()>,
75}
76
77impl<'or, OE, S> Timeout<'or, OE, S> {
78 pub fn new(source: OE, duration: Duration, scheduler: S) -> Self {
79 Self {
80 source,
81 duration,
82 scheduler,
83 _marker: Default::default(),
84 }
85 }
86}
87
88impl<'or, T, E, OE, S> Observable<'static, T, Error<E>> for Timeout<'or, OE, S>
89where
90 T: MaybeSend + 'static,
91 E: MaybeSend + 'static,
92 OE: Observable<'or, T, E>,
93 OE::D: MaybeSend + 'static,
94 S: Scheduler + Clone + MaybeSend + 'static,
95{
96 type D = subscribe_with_context::OwningDisposal<'static>;
97
98 fn subscribe(
99 self,
100 observer: impl Observer<T, Error<E>> + MaybeSend + 'static,
101 ) -> Subscription<Self::D> {
102 let model = Model {
103 deadline: Instant::now() + self.duration,
104 };
105 subscribe_with_context_owning_source(observer, model, |context| {
106 let source_subscription = self.source.subscribe(TimeoutObserver {
107 context: context.clone(),
108 duration: self.duration,
109 });
110 let timer = setup_timer(context, &self.scheduler);
111 source_subscription.preceded_by(timer)
112 })
113 }
114}
115
116struct Model {
117 deadline: Instant,
118}
119
120struct TimeoutObserver<T, E, OR, D: Disposable> {
121 context: SubscriptionContext<T, Error<E>, OR, Model, D>,
122 duration: Duration,
123}
124
125impl<T, E, OR, D> Observer<T, E> for TimeoutObserver<T, E, OR, D>
126where
127 T: MaybeSend + 'static,
128 E: MaybeSend + 'static,
129 OR: Observer<T, Error<E>> + MaybeSend + 'static,
130 D: Disposable + MaybeSend + 'static,
131{
132 fn on_next(&mut self, value: T) -> Flow {
133 self.context.update_flow(|model| {
134 model.deadline = Instant::now() + self.duration;
135 UpdateOutcome::empty().with_next_event(value)
136 })
137 }
138
139 fn on_termination(self, termination: Termination<E>) {
140 self.context.send_termination(match termination {
141 Termination::Completed => Termination::Completed,
142 Termination::Error(error) => Termination::Error(Error::SourceError(error)),
143 });
144 }
145}
146
147fn setup_timer<T, E, OR, S, D>(
152 context: SubscriptionContext<T, Error<E>, OR, Model, D>,
153 scheduler: &S,
154) -> OptionDisposal<BoundDropDisposal<S::D>>
155where
156 T: MaybeSend + 'static,
157 E: MaybeSend + 'static,
158 OR: Observer<T, Error<E>> + MaybeSend + 'static,
159 S: Scheduler + Clone + MaybeSend + 'static,
160 D: Disposable + MaybeSend + 'static,
161{
162 let deadline = context.update(|model| UpdateOutcome::new(model.deadline).without_events());
163 let Ok(deadline) = deadline else {
164 return OptionDisposal::none();
166 };
167
168 let weak_context = context.downgrade();
169 let timer = scheduler.schedule_recursively(
170 move |_| {
171 let Some(context) = weak_context.upgrade() else {
172 return RecursionAction::Stop;
173 };
174 context
175 .update(|model| {
176 if Instant::now() < model.deadline {
177 return UpdateOutcome::new(RecursionAction::ContinueAt(model.deadline))
178 .without_events();
179 }
180 UpdateOutcome::new(RecursionAction::Stop)
181 .with_termination_event(Termination::Error(Error::Timeout))
182 })
183 .unwrap_or(RecursionAction::Stop)
184 },
185 Some(deadline.saturating_duration_since(Instant::now())),
186 );
187 OptionDisposal::some(timer)
188}