1use std::task::Poll;
10
11use serde::{Deserialize, Serialize};
12
13use crate::{Bytes, Grant};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum Reason {
22 Dropped,
24 Expired,
26 Refused,
28 Invalid,
30 Session(String),
32}
33
34impl Reason {
35 pub fn as_str(&self) -> &str {
37 match self {
38 Self::Dropped => "dropped",
39 Self::Expired => "expired",
40 Self::Refused => "refused",
41 Self::Invalid => "invalid",
42 Self::Session(reason) => reason,
43 }
44 }
45}
46
47impl std::fmt::Display for Reason {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.write_str(self.as_str())
50 }
51}
52
53impl From<&str> for Reason {
54 fn from(reason: &str) -> Self {
55 match reason {
56 "dropped" => Self::Dropped,
57 "expired" => Self::Expired,
58 "refused" => Self::Refused,
59 "invalid" => Self::Invalid,
60 other => Self::Session(other.to_string()),
61 }
62 }
63}
64
65impl From<String> for Reason {
66 fn from(reason: String) -> Self {
67 Self::from(reason.as_str())
68 }
69}
70
71impl Serialize for Reason {
72 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
73 serializer.serialize_str(self.as_str())
74 }
75}
76
77impl<'de> Deserialize<'de> for Reason {
78 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
79 Ok(String::deserialize(deserializer)?.into())
80 }
81}
82
83#[derive(Debug)]
84struct State {
85 grant: Grant,
86 epoch: u64,
88 closed: Option<(Reason, Bytes)>,
90 revalidate: u64,
93}
94
95#[derive(Debug)]
99pub struct Producer {
100 state: kio::Shared<State>,
101}
102
103impl Producer {
104 pub fn new(grant: Grant) -> (Self, Consumer) {
106 let state = kio::Shared::new(State {
107 grant,
108 epoch: 0,
109 closed: None,
110 revalidate: 0,
111 });
112 (Self { state: state.clone() }, Consumer { state, seen: 0 })
113 }
114
115 pub fn update(&self, grant: Grant) {
117 let mut state = self.state.lock();
118 if state.closed.is_some() {
119 return;
120 }
121 state.grant = grant;
122 state.epoch += 1;
123 }
124
125 pub fn revoke(self, reason: Reason) -> Reason {
128 self.finish(reason, Bytes::default()).0
129 }
130
131 pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<(Reason, Bytes)> {
134 let state = std::task::ready!(self.state.poll(waiter, |state| ready_if(state.closed.is_some())));
135 Poll::Ready(state.closed.clone().expect("waited for a close"))
136 }
137
138 pub async fn closed(&self) -> (Reason, Bytes) {
140 kio::wait(|waiter| self.poll_closed(waiter)).await
141 }
142
143 pub fn poll_revalidate(&self, waiter: &kio::Waiter) -> Poll<()> {
146 let mut state = std::task::ready!(self.state.poll(waiter, |state| ready_if(state.revalidate > 0)));
147 state.revalidate = 0;
148 Poll::Ready(())
149 }
150
151 pub async fn revalidate_requested(&self) {
153 kio::wait(|waiter| self.poll_revalidate(waiter)).await
154 }
155
156 pub(crate) fn finish(&self, reason: Reason, bytes: Bytes) -> (Reason, Bytes) {
157 self.state.lock().closed.get_or_insert((reason, bytes)).clone()
158 }
159}
160
161impl Drop for Producer {
162 fn drop(&mut self) {
163 self.finish(Reason::Dropped, Bytes::default());
164 }
165}
166
167#[derive(Debug)]
171pub struct Consumer {
172 state: kio::Shared<State>,
173 seen: u64,
174}
175
176impl Consumer {
177 pub fn fixed(grant: Grant) -> Self {
180 let state = kio::Shared::new(State {
181 grant,
182 epoch: 0,
183 closed: None,
184 revalidate: 0,
185 });
186 Self { state, seen: 0 }
187 }
188
189 pub fn grant(&self) -> Grant {
191 self.state.read().grant.clone()
192 }
193
194 pub fn poll_changed(&mut self, waiter: &kio::Waiter) -> Poll<Result<Grant, Reason>> {
196 let seen = self.seen;
197 let state = std::task::ready!(
198 self.state
199 .poll(waiter, |state| ready_if(state.epoch > seen || state.closed.is_some()))
200 );
201 if let Some((reason, _)) = &state.closed {
202 return Poll::Ready(Err(reason.clone()));
203 }
204 self.seen = state.epoch;
205 Poll::Ready(Ok(state.grant.clone()))
206 }
207
208 pub async fn changed(&mut self) -> Result<Grant, Reason> {
210 kio::wait(|waiter| self.poll_changed(waiter)).await
211 }
212
213 pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Reason> {
215 let state = std::task::ready!(self.state.poll(waiter, |state| ready_if(state.closed.is_some())));
216 Poll::Ready(state.closed.as_ref().expect("waited for a close").0.clone())
217 }
218
219 pub async fn closed(&self) -> Reason {
221 kio::wait(|waiter| self.poll_closed(waiter)).await
222 }
223
224 pub fn revalidate(&self) {
227 self.state.lock().revalidate += 1;
228 }
229
230 pub fn close(self, reason: impl Into<Reason>, bytes: Bytes) -> Reason {
234 self.state.lock().closed.get_or_insert((reason.into(), bytes)).0.clone()
235 }
236}
237
238impl Drop for Consumer {
239 fn drop(&mut self) {
240 self.state
241 .lock()
242 .closed
243 .get_or_insert((Reason::Dropped, Bytes::default()));
244 }
245}
246
247fn ready_if(condition: bool) -> Poll<()> {
248 if condition { Poll::Ready(()) } else { Poll::Pending }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use std::future::Future;
255 use std::pin::pin;
256 use std::task::{Context, Waker};
257
258 fn grant(publish: &str) -> Grant {
259 Grant::new([publish.parse().unwrap()].into_iter().collect(), Default::default())
260 }
261
262 fn poll<F: Future>(future: F) -> Poll<F::Output> {
263 pin!(future).poll(&mut Context::from_waker(Waker::noop()))
264 }
265
266 #[test]
267 fn update_wakes_the_consumer_once_per_change() {
268 let (producer, mut consumer) = Producer::new(grant("a/**"));
269 assert_eq!(consumer.grant(), grant("a/**"));
270 assert!(poll(consumer.changed()).is_pending());
271
272 producer.update(grant("b/**"));
273 assert_eq!(poll(consumer.changed()), Poll::Ready(Ok(grant("b/**"))));
274 assert_eq!(consumer.grant(), grant("b/**"));
275 assert!(poll(consumer.changed()).is_pending());
276 }
277
278 #[test]
279 fn revoke_reaches_the_consumer() {
280 let (producer, mut consumer) = Producer::new(grant("a/**"));
281 assert!(poll(consumer.closed()).is_pending());
282
283 producer.revoke(Reason::Refused);
284 assert_eq!(poll(consumer.closed()), Poll::Ready(Reason::Refused));
285 assert_eq!(poll(consumer.changed()), Poll::Ready(Err(Reason::Refused)));
286 }
287
288 #[test]
289 fn the_session_close_hands_over_byte_totals() {
290 let (producer, consumer) = Producer::new(grant("a/**"));
291 consumer.close("done", Bytes { sent: 3, received: 5 });
292 assert_eq!(
293 poll(producer.closed()),
294 Poll::Ready((Reason::Session("done".into()), Bytes { sent: 3, received: 5 }))
295 );
296 }
297
298 #[test]
299 fn dropping_the_producer_revokes() {
300 let (producer, consumer) = Producer::new(grant("a/**"));
301 drop(producer);
302 assert_eq!(poll(consumer.closed()), Poll::Ready(Reason::Dropped));
303 }
304
305 #[test]
306 fn dropping_the_consumer_reports_zero_bytes() {
307 let (producer, consumer) = Producer::new(grant("a/**"));
308 drop(consumer);
309 assert_eq!(
310 poll(producer.closed()),
311 Poll::Ready((Reason::Dropped, Bytes::default()))
312 );
313 }
314
315 #[test]
316 fn the_session_close_reaches_the_producer_and_the_first_reason_wins() {
317 let (producer, consumer) = Producer::new(grant("a/**"));
318 assert!(poll(producer.closed()).is_pending());
319
320 let recorded = consumer.close("disconnected", Bytes { sent: 1, received: 2 });
321 assert_eq!(recorded, Reason::Session("disconnected".into()));
322 assert_eq!(
323 poll(producer.closed()),
324 Poll::Ready((Reason::Session("disconnected".into()), Bytes { sent: 1, received: 2 }))
325 );
326
327 assert_eq!(producer.revoke(Reason::Expired), Reason::Session("disconnected".into()));
329 }
330
331 #[test]
332 fn a_fixed_lease_only_ends_by_the_holder() {
333 let mut consumer = Consumer::fixed(grant("a/**"));
334 assert_eq!(consumer.grant(), grant("a/**"));
335 assert!(poll(consumer.changed()).is_pending());
336 assert!(poll(consumer.closed()).is_pending());
337 consumer.revalidate();
338 assert_eq!(consumer.grant(), grant("a/**"));
339 assert!(poll(consumer.changed()).is_pending());
340 assert!(poll(consumer.closed()).is_pending());
341 assert_eq!(consumer.close("done", Bytes::default()), Reason::Session("done".into()));
342 }
343
344 #[test]
345 fn n_nudges_wake_the_producer_once() {
346 let (producer, consumer) = Producer::new(grant("a/**"));
347 assert!(poll(producer.revalidate_requested()).is_pending());
348
349 for _ in 0..8 {
350 consumer.revalidate();
351 }
352 assert_eq!(poll(producer.revalidate_requested()), Poll::Ready(()));
353 assert!(poll(producer.revalidate_requested()).is_pending());
354
355 consumer.revalidate();
356 assert_eq!(poll(producer.revalidate_requested()), Poll::Ready(()));
357 }
358
359 #[test]
360 fn reason_round_trips_as_one_string() {
361 for (reason, text) in [
362 (Reason::Dropped, "\"dropped\""),
363 (Reason::Expired, "\"expired\""),
364 (Reason::Refused, "\"refused\""),
365 (Reason::Invalid, "\"invalid\""),
366 (Reason::Session("protocol error".into()), "\"protocol error\""),
367 ] {
368 assert_eq!(serde_json::to_string(&reason).unwrap(), text);
369 assert_eq!(serde_json::from_str::<Reason>(text).unwrap(), reason);
370 }
371 }
372}