From 49ad6391bfb42100f3e580f78fcbc3a73354af7b Mon Sep 17 00:00:00 2001 From: Bruno Tavares Date: Wed, 26 Feb 2020 19:45:06 -0300 Subject: [PATCH] Implement async API for random-access-storage This commit moves the API from a sync call into async functions using async_trait. We would like to use async operations on hypercore, and it would be nice to have the underlying storage using async-io when possible. As discussed on https://github.com/datrs/hypercore/issues/97, we could change the API to use async functions on the 'ram' trait. Async Trait methods are still unstable, but possible using the 'async-trait' crate through a macro. This PR moves the API to use async-fns. Tests are passing. --- Cargo.toml | 5 +- README.md | 9 +- benches/sync.rs | 81 ++++++++++-------- src/lib.rs | 74 ++++++++++------ tests/model.rs | 54 ++++++------ tests/regression.rs | 20 +++-- tests/test.rs | 203 ++++++++++++++++++++++++-------------------- 7 files changed, 254 insertions(+), 192 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 37990e6..dd54106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,12 @@ edition = "2018" [dependencies] anyhow = "1.0.26" mkdirp = "1.0.0" -random-access-storage = "3.0.0" +random-access-storage = "4.0.0" +async-std = "1.5.0" +async-trait = "0.1.24" [dev-dependencies] quickcheck = "0.9.2" rand = "0.7.3" tempfile = "3.1.0" +async-std = { version = "1.5.0", features = ["attributes"] } diff --git a/README.md b/README.md index 424e5c8..ff05ce8 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,15 @@ Continuously read,write to disk, using random offsets and lengths. Adapted from ## Usage ```rust -extern crate tempdir; -extern crate random_access_disk; - use std::path::PathBuf; use tempdir::TempDir; let dir = TempDir::new("random-access-disk").unwrap(); let mut file = random_access_disk::RandomAccessDisk::new(dir.path().join("README.db")); -file.write(0, b"hello").unwrap(); -file.write(5, b" world").unwrap(); -let _text = file.read(0, 11).unwrap(); +file.write(0, b"hello").await.unwrap(); +file.write(5, b" world").await.unwrap(); +let _text = file.read(0, 11).await.unwrap(); ``` ## Installation diff --git a/benches/sync.rs b/benches/sync.rs index 800ac14..9a75b83 100644 --- a/benches/sync.rs +++ b/benches/sync.rs @@ -1,55 +1,68 @@ #![feature(test)] mod sync { - extern crate random_access_disk as rad; - extern crate random_access_storage; - extern crate tempfile; extern crate test; + use random_access_disk as rad; + use test::Bencher; - use self::random_access_storage::RandomAccess; - use self::test::Bencher; + use random_access_storage::RandomAccess; #[bench] fn write_hello_world(b: &mut Bencher) { - let dir = tempfile::Builder::new() - .prefix("random-access-disk") - .tempdir() - .unwrap(); - let mut file = - rad::RandomAccessDisk::open(dir.path().join("1.db")).unwrap(); - b.iter(|| { - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); + async_std::task::block_on(async { + let dir = tempfile::Builder::new() + .prefix("random-access-disk") + .tempdir() + .unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("1.db")) + .await + .unwrap(); + b.iter(|| { + async_std::task::block_on(async { + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + }) + }); }); } #[bench] fn read_hello_world(b: &mut Bencher) { - let dir = tempfile::Builder::new() - .prefix("random-access-disk") - .tempdir() - .unwrap(); - let mut file = - rad::RandomAccessDisk::open(dir.path().join("2.db")).unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - b.iter(|| { - let _text = file.read(0, 11).unwrap(); + async_std::task::block_on(async { + let dir = tempfile::Builder::new() + .prefix("random-access-disk") + .tempdir() + .unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("2.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + b.iter(|| { + async_std::task::block_on(async { + let _text = file.read(0, 11).await.unwrap(); + }) + }); }); } #[bench] fn read_write_hello_world(b: &mut Bencher) { - let dir = tempfile::Builder::new() - .prefix("random-access-disk") - .tempdir() - .unwrap(); - let mut file = - rad::RandomAccessDisk::open(dir.path().join("3.db")).unwrap(); - b.iter(|| { - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - let _text = file.read(0, 11).unwrap(); + async_std::task::block_on(async { + let dir = tempfile::Builder::new() + .prefix("random-access-disk") + .tempdir() + .unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("3.db")) + .await + .unwrap(); + b.iter(|| { + async_std::task::block_on(async { + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + let _text = file.read(0, 11).await.unwrap(); + }) + }); }); } } diff --git a/src/lib.rs b/src/lib.rs index 9561b24..b098c99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,10 @@ #![cfg_attr(test, deny(warnings))] use anyhow::{anyhow, Error}; +use async_std::fs::{self, OpenOptions}; +use async_std::io::prelude::{SeekExt, WriteExt}; +use async_std::io::{ReadExt, SeekFrom}; use random_access_storage::RandomAccess; -use std::fs::{self, OpenOptions}; -use std::io::{Read, Seek, SeekFrom, Write}; use std::ops::Drop; use std::path; @@ -22,23 +23,31 @@ pub struct RandomAccessDisk { impl RandomAccessDisk { /// Create a new instance. #[allow(clippy::new_ret_no_self)] - pub fn open(filename: path::PathBuf) -> Result { - Self::builder(filename).build() + pub async fn open( + filename: path::PathBuf, + ) -> Result { + Self::builder(filename).build().await } + pub fn builder(filename: path::PathBuf) -> Builder { Builder::new(filename) } } +#[async_trait::async_trait] impl RandomAccess for RandomAccessDisk { type Error = Box; - fn write(&mut self, offset: u64, data: &[u8]) -> Result<(), Self::Error> { + async fn write( + &mut self, + offset: u64, + data: &[u8], + ) -> Result<(), Self::Error> { let mut file = self.file.as_ref().expect("self.file was None."); - file.seek(SeekFrom::Start(offset))?; - file.write_all(&data)?; + file.seek(SeekFrom::Start(offset)).await?; + file.write_all(&data).await?; if self.auto_sync { - file.sync_all()?; + file.sync_all().await?; } // We've changed the length of our file. @@ -58,7 +67,11 @@ impl RandomAccess for RandomAccessDisk { // because we're replacing empty data with actual zeroes - which does not // reflect the state of the world. // #[cfg_attr(test, allow(unused_io_amount))] - fn read(&mut self, offset: u64, length: u64) -> Result, Self::Error> { + async fn read( + &mut self, + offset: u64, + length: u64, + ) -> Result, Self::Error> { if (offset + length) as u64 > self.length { return Err( anyhow!( @@ -73,46 +86,50 @@ impl RandomAccess for RandomAccessDisk { let mut file = self.file.as_ref().expect("self.file was None."); let mut buffer = vec![0; length as usize]; - file.seek(SeekFrom::Start(offset))?; - let _bytes_read = file.read(&mut buffer[..])?; + file.seek(SeekFrom::Start(offset)).await?; + let _bytes_read = file.read(&mut buffer[..]).await?; Ok(buffer) } - fn read_to_writer( + async fn read_to_writer( &mut self, _offset: u64, _length: u64, - _buf: &mut impl Write, + _buf: &mut (impl async_std::io::Write + Send), ) -> Result<(), Self::Error> { unimplemented!() } - fn del(&mut self, _offset: u64, _length: u64) -> Result<(), Self::Error> { + async fn del( + &mut self, + _offset: u64, + _length: u64, + ) -> Result<(), Self::Error> { panic!("Not implemented yet"); } - fn truncate(&mut self, length: u64) -> Result<(), Self::Error> { + async fn truncate(&mut self, length: u64) -> Result<(), Self::Error> { let file = self.file.as_ref().expect("self.file was None."); self.length = length as u64; - file.set_len(self.length)?; + file.set_len(self.length).await?; if self.auto_sync { - file.sync_all()?; + file.sync_all().await?; } Ok(()) } - fn len(&self) -> Result { + async fn len(&self) -> Result { Ok(self.length) } - fn is_empty(&mut self) -> Result { + async fn is_empty(&mut self) -> Result { Ok(self.length == 0) } - fn sync_all(&mut self) -> Result<(), Self::Error> { + async fn sync_all(&mut self) -> Result<(), Self::Error> { if !self.auto_sync { let file = self.file.as_ref().expect("self.file was None."); - file.sync_all()?; + file.sync_all().await?; } Ok(()) } @@ -121,7 +138,12 @@ impl RandomAccess for RandomAccessDisk { impl Drop for RandomAccessDisk { fn drop(&mut self) { if let Some(file) = &self.file { - file.sync_all().unwrap(); + // We need to flush the file on drop. Unfortunately, that is not possible to do in a + // non-blocking fashion, but our only other option here is losing data remaining in the + // write cache. Good task schedulers should be resilient to occasional blocking hiccups in + // file destructors so we don't expect this to be a common problem in practice. + // (from async_std::fs::File::drop) + let _ = async_std::task::block_on(file.sync_all()); } } } @@ -142,7 +164,8 @@ impl Builder { self.auto_sync = auto_sync; self } - pub fn build(self) -> Result { + + pub async fn build(self) -> Result { if let Some(dirname) = self.filename.parent() { mkdirp::mkdirp(&dirname)?; } @@ -150,8 +173,9 @@ impl Builder { .create(true) .read(true) .write(true) - .open(&self.filename)?; - file.sync_all()?; + .open(&self.filename) + .await?; + file.sync_all().await?; let metadata = self.filename.metadata()?; Ok(RandomAccessDisk { diff --git a/tests/model.rs b/tests/model.rs index 76cd0da..0406bb6 100644 --- a/tests/model.rs +++ b/tests/model.rs @@ -33,34 +33,36 @@ impl Arbitrary for Op { quickcheck! { fn implementation_matches_model(ops: Vec) -> bool { - let dir = Builder::new().prefix("random-access-disk").tempdir().unwrap(); + async_std::task::block_on(async { + let dir = Builder::new().prefix("random-access-disk").tempdir().unwrap(); - let mut implementation = rad::RandomAccessDisk::open(dir.path().join("1.db")).unwrap(); - let mut model = vec![]; + let mut implementation = rad::RandomAccessDisk::open(dir.path().join("1.db")).await.unwrap(); + let mut model = vec![]; - for op in ops { - match op { - Read { offset, length } => { - let end = offset + length; - if model.len() as u64 >= end { - assert_eq!( - implementation.read(offset, length).expect("Reads should be successful."), - &model[offset as usize..end as usize] - ); - } else { - assert!(implementation.read(offset, length).is_err()); - } - }, - Write { offset, ref data } => { - let end = offset + (data.len() as u64); - if (model.len() as u64) < end { - model.resize(end as usize, 0); - } - implementation.write(offset, data).expect("Writes should be successful."); - model[offset as usize..end as usize].copy_from_slice(data); - }, + for op in ops { + match op { + Read { offset, length } => { + let end = offset + length; + if model.len() as u64 >= end { + assert_eq!( + implementation.read(offset, length).await.expect("Reads should be successful."), + &model[offset as usize..end as usize] + ); + } else { + assert!(implementation.read(offset, length).await.is_err()); + } + }, + Write { offset, ref data } => { + let end = offset + (data.len() as u64); + if (model.len() as u64) < end { + model.resize(end as usize, 0); + } + implementation.write(offset, data).await.expect("Writes should be successful."); + model[offset as usize..end as usize].copy_from_slice(data); + }, + } } - } - true + true + }) } } diff --git a/tests/regression.rs b/tests/regression.rs index 14e8f34..27ab5e6 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -3,28 +3,30 @@ use random_access_storage::RandomAccess; use std::env; use tempfile::Builder; -#[test] +#[async_std::test] // postmortem: read_exact wasn't behaving like we hoped, so we had to switch // back to `.read()` and disable clippy for that rule specifically. -fn regress_1() { +async fn regress_1() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); let mut file = - rad::RandomAccessDisk::open(dir.path().join("regression-1.db")).unwrap(); - file.write(27, b"").unwrap(); - file.read(13, 5).unwrap(); + rad::RandomAccessDisk::open(dir.path().join("regression-1.db")) + .await + .unwrap(); + file.write(27, b"").await.unwrap(); + file.read(13, 5).await.unwrap(); } -#[test] +#[async_std::test] // postmortem: accessing the same file twice would fail, so we had to switch to // from `.create_new()` to `.create()`. // // NOTE: test needs to be run twice in a row to trigger regression. I'm sorry. -fn regress_2() { +async fn regress_2() { let mut dir = env::temp_dir(); dir.push("regression-2.db"); - let mut file = rad::RandomAccessDisk::open(dir).unwrap(); - file.write(27, b"").unwrap(); + let mut file = rad::RandomAccessDisk::open(dir).await.unwrap(); + file.write(27, b"").await.unwrap(); } diff --git a/tests/test.rs b/tests/test.rs index d7d8104..f80d052 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -3,62 +3,72 @@ use random_access_storage::RandomAccess; use std::io::Read; use tempfile::Builder; -#[test] -fn can_call_new() { +#[async_std::test] +async fn can_call_new() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let _file = rad::RandomAccessDisk::open(dir.path().join("1.db")).unwrap(); + let _file = rad::RandomAccessDisk::open(dir.path().join("1.db")) + .await + .unwrap(); } -#[test] -fn can_open_buffer() { +#[async_std::test] +async fn can_open_buffer() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("2.db")).unwrap(); - file.write(0, b"hello").unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("2.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); } -#[test] -fn can_write() { +#[async_std::test] +async fn can_write() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("3.db")).unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("3.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); } -#[test] -fn can_read() { +#[async_std::test] +async fn can_read() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("4.db")).unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - let text = file.read(0, 11).unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("4.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + let text = file.read(0, 11).await.unwrap(); assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world"); } -#[test] -fn can_truncate_lt() { +#[async_std::test] +async fn can_truncate_lt() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("5.db")).unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - file.truncate(7).unwrap(); - let text = file.read(0, 7).unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("5.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + file.truncate(7).await.unwrap(); + let text = file.read(0, 7).await.unwrap(); assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello w"); - match file.read(0, 8) { + match file.read(0, 8).await { Ok(_) => panic!("file is too big. read past the end should have failed"), _ => {} }; @@ -68,22 +78,24 @@ fn can_truncate_lt() { assert_eq!(c_contents, "hello w"); } -#[test] -fn can_truncate_gt() { +#[async_std::test] +async fn can_truncate_gt() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("6.db")).unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - file.truncate(15).unwrap(); - let text = file.read(0, 15).unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("6.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + file.truncate(15).await.unwrap(); + let text = file.read(0, 15).await.unwrap(); assert_eq!( String::from_utf8(text.to_vec()).unwrap(), "hello world\0\0\0\0" ); - match file.read(0, 16) { + match file.read(0, 16).await { Ok(_) => panic!("file is too big. read past the end should have failed"), _ => {} }; @@ -93,19 +105,21 @@ fn can_truncate_gt() { assert_eq!(c_contents, "hello world\0\0\0\0"); } -#[test] -fn can_truncate_eq() { +#[async_std::test] +async fn can_truncate_eq() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("7.db")).unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - file.truncate(11).unwrap(); - let text = file.read(0, 11).unwrap(); + let mut file = rad::RandomAccessDisk::open(dir.path().join("7.db")) + .await + .unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + file.truncate(11).await.unwrap(); + let text = file.read(0, 11).await.unwrap(); assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world"); - match file.read(0, 12) { + match file.read(0, 12).await { Ok(_) => panic!("file is too big. read past the end should have failed"), _ => {} }; @@ -115,46 +129,50 @@ fn can_truncate_eq() { assert_eq!(c_contents, "hello world"); } -#[test] -fn can_len() { +#[async_std::test] +async fn can_len() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("8.db")).unwrap(); - assert_eq!(file.len().unwrap(), 0); - file.write(0, b"hello").unwrap(); - assert_eq!(file.len().unwrap(), 5); - file.write(5, b" world").unwrap(); - assert_eq!(file.len().unwrap(), 11); - file.truncate(15).unwrap(); - assert_eq!(file.len().unwrap(), 15); - file.truncate(8).unwrap(); - assert_eq!(file.len().unwrap(), 8); + let mut file = rad::RandomAccessDisk::open(dir.path().join("8.db")) + .await + .unwrap(); + assert_eq!(file.len().await.unwrap(), 0); + file.write(0, b"hello").await.unwrap(); + assert_eq!(file.len().await.unwrap(), 5); + file.write(5, b" world").await.unwrap(); + assert_eq!(file.len().await.unwrap(), 11); + file.truncate(15).await.unwrap(); + assert_eq!(file.len().await.unwrap(), 15); + file.truncate(8).await.unwrap(); + assert_eq!(file.len().await.unwrap(), 8); } -#[test] -fn can_is_empty() { +#[async_std::test] +async fn can_is_empty() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() .unwrap(); - let mut file = rad::RandomAccessDisk::open(dir.path().join("9.db")).unwrap(); - assert_eq!(file.is_empty().unwrap(), true); - file.write(0, b"hello").unwrap(); - assert_eq!(file.is_empty().unwrap(), false); - file.truncate(0).unwrap(); - assert_eq!(file.is_empty().unwrap(), true); - file.truncate(1).unwrap(); - assert_eq!(file.is_empty().unwrap(), false); - file.truncate(0).unwrap(); - assert_eq!(file.is_empty().unwrap(), true); - file.write(0, b"what").unwrap(); - assert_eq!(file.is_empty().unwrap(), false); + let mut file = rad::RandomAccessDisk::open(dir.path().join("9.db")) + .await + .unwrap(); + assert_eq!(file.is_empty().await.unwrap(), true); + file.write(0, b"hello").await.unwrap(); + assert_eq!(file.is_empty().await.unwrap(), false); + file.truncate(0).await.unwrap(); + assert_eq!(file.is_empty().await.unwrap(), true); + file.truncate(1).await.unwrap(); + assert_eq!(file.is_empty().await.unwrap(), false); + file.truncate(0).await.unwrap(); + assert_eq!(file.is_empty().await.unwrap(), true); + file.write(0, b"what").await.unwrap(); + assert_eq!(file.is_empty().await.unwrap(), false); } -#[test] -fn explicit_no_auto_sync() { +#[async_std::test] +async fn explicit_no_auto_sync() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() @@ -162,14 +180,15 @@ fn explicit_no_auto_sync() { let mut file = rad::RandomAccessDisk::builder(dir.path().join("10.db")) .auto_sync(false) .build() + .await .unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - file.truncate(11).unwrap(); - file.sync_all().unwrap(); - let text = file.read(0, 11).unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + file.truncate(11).await.unwrap(); + file.sync_all().await.unwrap(); + let text = file.read(0, 11).await.unwrap(); assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world"); - match file.read(0, 12) { + match file.read(0, 12).await { Ok(_) => panic!("file is too big. read past the end should have failed"), _ => {} }; @@ -179,8 +198,8 @@ fn explicit_no_auto_sync() { assert_eq!(c_contents, "hello world"); } -#[test] -fn explicit_auto_sync() { +#[async_std::test] +async fn explicit_auto_sync() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() @@ -188,13 +207,14 @@ fn explicit_auto_sync() { let mut file = rad::RandomAccessDisk::builder(dir.path().join("11.db")) .auto_sync(true) .build() + .await .unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - file.truncate(11).unwrap(); - let text = file.read(0, 11).unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + file.truncate(11).await.unwrap(); + let text = file.read(0, 11).await.unwrap(); assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world"); - match file.read(0, 12) { + match file.read(0, 12).await { Ok(_) => panic!("file is too big. read past the end should have failed"), _ => {} }; @@ -204,8 +224,8 @@ fn explicit_auto_sync() { assert_eq!(c_contents, "hello world"); } -#[test] -fn explicit_auto_sync_with_sync_call() { +#[async_std::test] +async fn explicit_auto_sync_with_sync_call() { let dir = Builder::new() .prefix("random-access-disk") .tempdir() @@ -213,14 +233,15 @@ fn explicit_auto_sync_with_sync_call() { let mut file = rad::RandomAccessDisk::builder(dir.path().join("12.db")) .auto_sync(true) .build() + .await .unwrap(); - file.write(0, b"hello").unwrap(); - file.write(5, b" world").unwrap(); - file.truncate(11).unwrap(); - file.sync_all().unwrap(); - let text = file.read(0, 11).unwrap(); + file.write(0, b"hello").await.unwrap(); + file.write(5, b" world").await.unwrap(); + file.truncate(11).await.unwrap(); + file.sync_all().await.unwrap(); + let text = file.read(0, 11).await.unwrap(); assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world"); - match file.read(0, 12) { + match file.read(0, 12).await { Ok(_) => panic!("file is too big. read past the end should have failed"), _ => {} };