1#[cfg(not(feature = "iouring-network"))]
2use crate::network::tokio::{Config as TokioNetworkConfig, Network as TokioNetwork};
3#[cfg(feature = "iouring-storage")]
4use crate::storage::iouring::{Config as IoUringConfig, Storage as IoUringStorage};
5#[cfg(not(feature = "iouring-storage"))]
6use crate::storage::tokio::{Config as TokioStorageConfig, Storage as TokioStorage};
7#[cfg(feature = "external")]
8use crate::Pacer;
9use crate::{
10 child_label,
11 network::metered::Network as MeteredNetwork,
12 prefixed_name,
13 process::metered::Metrics as MeteredProcess,
14 signal::Signal,
15 storage::metered::Storage as MeteredStorage,
16 telemetry::metrics::{
17 add_attribute, raw, task::Label, validate_label, CounterFamily, GaugeFamily, Metric,
18 Register, Registered, Registry,
19 },
20 utils::{self, signal::Stopper, supervision::Tree, Panicker},
21 BufferPool, BufferPoolConfig, Clock, Error, Execution, Handle, Name, SinkOf, Spawner as _,
22 StreamOf, Supervisor as _, METRICS_PREFIX,
23};
24#[cfg(feature = "iouring-network")]
25use crate::{
26 iouring,
27 network::iouring::{Config as IoUringNetworkConfig, Network as IoUringNetwork},
28};
29use commonware_macros::{select, stability};
30#[stability(BETA)]
31use commonware_parallel::Rayon;
32use commonware_utils::{sync::Mutex, sys_rng, NZUsize};
33use governor::clock::{Clock as GClock, ReasonablyRealtime};
34use rand_core::{Rng, TryCryptoRng, TryRng};
35#[stability(BETA)]
36use rayon::ThreadPoolBuilder;
37use std::{
38 convert::Infallible,
39 env,
40 future::Future,
41 net::{IpAddr, SocketAddr},
42 num::NonZeroUsize,
43 path::PathBuf,
44 sync::Arc,
45 time::{Duration, SystemTime},
46};
47use tokio::runtime::{Builder, Runtime};
48
49#[cfg(feature = "iouring-network")]
50cfg_if::cfg_if! {
51 if #[cfg(test)] {
52 const IOURING_NETWORK_SIZE: u32 = 128;
55 } else {
56 const IOURING_NETWORK_SIZE: u32 = 1024;
57 }
58}
59
60#[derive(Debug)]
61struct Metrics {
62 tasks_spawned: CounterFamily<Label>,
63 tasks_running: GaugeFamily<Label>,
64}
65
66impl Metrics {
67 pub fn init(registry: &mut impl Register) -> Self {
68 Self {
69 tasks_spawned: registry.register(
70 "tasks_spawned",
71 "Total number of tasks spawned",
72 raw::Family::default(),
73 ),
74 tasks_running: registry.register(
75 "tasks_running",
76 "Number of tasks currently running",
77 raw::Family::default(),
78 ),
79 }
80 }
81}
82
83#[derive(Clone, Debug)]
84pub struct NetworkConfig {
85 tcp_nodelay: Option<bool>,
90
91 zero_linger: bool,
100
101 read_write_timeout: Duration,
107}
108
109impl Default for NetworkConfig {
110 fn default() -> Self {
111 Self {
112 tcp_nodelay: Some(true),
113 zero_linger: true,
114 read_write_timeout: Duration::from_secs(60),
115 }
116 }
117}
118
119#[derive(Clone)]
121pub struct Config {
122 worker_threads: usize,
128
129 global_queue_interval: Option<u32>,
136
137 max_blocking_threads: usize,
145
146 thread_stack_size: usize,
153
154 catch_panics: bool,
156
157 storage_directory: PathBuf,
159
160 maximum_buffer_size: usize,
164
165 network_cfg: NetworkConfig,
167
168 network_buffer_pool_cfg: Option<BufferPoolConfig>,
170
171 storage_buffer_pool_cfg: Option<BufferPoolConfig>,
173}
174
175impl Config {
176 pub fn new() -> Self {
178 let rng = sys_rng().next_u64();
179 let storage_directory = env::temp_dir().join(format!("commonware_tokio_runtime_{rng}"));
180 Self {
181 worker_threads: 2,
182 global_queue_interval: None,
183 max_blocking_threads: 512,
184 thread_stack_size: utils::thread::system_thread_stack_size(),
185 catch_panics: false,
186 storage_directory,
187 maximum_buffer_size: 2 * 1024 * 1024, network_cfg: NetworkConfig::default(),
189 network_buffer_pool_cfg: None,
190 storage_buffer_pool_cfg: None,
191 }
192 }
193
194 pub const fn with_worker_threads(mut self, n: usize) -> Self {
197 self.worker_threads = n;
198 self
199 }
200 pub const fn with_global_queue_interval(mut self, n: u32) -> Self {
202 self.global_queue_interval = Some(n);
203 self
204 }
205 pub const fn with_max_blocking_threads(mut self, n: usize) -> Self {
207 self.max_blocking_threads = n;
208 self
209 }
210 pub const fn with_thread_stack_size(mut self, n: usize) -> Self {
212 self.thread_stack_size = n;
213 self
214 }
215 pub const fn with_catch_panics(mut self, b: bool) -> Self {
217 self.catch_panics = b;
218 self
219 }
220 pub const fn with_read_write_timeout(mut self, d: Duration) -> Self {
222 self.network_cfg.read_write_timeout = d;
223 self
224 }
225 pub const fn with_tcp_nodelay(mut self, n: Option<bool>) -> Self {
227 self.network_cfg.tcp_nodelay = n;
228 self
229 }
230 pub const fn with_zero_linger(mut self, l: bool) -> Self {
232 self.network_cfg.zero_linger = l;
233 self
234 }
235 pub fn with_storage_directory(mut self, p: impl Into<PathBuf>) -> Self {
237 self.storage_directory = p.into();
238 self
239 }
240 pub const fn with_maximum_buffer_size(mut self, n: usize) -> Self {
242 self.maximum_buffer_size = n;
243 self
244 }
245 pub const fn with_network_buffer_pool_config(mut self, cfg: BufferPoolConfig) -> Self {
247 self.network_buffer_pool_cfg = Some(cfg);
248 self
249 }
250 pub const fn with_storage_buffer_pool_config(mut self, cfg: BufferPoolConfig) -> Self {
252 self.storage_buffer_pool_cfg = Some(cfg);
253 self
254 }
255
256 pub const fn worker_threads(&self) -> usize {
259 self.worker_threads
260 }
261 pub const fn global_queue_interval(&self) -> Option<u32> {
263 self.global_queue_interval
264 }
265 pub const fn max_blocking_threads(&self) -> usize {
267 self.max_blocking_threads
268 }
269 pub const fn thread_stack_size(&self) -> usize {
271 self.thread_stack_size
272 }
273 pub const fn catch_panics(&self) -> bool {
275 self.catch_panics
276 }
277 pub const fn read_write_timeout(&self) -> Duration {
279 self.network_cfg.read_write_timeout
280 }
281 pub const fn tcp_nodelay(&self) -> Option<bool> {
283 self.network_cfg.tcp_nodelay
284 }
285 pub const fn zero_linger(&self) -> bool {
287 self.network_cfg.zero_linger
288 }
289 pub const fn storage_directory(&self) -> &PathBuf {
291 &self.storage_directory
292 }
293 pub const fn maximum_buffer_size(&self) -> usize {
295 self.maximum_buffer_size
296 }
297
298 fn resolved_network_buffer_pool_config(&self) -> BufferPoolConfig {
301 self.network_buffer_pool_cfg.clone().unwrap_or_else(|| {
302 BufferPoolConfig::for_network().with_parallelism(NZUsize!(self.worker_threads))
303 })
304 }
305
306 fn resolved_storage_buffer_pool_config(&self) -> BufferPoolConfig {
309 self.storage_buffer_pool_cfg.clone().unwrap_or_else(|| {
310 BufferPoolConfig::for_storage().with_parallelism(NZUsize!(self.worker_threads))
311 })
312 }
313}
314
315impl Default for Config {
316 fn default() -> Self {
317 Self::new()
318 }
319}
320
321pub struct Executor {
323 registry: Registry,
324 metrics: Arc<Metrics>,
325 runtime: Runtime,
326 shutdown: Mutex<Stopper>,
327 panicker: Panicker,
328 thread_stack_size: usize,
329}
330
331pub struct Runner {
333 cfg: Config,
334}
335
336impl Default for Runner {
337 fn default() -> Self {
338 Self::new(Config::default())
339 }
340}
341
342impl Runner {
343 pub const fn new(cfg: Config) -> Self {
345 Self { cfg }
346 }
347}
348
349impl crate::Runner for Runner {
350 type Context = Context;
351
352 fn start<F, Fut>(self, f: F) -> Fut::Output
353 where
354 F: FnOnce(Self::Context) -> Fut,
355 Fut: Future,
356 {
357 let mut registry = Registry::new();
359 let mut runtime_registry = registry.sub_registry(METRICS_PREFIX);
360
361 let metrics = Arc::new(Metrics::init(&mut runtime_registry));
363 let mut builder = Builder::new_multi_thread();
364 builder
365 .worker_threads(self.cfg.worker_threads)
366 .max_blocking_threads(self.cfg.max_blocking_threads)
367 .thread_stack_size(self.cfg.thread_stack_size)
368 .enable_all();
369 if let Some(global_queue_interval) = self.cfg.global_queue_interval {
370 builder.global_queue_interval(global_queue_interval);
371 }
372 let runtime = builder.build().expect("failed to create Tokio runtime");
373
374 let (panicker, panicked) = Panicker::new(self.cfg.catch_panics);
376
377 let process = MeteredProcess::init(&mut runtime_registry);
382 runtime.spawn(process.collect(tokio::time::sleep));
383
384 let network_buffer_pool = BufferPool::new(
386 self.cfg.resolved_network_buffer_pool_config(),
387 &mut runtime_registry.sub_registry("network_buffer_pool"),
388 );
389 let storage_buffer_pool = BufferPool::new(
390 self.cfg.resolved_storage_buffer_pool_config(),
391 &mut runtime_registry.sub_registry("storage_buffer_pool"),
392 );
393
394 if let Err(e) = crate::storage::sync(&self.cfg.storage_directory) {
397 panic!(
398 "failed to sync storage filesystem at startup ({}): {e}",
399 self.cfg.storage_directory.display()
400 );
401 }
402
403 cfg_if::cfg_if! {
405 if #[cfg(feature = "iouring-storage")] {
406 let mut iouring_registry = runtime_registry.sub_registry("iouring_storage");
407 let storage = MeteredStorage::new(
408 IoUringStorage::start(
409 IoUringConfig {
410 storage_directory: self.cfg.storage_directory.clone(),
411 iouring_config: Default::default(),
412 thread_stack_size: self.cfg.thread_stack_size,
413 },
414 &mut iouring_registry,
415 storage_buffer_pool.clone(),
416 ),
417 &mut runtime_registry,
418 );
419 } else {
420 let storage = MeteredStorage::new(
421 TokioStorage::new(
422 TokioStorageConfig::new(
423 self.cfg.storage_directory.clone(),
424 self.cfg.maximum_buffer_size,
425 ),
426 storage_buffer_pool.clone(),
427 ),
428 &mut runtime_registry,
429 );
430 }
431 }
432
433 cfg_if::cfg_if! {
435 if #[cfg(feature = "iouring-network")] {
436 let mut iouring_registry = runtime_registry.sub_registry("iouring_network");
437 let config = IoUringNetworkConfig {
438 tcp_nodelay: self.cfg.network_cfg.tcp_nodelay,
439 zero_linger: self.cfg.network_cfg.zero_linger,
440 read_write_timeout: self.cfg.network_cfg.read_write_timeout,
441 iouring_config: iouring::Config {
442 size: IOURING_NETWORK_SIZE,
444 max_request_timeout: self.cfg.network_cfg.read_write_timeout,
445 shutdown_timeout: Some(self.cfg.network_cfg.read_write_timeout),
446 ..Default::default()
447 },
448 thread_stack_size: self.cfg.thread_stack_size,
449 ..Default::default()
450 };
451 let network = MeteredNetwork::new(
452 IoUringNetwork::start(
453 config,
454 &mut iouring_registry,
455 network_buffer_pool.clone(),
456 )
457 .unwrap(),
458 &mut runtime_registry,
459 );
460 } else {
461 let config = TokioNetworkConfig::default()
462 .with_read_timeout(self.cfg.network_cfg.read_write_timeout)
463 .with_write_timeout(self.cfg.network_cfg.read_write_timeout)
464 .with_tcp_nodelay(self.cfg.network_cfg.tcp_nodelay)
465 .with_zero_linger(self.cfg.network_cfg.zero_linger);
466 let network = MeteredNetwork::new(
467 TokioNetwork::new(config, network_buffer_pool.clone()),
468 &mut runtime_registry,
469 );
470 }
471 }
472
473 let executor = Arc::new(Executor {
475 registry,
476 metrics,
477 runtime,
478 shutdown: Mutex::new(Stopper::default()),
479 panicker,
480 thread_stack_size: self.cfg.thread_stack_size,
481 });
482
483 let label = Label::root();
485 executor.metrics.tasks_spawned.get_or_create(&label).inc();
486 let gauge = executor.metrics.tasks_running.get_or_create(&label).clone();
487
488 let context = Context {
490 storage,
491 name: label.name(),
492 attributes: Vec::new(),
493 executor: executor.clone(),
494 network,
495 network_buffer_pool,
496 storage_buffer_pool,
497 tree: Tree::root(),
498 execution: Execution::default(),
499 };
500 let output = executor.runtime.block_on(panicked.interrupt(f(context)));
501 gauge.dec();
502
503 output
504 }
505}
506
507cfg_if::cfg_if! {
508 if #[cfg(feature = "iouring-storage")] {
509 type Storage = MeteredStorage<IoUringStorage>;
510 } else {
511 type Storage = MeteredStorage<TokioStorage>;
512 }
513}
514
515cfg_if::cfg_if! {
516 if #[cfg(feature = "iouring-network")] {
517 type Network = MeteredNetwork<IoUringNetwork>;
518 } else {
519 type Network = MeteredNetwork<TokioNetwork>;
520 }
521}
522
523pub struct Context {
527 name: String,
528 attributes: Vec<(String, String)>,
529 executor: Arc<Executor>,
530 storage: Storage,
531 network: Network,
532 network_buffer_pool: BufferPool,
533 storage_buffer_pool: BufferPool,
534 tree: Arc<Tree>,
535 execution: Execution,
536}
537
538impl Context {
539 fn metrics(&self) -> &Metrics {
541 &self.executor.metrics
542 }
543}
544
545impl crate::Spawner for Context {
546 fn dedicated(mut self) -> Self {
547 self.execution = Execution::Dedicated;
548 self
549 }
550
551 fn shared(mut self, blocking: bool) -> Self {
552 self.execution = Execution::Shared(blocking);
553 self
554 }
555
556 fn spawn<F, Fut, T>(mut self, f: F) -> Handle<T>
557 where
558 F: FnOnce(Self) -> Fut + Send + 'static,
559 Fut: Future<Output = T> + Send + 'static,
560 T: Send + 'static,
561 {
562 let (_, metric) = spawn_metrics!(self);
564
565 let parent = Arc::clone(&self.tree);
567 let past = self.execution;
568 self.execution = Execution::default();
569 let (child, aborted) = Tree::child(&parent);
570 if aborted {
571 return Handle::closed(metric);
572 }
573 self.tree = child;
574
575 let executor = self.executor.clone();
577 let future = f(self);
578 let (f, handle) = Handle::init(
579 future,
580 metric,
581 executor.panicker.clone(),
582 Arc::clone(&parent),
583 );
584
585 if matches!(past, Execution::Dedicated) {
586 utils::thread::spawn(executor.thread_stack_size, {
587 let handle = executor.runtime.handle().clone();
589 move || {
590 handle.block_on(f);
591 }
592 });
593 } else if matches!(past, Execution::Shared(true)) {
594 executor.runtime.spawn_blocking({
595 let handle = executor.runtime.handle().clone();
597 move || {
598 handle.block_on(f);
599 }
600 });
601 } else {
602 executor.runtime.spawn(f);
603 }
604
605 if let Some(aborter) = handle.aborter() {
607 parent.register(aborter);
608 }
609
610 handle
611 }
612
613 async fn stop(self, value: i32, timeout: Option<Duration>) -> Result<(), Error> {
614 let stop_resolved = {
615 let mut shutdown = self.executor.shutdown.lock();
616 shutdown.stop(value)
617 };
618
619 let timeout_future = timeout.map_or_else(
621 || futures::future::Either::Right(futures::future::pending()),
622 |duration| futures::future::Either::Left(self.sleep(duration)),
623 );
624 select! {
625 result = stop_resolved => {
626 result.map_err(|_| Error::Closed)?;
627 Ok(())
628 },
629 _ = timeout_future => Err(Error::Timeout),
630 }
631 }
632
633 fn stopped(&self) -> Signal {
634 self.executor.shutdown.lock().stopped()
635 }
636}
637
638#[stability(BETA)]
639impl crate::Strategizer for Context {
640 fn strategy(&self, parallelism: NonZeroUsize) -> Rayon {
641 let pool = ThreadPoolBuilder::new()
642 .num_threads(parallelism.get())
643 .spawn_handler(move |thread| {
644 self.child("rayon_thread")
647 .dedicated()
648 .spawn(move |_| async move { thread.run() });
649 Ok(())
650 })
651 .build()
652 .expect("failed to create Tokio Rayon thread pool");
653 Rayon::with_pool(Arc::new(pool))
654 }
655}
656
657impl crate::Supervisor for Context {
658 fn child(&self, label: &'static str) -> Self {
659 let (tree, _) = Tree::child(&self.tree);
660 Self {
661 name: child_label(&self.name, label),
662 attributes: self.attributes.clone(),
663 executor: self.executor.clone(),
664 storage: self.storage.clone(),
665 network: self.network.clone(),
666 network_buffer_pool: self.network_buffer_pool.clone(),
667 storage_buffer_pool: self.storage_buffer_pool.clone(),
668 tree,
669 execution: Execution::default(),
670 }
671 }
672
673 fn with_attribute(mut self, key: &'static str, value: impl std::fmt::Display) -> Self {
674 validate_label(key);
676
677 add_attribute(&mut self.attributes, key, value);
679 self
680 }
681
682 fn name(&self) -> Name {
683 Name {
684 label: self.name.clone(),
685 attributes: self.attributes.clone(),
686 }
687 }
688}
689
690impl crate::Metrics for Context {
691 fn register<N: Into<String>, H: Into<String>, M: Metric>(
692 &self,
693 name: N,
694 help: H,
695 metric: M,
696 ) -> Registered<M> {
697 let name = name.into();
698 let help = help.into();
699 let metric = Arc::new(metric);
700 self.executor.registry.register(
701 prefixed_name(&self.name, &name),
702 help,
703 self.attributes.clone(),
704 metric,
705 )
706 }
707
708 fn encode(&self) -> String {
709 self.executor.registry.encode()
710 }
711}
712
713impl Clock for Context {
714 fn current(&self) -> SystemTime {
715 SystemTime::now()
716 }
717
718 fn sleep(&self, duration: Duration) -> impl Future<Output = ()> + Send + 'static {
719 tokio::time::sleep(duration)
720 }
721
722 fn sleep_until(&self, deadline: SystemTime) -> impl Future<Output = ()> + Send + 'static {
723 let duration_until_deadline = deadline.duration_since(self.current()).unwrap_or_default();
724 tokio::time::sleep(duration_until_deadline)
725 }
726}
727
728#[cfg(feature = "external")]
729impl Pacer for Context {
730 fn pace<'a, F, T>(
731 &'a self,
732 _latency: Duration,
733 future: F,
734 ) -> impl Future<Output = T> + Send + 'a
735 where
736 F: Future<Output = T> + Send + 'a,
737 T: Send + 'a,
738 {
739 future
741 }
742}
743
744impl GClock for Context {
745 type Instant = SystemTime;
746
747 fn now(&self) -> Self::Instant {
748 self.current()
749 }
750}
751
752impl ReasonablyRealtime for Context {}
753
754impl crate::Network for Context {
755 type Listener = <Network as crate::Network>::Listener;
756
757 async fn bind(&self, socket: SocketAddr) -> Result<Self::Listener, Error> {
758 self.network.bind(socket).await
759 }
760
761 async fn dial(&self, socket: SocketAddr) -> Result<(SinkOf<Self>, StreamOf<Self>), Error> {
762 self.network.dial(socket).await
763 }
764}
765
766impl crate::Resolver for Context {
767 async fn resolve(&self, host: &str) -> Result<Vec<IpAddr>, Error> {
768 let addrs = tokio::net::lookup_host(format!("{host}:0"))
774 .await
775 .map_err(|e| Error::ResolveFailed(e.to_string()))?;
776 Ok(addrs.map(|addr| addr.ip()).collect())
777 }
778}
779
780impl TryRng for Context {
781 type Error = Infallible;
782
783 fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
784 Ok(sys_rng().next_u32())
785 }
786
787 fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
788 Ok(sys_rng().next_u64())
789 }
790
791 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
792 sys_rng().fill_bytes(dest);
793 Ok(())
794 }
795}
796
797impl TryCryptoRng for Context {}
798
799impl crate::Storage for Context {
800 type Blob = <Storage as crate::Storage>::Blob;
801
802 async fn open_versioned(
803 &self,
804 partition: &str,
805 name: &[u8],
806 versions: std::ops::RangeInclusive<u16>,
807 ) -> Result<(Self::Blob, u64, u16), Error> {
808 self.storage.open_versioned(partition, name, versions).await
809 }
810
811 async fn remove(&self, partition: &str, name: Option<&[u8]>) -> Result<(), Error> {
812 self.storage.remove(partition, name).await
813 }
814
815 async fn scan(&self, partition: &str) -> Result<Vec<Vec<u8>>, Error> {
816 self.storage.scan(partition).await
817 }
818}
819
820impl crate::BufferPooler for Context {
821 fn network_buffer_pool(&self) -> &BufferPool {
822 &self.network_buffer_pool
823 }
824
825 fn storage_buffer_pool(&self) -> &BufferPool {
826 &self.storage_buffer_pool
827 }
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833
834 #[test]
835 fn test_worker_threads_updates_default_buffer_pool_parallelism() {
836 let cfg = Config::new().with_worker_threads(8);
837
838 assert_eq!(cfg.worker_threads, 8);
839 let network = cfg.resolved_network_buffer_pool_config();
840 assert_eq!(network.parallelism, NZUsize!(8));
841 assert_eq!(
842 network.thread_cache_config,
843 BufferPoolConfig::for_network().thread_cache_config
844 );
845
846 let storage = cfg.resolved_storage_buffer_pool_config();
847 assert_eq!(storage.parallelism, NZUsize!(8));
848 assert_eq!(
849 storage.thread_cache_config,
850 BufferPoolConfig::for_storage().thread_cache_config
851 );
852 }
853
854 #[test]
855 fn test_default_thread_stack_size_uses_system_default() {
856 let cfg = Config::new();
857 assert_eq!(
858 cfg.thread_stack_size(),
859 utils::thread::system_thread_stack_size()
860 );
861 }
862
863 #[test]
864 fn test_thread_stack_size_override() {
865 let cfg = Config::new().with_thread_stack_size(4 * 1024 * 1024);
866 assert_eq!(cfg.thread_stack_size(), 4 * 1024 * 1024);
867 }
868
869 #[test]
870 fn test_explicit_buffer_pool_configs_override_worker_threads() {
871 let cfg = Config::new()
873 .with_network_buffer_pool_config(
874 BufferPoolConfig::for_network().with_parallelism(NZUsize!(2)),
875 )
876 .with_worker_threads(8)
877 .with_storage_buffer_pool_config(
878 BufferPoolConfig::for_storage().with_thread_cache_disabled(),
879 );
880
881 let network = cfg.resolved_network_buffer_pool_config();
882 assert_eq!(network.parallelism, NZUsize!(2));
883 assert_eq!(
884 network.thread_cache_config,
885 BufferPoolConfig::for_network().thread_cache_config
886 );
887
888 let storage = cfg.resolved_storage_buffer_pool_config();
889 assert_eq!(storage.parallelism, NZUsize!(1));
890 assert_eq!(
891 storage.thread_cache_config,
892 BufferPoolConfig::for_storage()
893 .with_thread_cache_disabled()
894 .thread_cache_config
895 );
896 }
897
898 #[cfg(windows)]
899 #[test]
900 fn test_startup_flush_survives_restart() {
901 use crate::{Blob as _, Runner as _, Storage as _};
902
903 let cfg = Config::new();
907 let dir = cfg.storage_directory().clone();
908 Runner::new(cfg).start(|context| async move {
909 let (blob, _) = context.open("test", b"blob").await.unwrap();
910 blob.write_at(0, vec![1u8, 2, 3, 4]).await.unwrap();
911 blob.sync().await.unwrap();
912 });
913 let reopened_len = Runner::new(Config::new().with_storage_directory(dir.clone())).start(
914 |context| async move {
915 let (_, len) = context.open("test", b"blob").await.unwrap();
916 len
917 },
918 );
919 assert_eq!(reopened_len, 4);
920 let _ = std::fs::remove_dir_all(&dir);
921 }
922}