1use std::borrow::Cow;
8use std::pin::Pin;
9use std::str::FromStr;
10use std::sync::Arc;
11use std::time::Duration;
12use std::{collections::HashMap, path::PathBuf};
13use std::{env, fmt};
14
15use crate::CancellationToken;
16use crate::transports::etcd as etcd_transport;
17use async_trait::async_trait;
18use futures::StreamExt;
19use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, percent_encode};
20use serde::{Deserialize, Serialize};
21use tokio_util::task::AbortOnDropHandle;
22
23mod mem;
24#[cfg(test)]
25pub(crate) use mem::MEMORY_EVENT_BUFFER_CAPACITY;
26pub use mem::MemoryStore;
27mod nats;
28pub use nats::NATSStore;
29mod etcd;
30pub use etcd::EtcdStore;
31mod file;
32pub use file::FileStore;
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub struct Key(String);
38
39impl Key {
40 pub fn new(s: String) -> Key {
41 Key(s)
42 }
43
44 pub fn from_url_safe(s: &str) -> Key {
47 Key(percent_decode_str(s).decode_utf8_lossy().to_string())
48 }
49
50 pub fn url_safe(&self) -> Cow<'_, str> {
53 percent_encode(self.0.as_bytes(), NON_ALPHANUMERIC).into()
54 }
55}
56
57impl From<&str> for Key {
58 fn from(s: &str) -> Key {
59 Key::new(s.to_string())
60 }
61}
62
63impl fmt::Display for Key {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.0)
66 }
67}
68
69impl AsRef<str> for Key {
70 fn as_ref(&self) -> &str {
71 &self.0
72 }
73}
74
75impl From<&Key> for String {
76 fn from(k: &Key) -> String {
77 k.0.clone()
78 }
79}
80
81#[derive(Debug, Clone, PartialEq)]
82pub struct KeyValue {
83 key: Key,
84 value: bytes::Bytes,
85}
86
87impl KeyValue {
88 pub fn new(key: Key, value: bytes::Bytes) -> Self {
89 KeyValue { key, value }
90 }
91
92 pub fn key(&self) -> String {
93 self.key.clone().to_string()
94 }
95
96 pub fn key_str(&self) -> &str {
97 self.key.as_ref()
98 }
99
100 pub fn value(&self) -> &[u8] {
101 &self.value
102 }
103
104 pub fn value_str(&self) -> anyhow::Result<&str> {
105 std::str::from_utf8(self.value()).map_err(From::from)
106 }
107}
108
109#[derive(Debug, Clone, PartialEq)]
111pub enum WatchEvent {
112 Put(KeyValue),
114 Delete(Key),
116 Resync(HashMap<Key, bytes::Bytes>),
120}
121
122#[async_trait]
123pub trait Store: Send + Sync {
124 type Bucket: Bucket + Send + Sync + 'static;
125
126 async fn get_or_create_bucket(
127 &self,
128 bucket_name: &str,
129 ttl: Option<Duration>,
131 ) -> Result<Self::Bucket, StoreError>;
132
133 async fn get_bucket(&self, bucket_name: &str) -> Result<Option<Self::Bucket>, StoreError>;
134
135 fn connection_id(&self) -> u64;
136
137 fn shutdown(&self);
138
139 async fn check_connection(&self) -> Result<(), StoreError> {
140 Ok(())
141 }
142}
143
144#[derive(Clone, Debug, Default)]
145pub enum Selector {
146 Etcd(Box<etcd_transport::ClientOptions>),
148 File(PathBuf),
149 #[default]
150 Memory,
151 }
153
154impl fmt::Display for Selector {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 match self {
157 Selector::Etcd(opts) => {
158 let urls = opts.etcd_url.join(",");
159 write!(f, "Etcd({urls})")
160 }
161 Selector::File(path) => write!(f, "File({})", path.display()),
162 Selector::Memory => write!(f, "Memory"),
163 }
164 }
165}
166
167impl FromStr for Selector {
168 type Err = anyhow::Error;
169
170 fn from_str(s: &str) -> anyhow::Result<Selector> {
171 match s {
172 "etcd" => Ok(Self::Etcd(Box::default())),
173 "file" => {
174 let root = env::var("DYN_FILE_KV")
175 .map(PathBuf::from)
176 .unwrap_or_else(|_| env::temp_dir().join("dynamo_store_kv"));
177 Ok(Self::File(root))
178 }
179 "mem" => Ok(Self::Memory),
180 x => anyhow::bail!("Unknown key-value store type '{x}'"),
181 }
182 }
183}
184
185impl TryFrom<String> for Selector {
186 type Error = anyhow::Error;
187
188 fn try_from(s: String) -> anyhow::Result<Selector> {
189 s.parse()
190 }
191}
192
193#[allow(clippy::large_enum_variant)]
194enum KeyValueStoreEnum {
195 Memory(MemoryStore),
196 Nats(NATSStore),
197 Etcd(EtcdStore),
198 File(FileStore),
199}
200
201impl KeyValueStoreEnum {
202 async fn get_or_create_bucket(
203 &self,
204 bucket_name: &str,
205 ttl: Option<Duration>,
207 ) -> Result<Box<dyn Bucket>, StoreError> {
208 use KeyValueStoreEnum::*;
209 Ok(match self {
210 Memory(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
211 Nats(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
212 Etcd(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
213 File(x) => Box::new(x.get_or_create_bucket(bucket_name, ttl).await?),
214 })
215 }
216
217 async fn get_bucket(&self, bucket_name: &str) -> Result<Option<Box<dyn Bucket>>, StoreError> {
218 use KeyValueStoreEnum::*;
219 let maybe_bucket: Option<Box<dyn Bucket>> = match self {
220 Memory(x) => x
221 .get_bucket(bucket_name)
222 .await?
223 .map(|b| Box::new(b) as Box<dyn Bucket>),
224 Nats(x) => x
225 .get_bucket(bucket_name)
226 .await?
227 .map(|b| Box::new(b) as Box<dyn Bucket>),
228 Etcd(x) => x
229 .get_bucket(bucket_name)
230 .await?
231 .map(|b| Box::new(b) as Box<dyn Bucket>),
232 File(x) => x
233 .get_bucket(bucket_name)
234 .await?
235 .map(|b| Box::new(b) as Box<dyn Bucket>),
236 };
237 Ok(maybe_bucket)
238 }
239
240 fn connection_id(&self) -> u64 {
241 use KeyValueStoreEnum::*;
242 match self {
243 Memory(x) => x.connection_id(),
244 Etcd(x) => x.connection_id(),
245 Nats(x) => x.connection_id(),
246 File(x) => x.connection_id(),
247 }
248 }
249
250 fn shutdown(&self) {
251 use KeyValueStoreEnum::*;
252 match self {
253 Memory(x) => x.shutdown(),
254 Etcd(x) => x.shutdown(),
255 Nats(x) => x.shutdown(),
256 File(x) => x.shutdown(),
257 }
258 }
259
260 async fn check_connection(&self) -> Result<(), StoreError> {
261 use KeyValueStoreEnum::*;
262 match self {
263 Memory(_) | File(_) => Ok(()),
264 Etcd(x) => x.check_connection().await,
265 Nats(x) => x.check_connection().await,
266 }
267 }
268}
269
270#[derive(Clone)]
271pub struct Manager(Arc<KeyValueStoreEnum>);
272
273impl Default for Manager {
274 fn default() -> Self {
275 Manager::memory()
276 }
277}
278
279impl Manager {
280 pub fn memory() -> Self {
282 Self::new(KeyValueStoreEnum::Memory(MemoryStore::new()))
283 }
284
285 pub fn etcd(etcd_client: crate::transports::etcd::Client) -> Self {
286 Self::new(KeyValueStoreEnum::Etcd(EtcdStore::new(etcd_client)))
287 }
288
289 pub fn file<P: Into<PathBuf>>(cancel_token: CancellationToken, root: P) -> Self {
290 Self::new(KeyValueStoreEnum::File(FileStore::new(cancel_token, root)))
291 }
292
293 fn new(s: KeyValueStoreEnum) -> Manager {
294 Manager(Arc::new(s))
295 }
296
297 pub async fn get_or_create_bucket(
298 &self,
299 bucket_name: &str,
300 ttl: Option<Duration>,
302 ) -> Result<Box<dyn Bucket>, StoreError> {
303 self.0.get_or_create_bucket(bucket_name, ttl).await
304 }
305
306 pub async fn get_bucket(
307 &self,
308 bucket_name: &str,
309 ) -> Result<Option<Box<dyn Bucket>>, StoreError> {
310 self.0.get_bucket(bucket_name).await
311 }
312
313 pub fn connection_id(&self) -> u64 {
314 self.0.connection_id()
315 }
316
317 pub async fn check_connection(&self) -> Result<(), StoreError> {
318 self.0.check_connection().await
319 }
320
321 pub async fn load<T: for<'a> Deserialize<'a>>(
322 &self,
323 bucket: &str,
324 key: &Key,
325 ) -> Result<Option<T>, StoreError> {
326 let Some(bucket) = self.0.get_bucket(bucket).await? else {
327 return Ok(None);
329 };
330 Ok(match bucket.get(key).await? {
331 Some(card_bytes) => {
332 let card: T = serde_json::from_slice(card_bytes.as_ref())?;
333 Some(card)
334 }
335 None => None,
336 })
337 }
338
339 async fn forward_watch_event(
340 tx: &tokio::sync::mpsc::Sender<WatchEvent>,
341 event: WatchEvent,
342 cancel_token: &CancellationToken,
343 bucket_name: &str,
344 ) -> bool {
345 tokio::select! {
346 _ = cancel_token.cancelled() => false,
347 result = tx.send(event) => {
348 if let Err(error) = result {
349 tracing::error!(
350 bucket_name,
351 %error,
352 "KeyValueStoreManager.watch receiver closed"
353 );
354 false
355 } else {
356 true
357 }
358 }
359 }
360 }
361
362 pub async fn watch(
383 self: Arc<Self>,
384 bucket_name: &str,
385 bucket_ttl: Option<Duration>,
386 cancel_token: CancellationToken,
387 ) -> Result<
388 (
389 tokio::task::JoinHandle<()>,
390 tokio::sync::mpsc::Receiver<WatchEvent>,
391 ),
392 StoreError,
393 > {
394 let bucket_name = bucket_name.to_string();
395 let (tx, rx) = tokio::sync::mpsc::channel(16384);
397 let (established_tx, established_rx) = tokio::sync::oneshot::channel();
398 let watch_task = AbortOnDropHandle::new(tokio::spawn({
401 let bucket_name = bucket_name.clone();
402 let cancel_token = cancel_token.clone();
403 async move {
404 let bucket = match self.0.get_or_create_bucket(&bucket_name, bucket_ttl).await {
406 Ok(bucket) => bucket,
407 Err(error) => {
408 let _ = established_tx.send(Err(error));
409 return;
410 }
411 };
412 let mut stream = match bucket.watch().await {
416 Ok(stream) => stream,
417 Err(error) => {
418 let _ = established_tx.send(Err(error));
419 return;
420 }
421 };
422 if established_tx.send(Ok(())).is_err() {
424 return;
425 }
426
427 loop {
428 let event = tokio::select! {
429 () = cancel_token.cancelled() => break,
430 () = tx.closed() => break,
432 result = stream.next() => match result {
433 Some(event) => event,
434 None => break,
435 }
436 };
437 if !Self::forward_watch_event(&tx, event, &cancel_token, &bucket_name).await {
438 break;
439 }
440 }
441 }
442 }));
443
444 let established = tokio::select! {
445 biased;
446 () = cancel_token.cancelled() => {
448 watch_task.abort();
449 let _ = watch_task.await;
450 return Err(StoreError::WatchCancelled(bucket_name));
451 }
452 established = established_rx => established,
453 };
454 established.map_err(|_| {
455 StoreError::ProviderError(format!(
456 "the watch task for bucket '{bucket_name}' ended before it established the watch"
457 ))
458 })??;
459
460 Ok((watch_task.detach(), rx))
461 }
462
463 pub async fn publish<T: Serialize + Versioned + Send + Sync>(
464 &self,
465 bucket_name: &str,
466 bucket_ttl: Option<Duration>,
467 key: &Key,
468 obj: &mut T,
469 ) -> anyhow::Result<StoreOutcome> {
470 let obj_json = serde_json::to_vec(obj)?;
471 let bucket = self.0.get_or_create_bucket(bucket_name, bucket_ttl).await?;
472
473 let outcome = bucket.insert(key, obj_json.into(), obj.revision()).await?;
474
475 match outcome {
476 StoreOutcome::Created(revision) | StoreOutcome::Exists(revision) => {
477 obj.set_revision(revision);
478 }
479 }
480 Ok(outcome)
481 }
482
483 pub fn shutdown(&self) {
486 self.0.shutdown()
487 }
488}
489
490#[async_trait]
492pub trait Bucket: Send + Sync {
493 async fn insert(
497 &self,
498 key: &Key,
499 value: bytes::Bytes,
500 revision: u64,
501 ) -> Result<StoreOutcome, StoreError>;
502
503 async fn get(&self, key: &Key) -> Result<Option<bytes::Bytes>, StoreError>;
506
507 async fn compare_and_replace(
515 &self,
516 key: &Key,
517 expected: bytes::Bytes,
518 value: bytes::Bytes,
519 ) -> Result<StoreOutcome, StoreError>;
520
521 async fn delete(&self, key: &Key) -> Result<(), StoreError>;
524
525 async fn watch(
536 &self,
537 ) -> Result<Pin<Box<dyn futures::Stream<Item = WatchEvent> + Send + '_>>, StoreError>;
538
539 async fn entries(&self) -> Result<HashMap<Key, bytes::Bytes>, StoreError>;
543}
544
545#[derive(Debug, Copy, Clone, Eq, PartialEq)]
546pub enum StoreOutcome {
547 Created(u64),
550 Exists(u64),
552}
553impl fmt::Display for StoreOutcome {
554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555 match self {
556 StoreOutcome::Created(revision) => write!(f, "Created at {revision}"),
557 StoreOutcome::Exists(revision) => write!(f, "Exists at {revision}"),
558 }
559 }
560}
561
562#[derive(thiserror::Error, Debug)]
563pub enum StoreError {
564 #[error("Could not find bucket '{0}'")]
565 MissingBucket(String),
566
567 #[error("Could not find key '{0}'")]
568 MissingKey(String),
569
570 #[error("Internal storage error: '{0}'")]
571 ProviderError(String),
572
573 #[error("Internal NATS error: {0}")]
574 NATSError(String),
575
576 #[error("Internal etcd error: {0}")]
577 EtcdError(String),
578
579 #[error("Internal filesystem error: {0}")]
580 FilesystemError(String),
581
582 #[error("Key Value Error: {0} for bucket '{1}'")]
583 KeyValueError(String, String),
584
585 #[error("Watch on bucket '{0}' was cancelled before it was established")]
588 WatchCancelled(String),
589
590 #[error("Error decoding bytes: {0}")]
591 JSONDecodeError(#[from] serde_json::error::Error),
592
593 #[error("Race condition, retry the call")]
594 Retry,
595}
596
597pub trait Versioned {
600 fn revision(&self) -> u64;
601 fn set_revision(&mut self, r: u64);
602}
603
604#[cfg(test)]
605mod tests {
606 use std::sync::Arc;
607
608 use super::*;
609 use futures::{FutureExt, StreamExt, pin_mut};
610
611 const BUCKET_NAME: &str = "v1/mdc";
612
613 #[allow(dead_code)]
616 pub struct TappableStream {
617 tx: tokio::sync::broadcast::Sender<WatchEvent>,
618 }
619
620 #[allow(dead_code)]
621 impl TappableStream {
622 async fn new<T>(stream: T, max_size: usize) -> Self
623 where
624 T: futures::Stream<Item = WatchEvent> + Send + 'static,
625 {
626 let (tx, _) = tokio::sync::broadcast::channel(max_size);
627 let tx2 = tx.clone();
628 tokio::spawn(async move {
629 pin_mut!(stream);
630 while let Some(x) = stream.next().await {
631 let _ = tx2.send(x);
632 }
633 });
634 TappableStream { tx }
635 }
636
637 fn subscribe(&self) -> tokio::sync::broadcast::Receiver<WatchEvent> {
638 self.tx.subscribe()
639 }
640 }
641
642 fn init() {
643 crate::logging::init();
644 }
645
646 #[tokio::test]
647 async fn manager_watch_emits_initial_snapshot_once_before_updates() {
648 let manager = Arc::new(Manager::memory());
649 let bucket = manager
650 .get_or_create_bucket(BUCKET_NAME, None)
651 .await
652 .unwrap();
653 let key = Key::new("ns/worker/generate/1".to_string());
654 bucket.insert(&key, "old".into(), 1).await.unwrap();
655
656 let cancel_token = CancellationToken::new();
657 let (watch_task, mut rx) = manager
658 .clone()
659 .watch(BUCKET_NAME, None, cancel_token.clone())
660 .await
661 .unwrap();
662
663 let first = tokio::time::timeout(Duration::from_secs(1), rx.recv())
664 .await
665 .unwrap()
666 .unwrap();
667 let WatchEvent::Resync(snapshot) = first else {
668 panic!("expected the initial resync, got {first:?}");
669 };
670 assert_eq!(
671 snapshot.get(&key).map(|value| value.as_ref()),
672 Some(b"old".as_slice())
673 );
674
675 bucket.insert(&key, "new".into(), 2).await.unwrap();
676 let second = tokio::time::timeout(Duration::from_secs(1), rx.recv())
677 .await
678 .unwrap()
679 .unwrap();
680 let WatchEvent::Put(second) = second else {
681 panic!("expected updated put");
682 };
683 assert_eq!(
684 second.value(),
685 b"new",
686 "the initial value must not be replayed after the snapshot"
687 );
688
689 cancel_token.cancel();
690 watch_task.await.unwrap();
691 }
692
693 #[tokio::test]
694 async fn manager_watch_reports_a_delete_that_follows_establishment() {
695 let manager = Arc::new(Manager::memory());
696 let bucket = manager
697 .get_or_create_bucket(BUCKET_NAME, None)
698 .await
699 .unwrap();
700 let key = Key::new("ns/worker/generate/1".to_string());
701 bucket.insert(&key, "value".into(), 1).await.unwrap();
702
703 let cancel_token = CancellationToken::new();
704 let (watch_task, mut rx) = manager
705 .clone()
706 .watch(BUCKET_NAME, None, cancel_token.clone())
707 .await
708 .unwrap();
709 bucket.delete(&key).await.unwrap();
710
711 let first = tokio::time::timeout(Duration::from_secs(1), rx.recv())
712 .await
713 .unwrap()
714 .unwrap();
715 let WatchEvent::Resync(snapshot) = first else {
716 panic!("expected the existing key in the initial snapshot, got {first:?}");
717 };
718 assert!(
719 snapshot.contains_key(&key),
720 "snapshot {snapshot:?} lacks {key}"
721 );
722
723 let second = tokio::time::timeout(Duration::from_secs(1), rx.recv())
724 .await
725 .unwrap()
726 .unwrap();
727 assert_eq!(
728 second,
729 WatchEvent::Delete(key),
730 "a delete after establishment must reach the receiver"
731 );
732
733 cancel_token.cancel();
734 watch_task.await.unwrap();
735 }
736
737 #[tokio::test]
738 async fn manager_watch_reports_an_unavailable_bucket() {
739 let root = tempfile::NamedTempFile::new().unwrap();
741 let cancel_token = CancellationToken::new();
742 let manager = Arc::new(Manager::file(cancel_token.clone(), root.path()));
743
744 let error = manager
745 .watch(BUCKET_NAME, None, cancel_token.clone())
746 .await
747 .expect_err("a bucket under a file cannot be created");
748
749 assert!(
750 matches!(error, StoreError::FilesystemError(_)),
751 "expected a filesystem error, got {error:?}"
752 );
753 cancel_token.cancel();
754 }
755
756 #[tokio::test]
757 async fn manager_watch_reports_cancellation_before_establishment() {
758 let manager = Arc::new(Manager::memory());
759 let cancel_token = CancellationToken::new();
760 cancel_token.cancel();
761
762 let error = manager
763 .clone()
764 .watch(BUCKET_NAME, None, cancel_token)
765 .await
766 .expect_err("a cancelled watch establishes no snapshot");
767
768 assert!(
769 matches!(&error, StoreError::WatchCancelled(bucket) if bucket == BUCKET_NAME),
770 "expected a cancelled watch, got {error:?}"
771 );
772 tokio::task::yield_now().await;
774 assert!(
775 manager.get_bucket(BUCKET_NAME).await.unwrap().is_none(),
776 "a cancelled watch created the bucket it gave up on"
777 );
778 }
779
780 #[tokio::test]
781 async fn manager_watch_abandoned_before_establishment_creates_no_bucket() {
782 let manager = Arc::new(Manager::memory());
783
784 let watch = manager
785 .clone()
786 .watch(BUCKET_NAME, None, CancellationToken::new());
787 assert!(
788 watch.now_or_never().is_none(),
789 "the watch completed on its first poll, before its setup task could run"
790 );
791
792 tokio::task::yield_now().await;
793 assert!(
794 manager.get_bucket(BUCKET_NAME).await.unwrap().is_none(),
795 "an abandoned watch created the bucket nobody waits for"
796 );
797 }
798
799 #[tokio::test]
800 async fn manager_watch_task_ends_when_its_receiver_drops() {
801 let manager = Arc::new(Manager::memory());
802 let (watch_task, rx) = manager
803 .watch(BUCKET_NAME, None, CancellationToken::new())
804 .await
805 .unwrap();
806
807 drop(rx);
808
809 tokio::time::timeout(Duration::from_secs(1), watch_task)
810 .await
811 .expect("the watch task outlived its receiver on a quiet bucket")
812 .unwrap();
813 }
814
815 #[tokio::test]
816 async fn saturated_watch_channel_delivers_final_taint_state() {
817 let cancel_token = CancellationToken::new();
818 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
819 let first = WatchEvent::Put(KeyValue::new(
820 Key::new("v1/mdc/ns/worker/generate/1".to_string()),
821 br#"{"runtime_config":{"taints":["slow"]}}"#[..].into(),
822 ));
823 assert!(Manager::forward_watch_event(&tx, first.clone(), &cancel_token, BUCKET_NAME).await);
824
825 let final_event = WatchEvent::Put(KeyValue::new(
826 Key::new("v1/mdc/ns/worker/generate/1".to_string()),
827 br#"{"runtime_config":{"taints":["fast"]}}"#[..].into(),
828 ));
829 let final_event_for_send = final_event.clone();
830 let tx_clone = tx.clone();
831 let cancel_clone = cancel_token.clone();
832 let send_task = tokio::spawn(async move {
833 Manager::forward_watch_event(
834 &tx_clone,
835 final_event_for_send,
836 &cancel_clone,
837 BUCKET_NAME,
838 )
839 .await
840 });
841
842 tokio::task::yield_now().await;
843 assert!(
844 !send_task.is_finished(),
845 "send must wait for channel capacity"
846 );
847 assert_eq!(rx.recv().await, Some(first));
848 assert!(send_task.await.unwrap());
849 assert_eq!(rx.recv().await, Some(final_event));
850 }
851
852 #[tokio::test]
853 async fn test_memory_storage() -> anyhow::Result<()> {
854 init();
855
856 let s = Arc::new(MemoryStore::new());
857 let s2 = Arc::clone(&s);
858
859 let bucket = s.get_or_create_bucket(BUCKET_NAME, None).await?;
860 let res = bucket.insert(&"test1".into(), "value1".into(), 0).await?;
861 assert_eq!(res, StoreOutcome::Created(0));
862
863 let expected = [
864 WatchEvent::Resync(HashMap::from([(Key::new("test1".into()), "value1".into())])),
865 WatchEvent::Put(KeyValue::new(Key::new("test2".into()), "value2".into())),
866 WatchEvent::Put(KeyValue::new(
867 Key::new("test2".into()),
868 "value2-updated".into(),
869 )),
870 WatchEvent::Put(KeyValue::new(Key::new("test3".into()), "value3".into())),
871 ];
872
873 let (got_first_tx, got_first_rx) = tokio::sync::oneshot::channel();
874 let ingress = tokio::spawn(async move {
875 let b2 = s2.get_or_create_bucket(BUCKET_NAME, None).await?;
876 let mut stream = b2.watch().await?;
877
878 let v = stream.next().await.unwrap();
880 assert_eq!(v, expected[0]);
881
882 got_first_tx.send(()).unwrap();
883
884 let v = stream.next().await.unwrap();
886 assert_eq!(v, expected[1]);
887
888 let v = stream.next().await.unwrap();
889 assert_eq!(v, expected[2]);
890
891 let v = stream.next().await.unwrap();
892 assert_eq!(v, expected[3]);
893
894 Ok::<_, StoreError>(())
895 });
896
897 got_first_rx.await?;
901
902 let res = bucket.insert(&"test2".into(), "value2".into(), 0).await?;
903 assert_eq!(res, StoreOutcome::Created(0));
904
905 let res = bucket.insert(&"test2".into(), "value2".into(), 0).await?;
907 assert_eq!(res, StoreOutcome::Exists(0));
908
909 let res = bucket
911 .insert(&"test2".into(), "value2-updated".into(), 1)
912 .await?;
913 assert_eq!(res, StoreOutcome::Created(1));
914
915 let res = bucket.insert(&"test3".into(), "value3".into(), 0).await?;
916 assert_eq!(res, StoreOutcome::Created(0));
917
918 let _ = ingress.await?;
920
921 Ok(())
922 }
923
924 #[tokio::test]
925 async fn test_broadcast_stream() -> anyhow::Result<()> {
926 init();
927
928 let s: &'static _ = Box::leak(Box::new(MemoryStore::new()));
929 let bucket: &'static _ =
930 Box::leak(Box::new(s.get_or_create_bucket(BUCKET_NAME, None).await?));
931
932 let res = bucket.insert(&"test1".into(), "value1".into(), 0).await?;
933 assert_eq!(res, StoreOutcome::Created(0));
934
935 let stream = bucket.watch().await?;
936 let tap = TappableStream::new(stream, 10).await;
937
938 let mut rx1 = tap.subscribe();
939 let mut rx2 = tap.subscribe();
940
941 let item = WatchEvent::Put(KeyValue::new(Key::new("test1".to_string()), "GK".into()));
942 let item_clone = item.clone();
943 let handle1 = tokio::spawn(async move {
944 let b = rx1.recv().await.unwrap();
945 assert_eq!(b, item_clone);
946 });
947 let handle2 = tokio::spawn(async move {
948 let b = rx2.recv().await.unwrap();
949 assert_eq!(b, item);
950 });
951
952 bucket.insert(&"test1".into(), "GK".into(), 1).await?;
953
954 let _ = futures::join!(handle1, handle2);
955 Ok(())
956 }
957}