1use std::collections::BTreeMap;
13use std::fmt::Debug;
14use std::ops::{Deref, DerefMut};
15use std::sync::Arc;
16
17use differential_dataflow::difference::Monoid;
18use differential_dataflow::lattice::Lattice;
19use futures::StreamExt;
20use futures::stream::FuturesUnordered;
21use mz_dyncfg::{Config, ConfigSet, ConfigValHandle, ParameterScope};
22use mz_ore::collections::HashSet;
23use mz_ore::instrument;
24use mz_persist_client::batch::Batch;
25use mz_persist_client::cfg::USE_CRITICAL_SINCE_TXN;
26use mz_persist_client::critical::{Opaque, SinceHandle};
27use mz_persist_client::write::WriteHandle;
28use mz_persist_client::{Diagnostics, PersistClient, ShardId};
29use mz_persist_types::schema::SchemaId;
30use mz_persist_types::txn::{TxnsCodec, TxnsEntry};
31use mz_persist_types::{Codec, Codec64, StepForward};
32use timely::order::TotalOrder;
33use timely::progress::Timestamp;
34use tracing::debug;
35
36use crate::TxnsCodecDefault;
37use crate::metrics::Metrics;
38use crate::txn_cache::{TxnsCache, Unapplied};
39use crate::txn_write::Txn;
40
41#[derive(Debug)]
107pub struct TxnsHandle<K: Codec, V: Codec, T, D, C: TxnsCodec = TxnsCodecDefault> {
108 pub(crate) metrics: Arc<Metrics>,
109 pub(crate) txns_cache: TxnsCache<T, C>,
110 pub(crate) txns_write: WriteHandle<C::Key, C::Val, T, i64>,
111 pub(crate) txns_since: SinceHandle<C::Key, C::Val, T, i64>,
112 pub(crate) datas: DataHandles<K, V, T, D>,
113}
114
115impl<K, V, T, D, C> TxnsHandle<K, V, T, D, C>
116where
117 K: Debug + Codec,
118 V: Debug + Codec,
119 T: Timestamp + Lattice + TotalOrder + StepForward + Codec64 + Sync,
120 D: Debug + Monoid + Ord + Codec64 + Send + Sync,
121 C: TxnsCodec,
122{
123 pub async fn open(
133 init_ts: T,
134 client: PersistClient,
135 dyncfgs: ConfigSet,
136 metrics: Arc<Metrics>,
137 txns_id: ShardId,
138 opaque: Opaque,
139 ) -> Self {
140 let (txns_key_schema, txns_val_schema) = C::schemas();
141 let (mut txns_write, txns_read) = client
142 .open(
143 txns_id,
144 Arc::new(txns_key_schema),
145 Arc::new(txns_val_schema),
146 Diagnostics {
147 shard_name: "txns".to_owned(),
148 handle_purpose: "commit txns".to_owned(),
149 },
150 USE_CRITICAL_SINCE_TXN.get(client.dyncfgs()),
151 )
152 .await
153 .expect("txns schema shouldn't change");
154 let txns_since = client
155 .open_critical_since(
156 txns_id,
157 PersistClient::CONTROLLER_CRITICAL_SINCE,
160 opaque,
161 Diagnostics {
162 shard_name: "txns".to_owned(),
163 handle_purpose: "commit txns".to_owned(),
164 },
165 )
166 .await
167 .expect("txns schema shouldn't change");
168 let txns_cache = TxnsCache::init(init_ts, txns_read, &mut txns_write).await;
169 TxnsHandle {
170 metrics,
171 txns_cache,
172 txns_write,
173 txns_since,
174 datas: DataHandles {
175 dyncfgs,
176 client: Arc::new(client),
177 data_write_for_apply: BTreeMap::new(),
178 data_write_for_commit: BTreeMap::new(),
179 },
180 }
181 }
182
183 pub fn begin(&self) -> Txn<K, V, T, D> {
186 Txn::new()
189 }
190
191 #[instrument(level = "debug", fields(ts = ?register_ts))]
211 pub async fn register(
212 &mut self,
213 register_ts: T,
214 data_writes: impl IntoIterator<Item = WriteHandle<K, V, T, D>>,
215 ) -> Result<Tidy, T> {
216 let op = &Arc::clone(&self.metrics).register;
217 op.run(async {
218 let mut data_writes = data_writes.into_iter().collect::<Vec<_>>();
219
220 for data_write in &mut data_writes {
224 data_write
227 .try_register_schema()
228 .await
229 .expect("schema should be registered");
230 }
231
232 let updates = data_writes
233 .iter()
234 .map(|data_write| {
235 let data_id = data_write.shard_id();
236 let entry = TxnsEntry::Register(data_id, T::encode(®ister_ts));
237 (data_id, C::encode(entry))
238 })
239 .collect::<Vec<_>>();
240 let data_ids_debug = || {
241 data_writes
242 .iter()
243 .map(|x| format!("{:.9}", x.shard_id().to_string()))
244 .collect::<Vec<_>>()
245 .join(" ")
246 };
247
248 let mut txns_upper = self
249 .txns_write
250 .shared_upper()
251 .into_option()
252 .expect("txns should not be closed");
253 loop {
254 txns_upper = self.txns_cache.update_ge(&txns_upper).await.clone();
255 let updates = updates
259 .iter()
260 .flat_map(|(data_id, (key, val))| {
261 let registered =
262 self.txns_cache.registered_at_progress(data_id, &txns_upper);
263 (!registered).then_some(((key, val), ®ister_ts, 1))
264 })
265 .collect::<Vec<_>>();
266 if register_ts < txns_upper {
268 debug!(
269 "txns register {} at {:?} mismatch current={:?}",
270 data_ids_debug(),
271 register_ts,
272 txns_upper,
273 );
274 return Err(txns_upper);
275 }
276
277 let res = crate::small_caa(
278 || format!("txns register {}", data_ids_debug()),
279 &mut self.txns_write,
280 &updates,
281 txns_upper,
282 register_ts.step_forward(),
283 )
284 .await;
285 match res {
286 Ok(()) => {
287 debug!(
288 "txns register {} at {:?} success",
289 data_ids_debug(),
290 register_ts
291 );
292 break;
293 }
294 Err(new_txns_upper) => {
295 self.metrics.register.retry_count.inc();
296 txns_upper = new_txns_upper;
297 continue;
298 }
299 }
300 }
301 for data_write in data_writes {
302 match self.datas.data_write_for_commit.get(&data_write.shard_id()) {
308 None => {
309 self.datas
310 .data_write_for_commit
311 .insert(data_write.shard_id(), DataWriteCommit(data_write));
312 }
313 Some(previous) => {
314 let new_schema_id = data_write.schema_id().expect("ensured above");
315
316 if let Some(prev_schema_id) = previous.schema_id()
317 && prev_schema_id > new_schema_id
318 {
319 mz_ore::soft_panic_or_log!(
320 "tried registering a WriteHandle with an older SchemaId; \
321 prev_schema_id: {} new_schema_id: {} shard_id: {}",
322 prev_schema_id,
323 new_schema_id,
324 previous.shard_id(),
325 );
326 continue;
327 } else if previous.schema_id().is_none() {
328 mz_ore::soft_panic_or_log!(
329 "encountered data shard without a schema; shard_id: {}",
330 previous.shard_id(),
331 );
332 }
333
334 tracing::info!(
335 prev_schema_id = ?previous.schema_id(),
336 ?new_schema_id,
337 shard_id = %previous.shard_id(),
338 "replacing WriteHandle"
339 );
340 self.datas
341 .data_write_for_commit
342 .insert(data_write.shard_id(), DataWriteCommit(data_write));
343 }
344 }
345 }
346 let tidy = self.apply_le(®ister_ts).await;
347
348 Ok(tidy)
349 })
350 .await
351 }
352
353 #[instrument(level = "debug", fields(ts = ?forget_ts))]
373 pub async fn forget(
374 &mut self,
375 forget_ts: T,
376 data_ids: impl IntoIterator<Item = ShardId>,
377 ) -> Result<Tidy, T> {
378 let op = &Arc::clone(&self.metrics).forget;
379 op.run(async {
380 let data_ids = data_ids.into_iter().collect::<Vec<_>>();
381 let mut txns_upper = self
382 .txns_write
383 .shared_upper()
384 .into_option()
385 .expect("txns should not be closed");
386 loop {
387 txns_upper = self.txns_cache.update_ge(&txns_upper).await.clone();
388
389 let data_ids_debug = || {
390 data_ids
391 .iter()
392 .map(|x| format!("{:.9}", x.to_string()))
393 .collect::<Vec<_>>()
394 .join(" ")
395 };
396 let updates = data_ids
397 .iter()
398 .filter(|data_id| self.txns_cache.registered_at_progress(data_id, &txns_upper))
402 .map(|data_id| C::encode(TxnsEntry::Register(*data_id, T::encode(&forget_ts))))
403 .collect::<Vec<_>>();
404 let updates = updates
405 .iter()
406 .map(|(key, val)| ((key, val), &forget_ts, -1))
407 .collect::<Vec<_>>();
408
409 if forget_ts < txns_upper {
411 debug!(
412 "txns forget {} at {:?} mismatch current={:?}",
413 data_ids_debug(),
414 forget_ts,
415 txns_upper,
416 );
417 return Err(txns_upper);
418 }
419
420 {
423 let data_ids: HashSet<_> = data_ids.iter().cloned().collect();
424 let data_latest_unapplied = self
425 .txns_cache
426 .unapplied_batches
427 .values()
428 .rev()
429 .find(|(x, _, _)| data_ids.contains(x));
430 if let Some((_, _, latest_write)) = data_latest_unapplied {
431 debug!(
432 "txns forget {} applying latest write {:?}",
433 data_ids_debug(),
434 latest_write,
435 );
436 let latest_write = latest_write.clone();
437 let _tidy = self.apply_le(&latest_write).await;
438 }
439 }
440 let res = crate::small_caa(
441 || format!("txns forget {}", data_ids_debug()),
442 &mut self.txns_write,
443 &updates,
444 txns_upper,
445 forget_ts.step_forward(),
446 )
447 .await;
448 match res {
449 Ok(()) => {
450 debug!(
451 "txns forget {} at {:?} success",
452 data_ids_debug(),
453 forget_ts
454 );
455 break;
456 }
457 Err(new_txns_upper) => {
458 self.metrics.forget.retry_count.inc();
459 txns_upper = new_txns_upper;
460 continue;
461 }
462 }
463 }
464
465 let tidy = self.apply_le(&forget_ts).await;
468 for data_id in &data_ids {
469 self.datas.data_write_for_commit.remove(data_id);
470 }
471
472 Ok(tidy)
473 })
474 .await
475 }
476
477 #[instrument(level = "debug", fields(ts = ?forget_ts))]
480 pub async fn forget_all(&mut self, forget_ts: T) -> Result<(Vec<ShardId>, Tidy), T> {
481 let op = &Arc::clone(&self.metrics).forget_all;
482 op.run(async {
483 let mut txns_upper = self
484 .txns_write
485 .shared_upper()
486 .into_option()
487 .expect("txns should not be closed");
488 let registered = loop {
489 txns_upper = self.txns_cache.update_ge(&txns_upper).await.clone();
490
491 let registered = self.txns_cache.all_registered_at_progress(&txns_upper);
492 let data_ids_debug = || {
493 registered
494 .iter()
495 .map(|x| format!("{:.9}", x.to_string()))
496 .collect::<Vec<_>>()
497 .join(" ")
498 };
499 let updates = registered
500 .iter()
501 .map(|data_id| {
502 C::encode(crate::TxnsEntry::Register(*data_id, T::encode(&forget_ts)))
503 })
504 .collect::<Vec<_>>();
505 let updates = updates
506 .iter()
507 .map(|(key, val)| ((key, val), &forget_ts, -1))
508 .collect::<Vec<_>>();
509
510 if forget_ts < txns_upper {
512 debug!(
513 "txns forget_all {} at {:?} mismatch current={:?}",
514 data_ids_debug(),
515 forget_ts,
516 txns_upper,
517 );
518 return Err(txns_upper);
519 }
520
521 let data_latest_unapplied = self.txns_cache.unapplied_batches.values().last();
529 if let Some((_, _, latest_write)) = data_latest_unapplied {
530 debug!(
531 "txns forget_all {} applying latest write {:?}",
532 data_ids_debug(),
533 latest_write,
534 );
535 let latest_write = latest_write.clone();
536 let _tidy = self.apply_le(&latest_write).await;
537 }
538 let res = crate::small_caa(
539 || format!("txns forget_all {}", data_ids_debug()),
540 &mut self.txns_write,
541 &updates,
542 txns_upper,
543 forget_ts.step_forward(),
544 )
545 .await;
546 match res {
547 Ok(()) => {
548 debug!(
549 "txns forget_all {} at {:?} success",
550 data_ids_debug(),
551 forget_ts
552 );
553 break registered;
554 }
555 Err(new_txns_upper) => {
556 self.metrics.forget_all.retry_count.inc();
557 txns_upper = new_txns_upper;
558 continue;
559 }
560 }
561 };
562
563 for data_id in registered.iter() {
564 self.datas.data_write_for_commit.remove(data_id);
565 }
566 let tidy = self.apply_le(&forget_ts).await;
567
568 Ok((registered, tidy))
569 })
570 .await
571 }
572
573 #[instrument(level = "debug", fields(ts = ?ts))]
586 pub async fn apply_le(&mut self, ts: &T) -> Tidy {
587 let op = &self.metrics.apply_le;
588 op.run(async {
589 debug!("apply_le {:?}", ts);
590 let _ = self.txns_cache.update_gt(ts).await;
591 self.txns_cache.update_gauges(&self.metrics);
592
593 let mut unapplied_by_data = BTreeMap::<_, Vec<_>>::new();
594 for (data_id, unapplied, unapplied_ts) in self.txns_cache.unapplied() {
595 if ts < unapplied_ts {
596 break;
597 }
598 unapplied_by_data
599 .entry(*data_id)
600 .or_default()
601 .push((unapplied, unapplied_ts));
602 }
603
604 let retractions = FuturesUnordered::new();
605 for (data_id, unapplied) in unapplied_by_data {
606 let mut data_write = self.datas.take_write_for_apply(&data_id).await;
607 retractions.push(async move {
608 let mut ret = Vec::new();
609 for (unapplied, unapplied_ts) in unapplied {
610 match unapplied {
611 Unapplied::RegisterForget => {
612 let () = crate::empty_caa(
613 || {
614 format!(
615 "data {:.9} register/forget fill",
616 data_id.to_string()
617 )
618 },
619 &mut data_write,
620 unapplied_ts.clone(),
621 )
622 .await;
623 }
624 Unapplied::Batch(batch_raws) => {
625 let batch_raws = batch_raws
626 .into_iter()
627 .map(|batch_raw| batch_raw.as_slice())
628 .collect();
629 crate::apply_caa(
630 &mut data_write,
631 &batch_raws,
632 unapplied_ts.clone(),
633 )
634 .await;
635 for batch_raw in batch_raws {
636 ret.push((
640 batch_raw.to_vec(),
641 (T::encode(unapplied_ts), data_id),
642 ));
643 }
644 }
645 }
646 }
647 (data_write, ret)
648 });
649 }
650 let retractions = retractions.collect::<Vec<_>>().await;
651 let retractions = retractions
652 .into_iter()
653 .flat_map(|(data_write, retractions)| {
654 self.datas.put_write_for_apply(data_write);
655 retractions
656 })
657 .collect();
658
659 self.txns_cache.mark_register_applied(ts);
661
662 debug!("apply_le {:?} success", ts);
663 Tidy { retractions }
664 })
665 .await
666 }
667
668 #[cfg(test)]
673 pub async fn tidy_at(&mut self, tidy_ts: T, tidy: Tidy) -> Result<(), T> {
674 debug!("tidy at {:?}", tidy_ts);
675
676 let mut txn = self.begin();
677 txn.tidy(tidy);
678 let apply = txn.commit_at(self, tidy_ts.clone()).await?;
682 assert!(apply.is_empty());
683
684 debug!("tidy at {:?} success", tidy_ts);
685 Ok(())
686 }
687
688 pub async fn compact_to(&mut self, mut since_ts: T) {
694 let op = &self.metrics.compact_to;
695 op.run(async {
696 tracing::debug!("compact_to {:?}", since_ts);
697 let _ = self.txns_cache.update_gt(&since_ts).await;
698
699 let min_unapplied_ts = self.txns_cache.min_unapplied_ts();
703 if min_unapplied_ts < &since_ts {
704 since_ts.clone_from(min_unapplied_ts);
705 }
706 crate::cads::<T, C>(&mut self.txns_since, since_ts).await;
707 })
708 .await
709 }
710
711 pub async fn upgrade_version(&mut self) {
716 self.txns_since
717 .upgrade_version()
718 .await
719 .expect("invalid usage")
720 }
721
722 pub fn txns_id(&self) -> ShardId {
724 self.txns_write.shard_id()
725 }
726
727 pub fn read_cache(&self) -> &TxnsCache<T, C> {
729 &self.txns_cache
730 }
731}
732
733#[derive(Debug, Default)]
739pub struct Tidy {
740 pub(crate) retractions: BTreeMap<Vec<u8>, ([u8; 8], ShardId)>,
741}
742
743impl Tidy {
744 pub fn merge(&mut self, other: Tidy) {
746 self.retractions.extend(other.retractions)
747 }
748}
749
750#[derive(Debug)]
752pub(crate) struct DataHandles<K: Codec, V: Codec, T, D> {
753 pub(crate) dyncfgs: ConfigSet,
754 pub(crate) client: Arc<PersistClient>,
755 data_write_for_apply: BTreeMap<ShardId, DataWriteApply<K, V, T, D>>,
759 data_write_for_commit: BTreeMap<ShardId, DataWriteCommit<K, V, T, D>>,
768}
769
770impl<K, V, T, D> DataHandles<K, V, T, D>
771where
772 K: Debug + Codec,
773 V: Debug + Codec,
774 T: Timestamp + Lattice + TotalOrder + Codec64 + Sync,
775 D: Monoid + Ord + Codec64 + Send + Sync,
776{
777 async fn open_data_write_for_apply(&self, data_id: ShardId) -> DataWriteApply<K, V, T, D> {
778 let diagnostics = Diagnostics::from_purpose("txn data");
779 let schemas = self
780 .client
781 .latest_schema::<K, V, T, D>(data_id, diagnostics.clone())
782 .await
783 .expect("codecs have not changed");
784 let (key_schema, val_schema) = match schemas {
785 Some((_, key_schema, val_schema)) => (Arc::new(key_schema), Arc::new(val_schema)),
786 None => unreachable!("data shard {} should have a schema", data_id),
789 };
790 let wrapped = self
791 .client
792 .open_writer(data_id, key_schema, val_schema, diagnostics)
793 .await
794 .expect("schema shouldn't change");
795 DataWriteApply {
796 apply_ensure_schema_match: APPLY_ENSURE_SCHEMA_MATCH.handle(&self.dyncfgs),
797 client: Arc::clone(&self.client),
798 wrapped,
799 }
800 }
801
802 pub(crate) async fn take_write_for_apply(
803 &mut self,
804 data_id: &ShardId,
805 ) -> DataWriteApply<K, V, T, D> {
806 if let Some(data_write) = self.data_write_for_apply.remove(data_id) {
807 return data_write;
808 }
809 self.open_data_write_for_apply(*data_id).await
810 }
811
812 pub(crate) fn put_write_for_apply(&mut self, data_write: DataWriteApply<K, V, T, D>) {
813 self.data_write_for_apply
814 .insert(data_write.shard_id(), data_write);
815 }
816
817 pub(crate) fn take_write_for_commit(
818 &mut self,
819 data_id: &ShardId,
820 ) -> Option<DataWriteCommit<K, V, T, D>> {
821 self.data_write_for_commit.remove(data_id)
822 }
823
824 pub(crate) fn put_write_for_commit(&mut self, data_write: DataWriteCommit<K, V, T, D>) {
825 let prev = self
826 .data_write_for_commit
827 .insert(data_write.shard_id(), data_write);
828 assert!(prev.is_none());
829 }
830}
831
832#[derive(Debug)]
842pub(crate) struct DataWriteCommit<K: Codec, V: Codec, T, D>(pub(crate) WriteHandle<K, V, T, D>);
843
844impl<K: Codec, V: Codec, T, D> Deref for DataWriteCommit<K, V, T, D> {
845 type Target = WriteHandle<K, V, T, D>;
846
847 fn deref(&self) -> &Self::Target {
848 &self.0
849 }
850}
851
852impl<K: Codec, V: Codec, T, D> DerefMut for DataWriteCommit<K, V, T, D> {
853 fn deref_mut(&mut self) -> &mut Self::Target {
854 &mut self.0
855 }
856}
857
858#[derive(Debug)]
867pub(crate) struct DataWriteApply<K: Codec, V: Codec, T, D> {
868 client: Arc<PersistClient>,
869 apply_ensure_schema_match: ConfigValHandle<bool>,
870 pub(crate) wrapped: WriteHandle<K, V, T, D>,
871}
872
873impl<K: Codec, V: Codec, T, D> Deref for DataWriteApply<K, V, T, D> {
874 type Target = WriteHandle<K, V, T, D>;
875
876 fn deref(&self) -> &Self::Target {
877 &self.wrapped
878 }
879}
880
881impl<K: Codec, V: Codec, T, D> DerefMut for DataWriteApply<K, V, T, D> {
882 fn deref_mut(&mut self) -> &mut Self::Target {
883 &mut self.wrapped
884 }
885}
886
887pub(crate) const APPLY_ENSURE_SCHEMA_MATCH: Config<bool> = Config::new(
888 "txn_wal_apply_ensure_schema_match",
889 true,
890 "CYA to skip updating write handle to batch schema in apply",
891 ParameterScope::Environment,
892);
893
894fn at_most_one_schema(
895 schemas: impl Iterator<Item = SchemaId>,
896) -> Result<Option<SchemaId>, (SchemaId, SchemaId)> {
897 let mut schema = None;
898 for s in schemas {
899 match schema {
900 None => schema = Some(s),
901 Some(x) if s != x => return Err((s, x)),
902 Some(_) => continue,
903 }
904 }
905 Ok(schema)
906}
907
908impl<K, V, T, D> DataWriteApply<K, V, T, D>
909where
910 K: Debug + Codec,
911 V: Debug + Codec,
912 T: Timestamp + Lattice + TotalOrder + Codec64 + Sync,
913 D: Monoid + Ord + Codec64 + Send + Sync,
914{
915 pub(crate) async fn maybe_replace_with_batch_schema(&mut self, batches: &[Batch<K, V, T, D>]) {
916 if !self.apply_ensure_schema_match.get() {
919 return;
920 }
921 let batch_schema = at_most_one_schema(batches.iter().flat_map(|x| x.schemas()));
922 let batch_schema = batch_schema.unwrap_or_else(|_| {
923 panic!(
924 "txn-wal uses at most one schema to commit batches, got: {:?}",
925 batches.iter().flat_map(|x| x.schemas()).collect::<Vec<_>>()
926 )
927 });
928 let (batch_schema, handle_schema) = match (batch_schema, self.wrapped.schema_id()) {
929 (Some(batch_schema), Some(handle_schema)) if batch_schema != handle_schema => {
930 (batch_schema, handle_schema)
931 }
932 _ => return,
933 };
934
935 let data_id = self.shard_id();
936 let diagnostics = Diagnostics::from_purpose("txn data");
937 let (key_schema, val_schema) = self
938 .client
939 .get_schema::<K, V, T, D>(data_id, batch_schema, diagnostics.clone())
940 .await
941 .expect("codecs shouldn't change")
942 .expect("id must have been registered to create this batch");
943 let new_data_write = self
944 .client
945 .open_writer(
946 self.shard_id(),
947 Arc::new(key_schema),
948 Arc::new(val_schema),
949 diagnostics,
950 )
951 .await
952 .expect("codecs shouldn't change");
953 tracing::info!(
954 "updated {} write handle from {} to {} to apply batches",
955 data_id,
956 handle_schema,
957 batch_schema
958 );
959 assert_eq!(new_data_write.schema_id(), Some(batch_schema));
960 self.wrapped = new_data_write;
961 }
962}
963
964#[cfg(test)]
965mod tests {
966 use std::time::{Duration, UNIX_EPOCH};
967
968 use differential_dataflow::Hashable;
969 use futures::future::BoxFuture;
970 use mz_ore::assert_none;
971 use mz_ore::cast::CastFrom;
972 use mz_ore::collections::CollectionExt;
973 use mz_ore::metrics::MetricsRegistry;
974 use mz_persist_client::PersistLocation;
975 use mz_persist_client::cache::PersistClientCache;
976 use mz_persist_client::cfg::RetryParameters;
977 use rand::rngs::SmallRng;
978 use rand::{RngCore, SeedableRng};
979 use timely::progress::Antichain;
980 use tokio::sync::oneshot;
981 use tracing::{Instrument, info, info_span};
982
983 use crate::operator::DataSubscribe;
984 use crate::tests::{CommitLog, reader, write_directly, writer};
985
986 use super::*;
987
988 impl TxnsHandle<String, (), u64, i64, TxnsCodecDefault> {
989 pub(crate) async fn expect_open(client: PersistClient) -> Self {
990 Self::expect_open_id(client, ShardId::new()).await
991 }
992
993 pub(crate) async fn expect_open_id(client: PersistClient, txns_id: ShardId) -> Self {
994 let dyncfgs = crate::all_dyncfgs(client.dyncfgs().clone());
995 Self::open(
996 0,
997 client,
998 dyncfgs,
999 Arc::new(Metrics::new(&MetricsRegistry::new())),
1000 txns_id,
1001 Opaque::encode(&0u64),
1002 )
1003 .await
1004 }
1005
1006 pub(crate) fn new_log(&self) -> CommitLog {
1007 CommitLog::new((*self.datas.client).clone(), self.txns_id())
1008 }
1009
1010 pub(crate) async fn expect_register(&mut self, register_ts: u64) -> ShardId {
1011 self.expect_registers(register_ts, 1).await.into_element()
1012 }
1013
1014 pub(crate) async fn expect_registers(
1015 &mut self,
1016 register_ts: u64,
1017 amount: usize,
1018 ) -> Vec<ShardId> {
1019 let data_ids: Vec<_> = (0..amount).map(|_| ShardId::new()).collect();
1020 let mut writers = Vec::new();
1021 for data_id in &data_ids {
1022 writers.push(writer(&self.datas.client, *data_id).await);
1023 }
1024 self.register(register_ts, writers).await.unwrap();
1025 data_ids
1026 }
1027
1028 pub(crate) async fn expect_commit_at(
1029 &mut self,
1030 commit_ts: u64,
1031 data_id: ShardId,
1032 keys: &[&str],
1033 log: &CommitLog,
1034 ) -> Tidy {
1035 let mut txn = self.begin();
1036 for key in keys {
1037 txn.write(&data_id, (*key).into(), (), 1).await;
1038 }
1039 let tidy = txn
1040 .commit_at(self, commit_ts)
1041 .await
1042 .unwrap()
1043 .apply(self)
1044 .await;
1045 for key in keys {
1046 log.record((data_id, (*key).into(), commit_ts, 1));
1047 }
1048 tidy
1049 }
1050 }
1051
1052 #[mz_ore::test(tokio::test)]
1053 #[cfg_attr(miri, ignore)] async fn register_at() {
1055 let client = PersistClient::new_for_tests().await;
1056 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1057 let log = txns.new_log();
1058 let d0 = txns.expect_register(2).await;
1059
1060 txns.register(3, [writer(&client, d0).await]).await.unwrap();
1062
1063 let d1 = ShardId::new();
1067 assert_eq!(
1068 txns.register(2, [writer(&client, d1).await])
1069 .await
1070 .unwrap_err(),
1071 4
1072 );
1073
1074 txns.expect_commit_at(4, d0, &["foo"], &log).await;
1076 txns.register(5, [writer(&client, d1).await]).await.unwrap();
1077
1078 let d2 = ShardId::new();
1080 txns.register(6, [writer(&client, d0).await, writer(&client, d2).await])
1081 .await
1082 .unwrap();
1083
1084 let () = log.assert_snapshot(d0, 6).await;
1085 let () = log.assert_snapshot(d1, 6).await;
1086 }
1087
1088 #[mz_ore::test(tokio::test)]
1091 #[cfg_attr(miri, ignore)] #[should_panic(expected = "left: [(\"foo\", 2, 1)]\n right: [(\"foo\", 2, 2)]")]
1093 async fn incorrect_usage_register_write_same_time() {
1094 let client = PersistClient::new_for_tests().await;
1095 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1096 let log = txns.new_log();
1097 let d0 = txns.expect_register(1).await;
1098 let mut d0_write = writer(&client, d0).await;
1099
1100 let mut txn = txns.begin_test();
1102 txn.write(&d0, "foo".into(), (), 1).await;
1103 let apply = txn.commit_at(&mut txns, 2).await.unwrap();
1104 log.record_txn(2, &txn);
1105 let () = d0_write
1107 .compare_and_append(
1108 &[(("foo".to_owned(), ()), 2, 1)],
1109 Antichain::from_elem(2),
1110 Antichain::from_elem(3),
1111 )
1112 .await
1113 .unwrap()
1114 .unwrap();
1115 log.record((d0, "foo".into(), 2, 1));
1116 apply.apply(&mut txns).await;
1117
1118 log.assert_snapshot(d0, 2).await;
1120 }
1121
1122 #[mz_ore::test(tokio::test)]
1123 #[cfg_attr(miri, ignore)] async fn forget_at() {
1125 Box::pin(forget_at_inner()).await
1129 }
1130
1131 async fn forget_at_inner() {
1132 let client = PersistClient::new_for_tests().await;
1133 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1134 let log = txns.new_log();
1135
1136 txns.forget(1, [ShardId::new()]).await.unwrap();
1138
1139 txns.forget(2, (0..5).map(|_| ShardId::new()))
1141 .await
1142 .unwrap();
1143
1144 let d0 = txns.expect_register(3).await;
1146 txns.forget(4, [d0]).await.unwrap();
1147
1148 let ds = txns.expect_registers(5, 5).await;
1150 txns.forget(6, ds.clone()).await.unwrap();
1151
1152 txns.forget(7, [d0]).await.unwrap();
1154 txns.forget(8, ds.clone()).await.unwrap();
1155
1156 let d1 = txns.expect_register(9).await;
1159 assert_eq!(txns.forget(9, [d1]).await.unwrap_err(), 10);
1160
1161 let mut d0_write = writer(&client, d0).await;
1163 txns.expect_commit_at(10, d1, &["d1"], &log).await;
1164 let updates = [(("d0".to_owned(), ()), 10, 1)];
1165 d0_write
1166 .compare_and_append(&updates, d0_write.shared_upper(), Antichain::from_elem(11))
1167 .await
1168 .unwrap()
1169 .unwrap();
1170 log.record((d0, "d0".into(), 10, 1));
1171
1172 txns.register(11, [writer(&client, d0).await])
1174 .await
1175 .unwrap();
1176 let mut forget_expected = vec![d0, d1];
1177 forget_expected.sort();
1178 assert_eq!(txns.forget_all(12).await.unwrap().0, forget_expected);
1179
1180 d0_write
1182 .compare_and_append_batch(&mut [], d0_write.shared_upper(), Antichain::new(), true)
1183 .await
1184 .unwrap()
1185 .unwrap();
1186
1187 let () = log.assert_snapshot(d0, 12).await;
1188 let () = log.assert_snapshot(d1, 12).await;
1189
1190 for di in ds {
1191 let mut di_write = writer(&client, di).await;
1192
1193 di_write
1195 .compare_and_append_batch(&mut [], di_write.shared_upper(), Antichain::new(), true)
1196 .await
1197 .unwrap()
1198 .unwrap();
1199
1200 let () = log.assert_snapshot(di, 8).await;
1201 }
1202 }
1203
1204 #[mz_ore::test(tokio::test)]
1205 #[cfg_attr(miri, ignore)] async fn register_forget() {
1207 async fn step_some_past(subs: &mut Vec<DataSubscribe>, ts: u64) {
1208 for (idx, sub) in subs.iter_mut().enumerate() {
1209 if usize::cast_from(ts) % (idx + 1) == 0 {
1211 async {
1212 info!("stepping sub {} past {}", idx, ts);
1213 sub.step_past(ts).await;
1214 }
1215 .instrument(info_span!("sub", idx))
1216 .await;
1217 }
1218 }
1219 }
1220
1221 let client = PersistClient::new_for_tests().await;
1222 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1223 let log = txns.new_log();
1224 let d0 = ShardId::new();
1225 let mut d0_write = writer(&client, d0).await;
1226 let mut subs = Vec::new();
1227
1228 let mut ts = 0;
1238 while ts < 32 {
1239 subs.push(txns.read_cache().expect_subscribe(&client, d0, ts));
1240 ts += 1;
1241 info!("{} direct", ts);
1242 txns.begin().commit_at(&mut txns, ts).await.unwrap();
1243 write_directly(ts, &mut d0_write, &[&format!("d{}", ts)], &log).await;
1244 step_some_past(&mut subs, ts).await;
1245 if ts % 11 == 0 {
1246 txns.compact_to(ts).await;
1247 }
1248
1249 subs.push(txns.read_cache().expect_subscribe(&client, d0, ts));
1250 ts += 1;
1251 info!("{} register", ts);
1252 txns.register(ts, [writer(&client, d0).await])
1253 .await
1254 .unwrap();
1255 step_some_past(&mut subs, ts).await;
1256 if ts % 11 == 0 {
1257 txns.compact_to(ts).await;
1258 }
1259
1260 subs.push(txns.read_cache().expect_subscribe(&client, d0, ts));
1261 ts += 1;
1262 info!("{} txns", ts);
1263 txns.expect_commit_at(ts, d0, &[&format!("t{}", ts)], &log)
1264 .await;
1265 step_some_past(&mut subs, ts).await;
1266 if ts % 11 == 0 {
1267 txns.compact_to(ts).await;
1268 }
1269
1270 subs.push(txns.read_cache().expect_subscribe(&client, d0, ts));
1271 ts += 1;
1272 info!("{} forget", ts);
1273 txns.forget(ts, [d0]).await.unwrap();
1274 step_some_past(&mut subs, ts).await;
1275 if ts % 11 == 0 {
1276 txns.compact_to(ts).await;
1277 }
1278 }
1279
1280 for mut sub in subs.into_iter() {
1282 sub.step_past(ts).await;
1283 log.assert_eq(d0, sub.as_of, sub.progress(), sub.output().clone());
1284 }
1285 }
1286
1287 #[mz_ore::test(tokio::test)]
1296 #[cfg_attr(miri, ignore)] async fn race_data_shard_register_and_commit() {
1298 let client = PersistClient::new_for_tests().await;
1299 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1300 let d0 = txns.expect_register(1).await;
1301
1302 let mut txn = txns.begin();
1303 txn.write(&d0, "foo".into(), (), 1).await;
1304 let commit_apply = txn.commit_at(&mut txns, 2).await.unwrap();
1305
1306 txns.register(3, [writer(&client, d0).await]).await.unwrap();
1307
1308 let actual = txns.txns_cache.expect_snapshot(&client, d0, 1).await;
1311 assert_eq!(actual, Vec::<String>::new());
1312
1313 commit_apply.apply(&mut txns).await;
1314 let actual = txns.txns_cache.expect_snapshot(&client, d0, 2).await;
1315 assert_eq!(actual, vec!["foo".to_owned()]);
1316 }
1317
1318 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
1320 #[cfg_attr(miri, ignore)] async fn apply_many_ts() {
1322 let client = PersistClient::new_for_tests().await;
1323 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1324 let log = txns.new_log();
1325 let d0 = txns.expect_register(1).await;
1326
1327 for ts in 2..10 {
1328 let mut txn = txns.begin();
1329 txn.write(&d0, ts.to_string(), (), 1).await;
1330 let _apply = txn.commit_at(&mut txns, ts).await.unwrap();
1331 log.record((d0, ts.to_string(), ts, 1));
1332 }
1333 txns.expect_commit_at(10, d0, &[], &log).await;
1336
1337 log.assert_snapshot(d0, 10).await;
1338 }
1339
1340 struct StressWorker {
1341 idx: usize,
1342 data_ids: Vec<ShardId>,
1343 txns: TxnsHandle<String, (), u64, i64>,
1344 log: CommitLog,
1345 tidy: Tidy,
1346 ts: u64,
1347 step: usize,
1348 rng: SmallRng,
1349 reads: Vec<(
1350 oneshot::Sender<u64>,
1351 ShardId,
1352 u64,
1353 mz_ore::task::JoinHandle<Vec<(String, u64, i64)>>,
1354 )>,
1355 }
1356
1357 impl StressWorker {
1358 pub async fn step(&mut self) {
1359 debug!(
1360 "stress {} step {} START ts={}",
1361 self.idx, self.step, self.ts
1362 );
1363 let data_id =
1364 self.data_ids[usize::cast_from(self.rng.next_u64()) % self.data_ids.len()];
1365 match self.rng.next_u64() % 6 {
1366 0 => self.write(data_id).await,
1367 1 => self.register(data_id).await,
1370 2 => self.forget(data_id).await,
1371 3 => {
1372 debug!("stress update {:.9} to {}", data_id.to_string(), self.ts);
1373 let _ = self.txns.txns_cache.update_ge(&self.ts).await;
1374 }
1375 4 => self.start_read(data_id),
1376 5 => self.start_read(data_id),
1377 _ => unreachable!(""),
1378 }
1379 debug!("stress {} step {} DONE ts={}", self.idx, self.step, self.ts);
1380 self.step += 1;
1381 }
1382
1383 fn key(&self) -> String {
1384 format!("w{}s{}", self.idx, self.step)
1385 }
1386
1387 async fn registered_at_progress_ts(&mut self, data_id: ShardId) -> bool {
1388 self.ts = *self.txns.txns_cache.update_ge(&self.ts).await;
1389 self.txns
1390 .txns_cache
1391 .registered_at_progress(&data_id, &self.ts)
1392 }
1393
1394 async fn write(&mut self, data_id: ShardId) {
1397 self.retry_ts_err(&mut |w: &mut StressWorker| {
1401 Box::pin(async move {
1402 if w.registered_at_progress_ts(data_id).await {
1403 w.write_via_txns(data_id).await
1404 } else {
1405 w.write_direct(data_id).await
1406 }
1407 })
1408 })
1409 .await
1410 }
1411
1412 async fn write_via_txns(&mut self, data_id: ShardId) -> Result<(), u64> {
1413 debug!(
1414 "stress write_via_txns {:.9} at {}",
1415 data_id.to_string(),
1416 self.ts
1417 );
1418 if !self.txns.datas.data_write_for_commit.contains_key(&data_id) {
1424 let x = writer(&self.txns.datas.client, data_id).await;
1425 self.txns
1426 .datas
1427 .data_write_for_commit
1428 .insert(data_id, DataWriteCommit(x));
1429 }
1430 let mut txn = self.txns.begin_test();
1431 txn.tidy(std::mem::take(&mut self.tidy));
1432 txn.write(&data_id, self.key(), (), 1).await;
1433 let apply = txn.commit_at(&mut self.txns, self.ts).await?;
1434 debug!(
1435 "log {:.9} {} at {}",
1436 data_id.to_string(),
1437 self.key(),
1438 self.ts
1439 );
1440 self.log.record_txn(self.ts, &txn);
1441 if self.rng.next_u64() % 3 == 0 {
1442 self.tidy.merge(apply.apply(&mut self.txns).await);
1443 }
1444 Ok(())
1445 }
1446
1447 async fn write_direct(&mut self, data_id: ShardId) -> Result<(), u64> {
1448 debug!(
1449 "stress write_direct {:.9} at {}",
1450 data_id.to_string(),
1451 self.ts
1452 );
1453 self.txns.begin().commit_at(&mut self.txns, self.ts).await?;
1456
1457 let mut write = writer(&self.txns.datas.client, data_id).await;
1458 let mut current = write.shared_upper().into_option().unwrap();
1459 loop {
1460 if !(current <= self.ts) {
1461 return Err(current);
1462 }
1463 let key = self.key();
1464 let updates = [((&key, &()), &self.ts, 1)];
1465 let res = crate::small_caa(
1466 || format!("data {:.9} direct", data_id.to_string()),
1467 &mut write,
1468 &updates,
1469 current,
1470 self.ts + 1,
1471 )
1472 .await;
1473 match res {
1474 Ok(()) => {
1475 debug!("log {:.9} {} at {}", data_id.to_string(), key, self.ts);
1476 self.log.record((data_id, key, self.ts, 1));
1477 return Ok(());
1478 }
1479 Err(new_current) => current = new_current,
1480 }
1481 }
1482 }
1483
1484 async fn register(&mut self, data_id: ShardId) {
1485 self.retry_ts_err(&mut |w: &mut StressWorker| {
1486 debug!("stress register {:.9} at {}", data_id.to_string(), w.ts);
1487 Box::pin(async move {
1488 let data_write = writer(&w.txns.datas.client, data_id).await;
1489 let _ = w.txns.register(w.ts, [data_write]).await?;
1490 Ok(())
1491 })
1492 })
1493 .await
1494 }
1495
1496 async fn forget(&mut self, data_id: ShardId) {
1497 self.retry_ts_err(&mut |w: &mut StressWorker| {
1498 debug!("stress forget {:.9} at {}", data_id.to_string(), w.ts);
1499 Box::pin(async move { w.txns.forget(w.ts, [data_id]).await.map(|_| ()) })
1500 })
1501 .await
1502 }
1503
1504 fn start_read(&mut self, data_id: ShardId) {
1505 debug!(
1506 "stress start_read {:.9} at {}",
1507 data_id.to_string(),
1508 self.ts
1509 );
1510 let client = (*self.txns.datas.client).clone();
1511 let txns_id = self.txns.txns_id();
1512 let as_of = self.ts;
1513 debug!("start_read {:.9} as_of {}", data_id.to_string(), as_of);
1514 let (tx, mut rx) = oneshot::channel();
1515 let subscribe = mz_ore::task::spawn_blocking(
1516 || format!("{:.9}-{}", data_id.to_string(), as_of),
1517 move || {
1518 let mut subscribe = DataSubscribe::new(
1519 "test",
1520 client,
1521 txns_id,
1522 data_id,
1523 as_of,
1524 Antichain::new(),
1525 );
1526 let data_id = format!("{:.9}", data_id.to_string());
1527 let _guard = info_span!("read_worker", %data_id, as_of).entered();
1528 loop {
1529 subscribe.worker.step_or_park(None);
1530 subscribe.capture_output();
1531 let until = match rx.try_recv() {
1532 Ok(ts) => ts,
1533 Err(oneshot::error::TryRecvError::Empty) => {
1534 continue;
1535 }
1536 Err(oneshot::error::TryRecvError::Closed) => 0,
1537 };
1538 while subscribe.progress() < until {
1539 subscribe.worker.step_or_park(None);
1540 subscribe.capture_output();
1541 }
1542 return subscribe.output().clone();
1543 }
1544 },
1545 );
1546 self.reads.push((tx, data_id, as_of, subscribe));
1547 }
1548
1549 async fn retry_ts_err<W>(&mut self, work_fn: &mut W)
1550 where
1551 W: for<'b> FnMut(&'b mut Self) -> BoxFuture<'b, Result<(), u64>>,
1552 {
1553 loop {
1554 match work_fn(self).await {
1555 Ok(ret) => return ret,
1556 Err(new_ts) => self.ts = new_ts,
1557 }
1558 }
1559 }
1560 }
1561
1562 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
1563 #[cfg_attr(miri, ignore)] async fn stress_correctness() {
1565 const NUM_DATA_SHARDS: usize = 2;
1566 const NUM_WORKERS: usize = 2;
1567 const NUM_STEPS_PER_WORKER: usize = 100;
1568 let seed = UNIX_EPOCH.elapsed().unwrap().hashed();
1569 eprintln!("using seed {}", seed);
1570
1571 let mut clients = PersistClientCache::new_no_metrics();
1572 clients
1575 .cfg()
1576 .set_next_listen_batch_retryer(RetryParameters {
1577 fixed_sleep: Duration::ZERO,
1578 initial_backoff: Duration::from_millis(1),
1579 multiplier: 1,
1580 clamp: Duration::from_millis(1),
1581 });
1582 let client = clients.open(PersistLocation::new_in_mem()).await.unwrap();
1583 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1584 let log = txns.new_log();
1585 let data_ids = (0..NUM_DATA_SHARDS)
1586 .map(|_| ShardId::new())
1587 .collect::<Vec<_>>();
1588 let data_writes = data_ids
1589 .iter()
1590 .map(|data_id| writer(&client, *data_id))
1591 .collect::<FuturesUnordered<_>>()
1592 .collect::<Vec<_>>()
1593 .await;
1594 let _data_sinces = data_ids
1595 .iter()
1596 .map(|data_id| reader(&client, *data_id))
1597 .collect::<FuturesUnordered<_>>()
1598 .collect::<Vec<_>>()
1599 .await;
1600 let register_ts = 1;
1601 txns.register(register_ts, data_writes).await.unwrap();
1602
1603 let mut workers = Vec::new();
1604 for idx in 0..NUM_WORKERS {
1605 clients.clear_state_cache();
1608 let client = clients.open(PersistLocation::new_in_mem()).await.unwrap();
1609 let mut worker = StressWorker {
1610 idx,
1611 log: log.clone(),
1612 txns: TxnsHandle::expect_open_id(client.clone(), txns.txns_id()).await,
1613 data_ids: data_ids.clone(),
1614 tidy: Tidy::default(),
1615 ts: register_ts,
1616 step: 0,
1617 rng: SmallRng::seed_from_u64(seed.wrapping_add(u64::cast_from(idx))),
1618 reads: Vec::new(),
1619 };
1620 let worker = async move {
1621 while worker.step < NUM_STEPS_PER_WORKER {
1622 worker.step().await;
1623 }
1624 (worker.ts, worker.reads)
1625 }
1626 .instrument(info_span!("stress_worker", idx));
1627 workers.push(mz_ore::task::spawn(|| format!("worker-{}", idx), worker));
1628 }
1629
1630 let mut max_ts = 0;
1631 let mut reads = Vec::new();
1632 for worker in workers {
1633 let (t, mut r) = worker.await;
1634 max_ts = std::cmp::max(max_ts, t);
1635 reads.append(&mut r);
1636 }
1637
1638 tokio::time::timeout(Duration::from_secs(30), async {
1640 info!("finished with max_ts of {}", max_ts);
1641 txns.apply_le(&max_ts).await;
1642 for data_id in data_ids {
1643 info!("reading data shard {}", data_id);
1644 log.assert_snapshot(data_id, max_ts)
1645 .instrument(info_span!("read_data", data_id = format!("{:.9}", data_id)))
1646 .await;
1647 }
1648 info!("now waiting for reads {}", max_ts);
1649 for (tx, data_id, as_of, subscribe) in reads {
1650 let _ = tx.send(max_ts + 1);
1651 let output = subscribe.await;
1652 log.assert_eq(data_id, as_of, max_ts + 1, output);
1653 }
1654 })
1655 .await
1656 .unwrap();
1657 }
1658
1659 #[mz_ore::test(tokio::test)]
1660 #[cfg_attr(miri, ignore)] async fn advance_physical_uppers_past() {
1662 let client = PersistClient::new_for_tests().await;
1663 let mut txns = TxnsHandle::expect_open(client.clone()).await;
1664 let log = txns.new_log();
1665 let d0 = txns.expect_register(1).await;
1666 let mut d0_write = writer(&client, d0).await;
1667 let d1 = txns.expect_register(2).await;
1668 let mut d1_write = writer(&client, d1).await;
1669
1670 assert_eq!(d0_write.fetch_recent_upper().await.elements(), &[2]);
1671 assert_eq!(d1_write.fetch_recent_upper().await.elements(), &[3]);
1672
1673 txns.expect_commit_at(3, d0, &["0-2"], &log).await;
1677 assert_eq!(d0_write.fetch_recent_upper().await.elements(), &[4]);
1678 assert_eq!(d1_write.fetch_recent_upper().await.elements(), &[3]);
1679
1680 txns.expect_commit_at(4, d1, &["1-3"], &log).await;
1682 assert_eq!(d0_write.fetch_recent_upper().await.elements(), &[4]);
1683 assert_eq!(d1_write.fetch_recent_upper().await.elements(), &[5]);
1684
1685 log.assert_snapshot(d0, 4).await;
1686 log.assert_snapshot(d1, 4).await;
1687 }
1688
1689 #[mz_ore::test(tokio::test)]
1690 #[cfg_attr(miri, ignore)]
1691 #[allow(clippy::unnecessary_get_then_check)] async fn schemas() {
1693 let client = PersistClient::new_for_tests().await;
1694 let mut txns0 = TxnsHandle::expect_open(client.clone()).await;
1695 let mut txns1 = TxnsHandle::expect_open_id(client.clone(), txns0.txns_id()).await;
1696 let log = txns0.new_log();
1697 let d0 = txns0.expect_register(1).await;
1698
1699 assert!(txns0.datas.data_write_for_commit.get(&d0).is_some());
1702 let mut txn = txns0.begin_test();
1703 txn.write(&d0, "foo".into(), (), 1).await;
1704 let apply = txn.commit_at(&mut txns0, 2).await.unwrap();
1705 log.record_txn(2, &txn);
1706
1707 assert!(txns1.datas.data_write_for_commit.get(&d0).is_none());
1709 let _tidy = apply.apply(&mut txns1).await;
1710
1711 assert!(txns1.datas.data_write_for_commit.get(&d0).is_none());
1713 let res = mz_ore::task::spawn(|| "test", async move {
1714 let mut txn = txns1.begin();
1715 txn.write(&d0, "bar".into(), (), 1).await;
1716 let _ = txn.commit_at(&mut txns1, 3).await;
1718 })
1719 .into_tokio_handle();
1720 assert!(res.await.is_err());
1721
1722 assert!(txns0.datas.data_write_for_commit.get(&d0).is_some());
1725 txns0.forget(3, [d0]).await.unwrap();
1726 assert_none!(txns0.datas.data_write_for_commit.get(&d0));
1727
1728 assert_none!(txns0.datas.data_write_for_commit.get(&d0));
1730 txns0.forget(4, [d0]).await.unwrap();
1731 assert_none!(txns0.datas.data_write_for_commit.get(&d0));
1732
1733 assert_none!(txns0.datas.data_write_for_commit.get(&d0));
1735 txns0
1736 .register(5, [writer(&client, d0).await])
1737 .await
1738 .unwrap();
1739 assert!(txns0.datas.data_write_for_commit.get(&d0).is_some());
1740 txns0.expect_commit_at(6, d0, &["baz"], &log).await;
1741
1742 log.assert_snapshot(d0, 6).await;
1743 }
1744}