Skip to content

Commit fc07e9c

Browse files
authored
Implement async API for random-access-storage (#26)
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 datrs/hypercore#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.
1 parent c10203a commit fc07e9c

File tree

7 files changed

+120
-100
lines changed

7 files changed

+120
-100
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ edition = "2018"
1111

1212
[dependencies]
1313
anyhow = "1.0.26"
14-
random-access-storage = "3.0.0"
14+
random-access-storage = "4.0.0"
15+
futures = "0.3.4"
16+
async-trait = "0.1.24"
1517

1618
[dev-dependencies]
1719
quickcheck = "0.9.2"
1820
rand = "0.7.3"
21+
async-std = { version = "1.5.0", features = ["attributes"] }

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ Continuously read,write to memory using random offsets and lengths.
99

1010
## Usage
1111
```rust
12-
extern crate random_access_memory as ram;
12+
use random_access_memory as ram;
1313

1414
let mut file = ram::Sync::default();
15-
file.write(0, b"hello").unwrap();
16-
file.write(5, b" world").unwrap();
17-
let text = file.read(0, 11).unwrap();
15+
file.write(0, b"hello").await.unwrap();
16+
file.write(5, b" world").await.unwrap();
17+
let text = file.read(0, 11).await.unwrap();
1818
assert_eq!(text, b"hello world");
1919
```
2020

benches/sync.rs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,46 @@
11
#![feature(test)]
22

33
mod sync {
4-
extern crate random_access_memory as ram;
5-
extern crate random_access_storage;
64
extern crate test;
75

8-
use self::random_access_storage::RandomAccess;
9-
use self::test::Bencher;
6+
use random_access_memory as ram;
7+
use random_access_storage::RandomAccess;
8+
use test::Bencher;
109

1110
#[bench]
1211
fn write_hello_world(b: &mut Bencher) {
1312
b.iter(|| {
14-
let mut file = ram::RandomAccessMemory::default();
15-
file.write(0, b"hello").unwrap();
16-
file.write(5, b" world").unwrap();
13+
async_std::task::block_on(async {
14+
let mut file = ram::RandomAccessMemory::default();
15+
file.write(0, b"hello").await.unwrap();
16+
file.write(5, b" world").await.unwrap();
17+
})
1718
});
1819
}
1920

2021
#[bench]
2122
fn read_hello_world(b: &mut Bencher) {
22-
let mut file = ram::RandomAccessMemory::default();
23-
file.write(0, b"hello").unwrap();
24-
file.write(5, b" world").unwrap();
25-
b.iter(|| {
26-
let _text = file.read(0, 11).unwrap();
23+
async_std::task::block_on(async {
24+
let mut file = ram::RandomAccessMemory::default();
25+
file.write(0, b"hello").await.unwrap();
26+
file.write(5, b" world").await.unwrap();
27+
b.iter(|| {
28+
async_std::task::block_on(async {
29+
let _text = file.read(0, 11).await.unwrap();
30+
})
31+
});
2732
});
2833
}
2934

3035
#[bench]
3136
fn read_write_hello_world(b: &mut Bencher) {
3237
b.iter(|| {
33-
let mut file = ram::RandomAccessMemory::default();
34-
file.write(0, b"hello").unwrap();
35-
file.write(5, b" world").unwrap();
36-
let _text = file.read(0, 11).unwrap();
38+
async_std::task::block_on(async {
39+
let mut file = ram::RandomAccessMemory::default();
40+
file.write(0, b"hello").await.unwrap();
41+
file.write(5, b" world").await.unwrap();
42+
let _text = file.read(0, 11).await.unwrap();
43+
})
3744
});
3845
}
3946
}

src/lib.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use anyhow::anyhow;
77
use random_access_storage::RandomAccess;
88
use std::cmp;
9-
use std::io;
109

1110
/// Main constructor.
1211
#[derive(Debug)]
@@ -52,10 +51,15 @@ impl RandomAccessMemory {
5251
}
5352
}
5453

54+
#[async_trait::async_trait]
5555
impl RandomAccess for RandomAccessMemory {
5656
type Error = Box<dyn std::error::Error + Send + Sync>;
5757

58-
fn write(&mut self, offset: u64, data: &[u8]) -> Result<(), Self::Error> {
58+
async fn write(
59+
&mut self,
60+
offset: u64,
61+
data: &[u8],
62+
) -> Result<(), Self::Error> {
5963
let new_len = offset + data.len() as u64;
6064
if new_len > self.length {
6165
self.length = new_len;
@@ -101,11 +105,15 @@ impl RandomAccess for RandomAccessMemory {
101105
Ok(())
102106
}
103107

104-
fn sync_all(&mut self) -> Result<(), Self::Error> {
108+
async fn sync_all(&mut self) -> Result<(), Self::Error> {
105109
Ok(())
106110
}
107111

108-
fn read(&mut self, offset: u64, length: u64) -> Result<Vec<u8>, Self::Error> {
112+
async fn read(
113+
&mut self,
114+
offset: u64,
115+
length: u64,
116+
) -> Result<Vec<u8>, Self::Error> {
109117
if (offset + length) > self.length {
110118
return Err(
111119
anyhow!(
@@ -156,16 +164,16 @@ impl RandomAccess for RandomAccessMemory {
156164
Ok(res_buf)
157165
}
158166

159-
fn read_to_writer(
167+
async fn read_to_writer(
160168
&mut self,
161169
_offset: u64,
162170
_length: u64,
163-
_buf: &mut impl io::Write,
171+
_buf: &mut (impl futures::io::AsyncWrite + Send),
164172
) -> Result<(), Self::Error> {
165173
unimplemented!()
166174
}
167175

168-
fn del(&mut self, offset: u64, length: u64) -> Result<(), Self::Error> {
176+
async fn del(&mut self, offset: u64, length: u64) -> Result<(), Self::Error> {
169177
let overflow = offset % self.page_size as u64;
170178
let inc = match overflow {
171179
0 => 0,
@@ -190,15 +198,15 @@ impl RandomAccess for RandomAccessMemory {
190198
Ok(())
191199
}
192200

193-
fn truncate(&mut self, _length: u64) -> Result<(), Self::Error> {
201+
async fn truncate(&mut self, _length: u64) -> Result<(), Self::Error> {
194202
unimplemented!()
195203
}
196204

197-
fn len(&self) -> Result<u64, Self::Error> {
205+
async fn len(&self) -> Result<u64, Self::Error> {
198206
Ok(self.length)
199207
}
200208

201-
fn is_empty(&mut self) -> Result<bool, Self::Error> {
209+
async fn is_empty(&mut self) -> Result<bool, Self::Error> {
202210
Ok(self.length == 0)
203211
}
204212
}

tests/model.rs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,34 @@ impl Arbitrary for Op {
3232

3333
quickcheck! {
3434
fn implementation_matches_model(ops: Vec<Op>) -> bool {
35-
let mut implementation = ram::RandomAccessMemory::new(10);
36-
let mut model = vec![];
35+
async_std::task::block_on(async {
36+
let mut implementation = ram::RandomAccessMemory::new(10);
37+
let mut model = vec![];
3738

38-
for op in ops {
39-
match op {
40-
Read { offset, length } => {
41-
let end = offset + length;
42-
if model.len() >= end as usize {
43-
assert_eq!(
44-
&*implementation.read(offset, length).expect("Reads should be successful."),
45-
&model[offset as usize..end as usize]
46-
);
47-
} else {
48-
assert!(implementation.read(offset, length).is_err());
49-
}
50-
},
51-
Write { offset, ref data } => {
52-
let end = offset + data.len() as u64;
53-
if model.len() < end as usize {
54-
model.resize(end as usize, 0);
55-
}
56-
implementation.write(offset, &*data).expect("Writes should be successful.");
57-
model[offset as usize..end as usize].copy_from_slice(data);
58-
},
39+
for op in ops {
40+
match op {
41+
Read { offset, length } => {
42+
let end = offset + length;
43+
if model.len() >= end as usize {
44+
assert_eq!(
45+
&*implementation.read(offset, length).await.expect("Reads should be successful."),
46+
&model[offset as usize..end as usize]
47+
);
48+
} else {
49+
assert!(implementation.read(offset, length).await.is_err());
50+
}
51+
},
52+
Write { offset, ref data } => {
53+
let end = offset + data.len() as u64;
54+
if model.len() < end as usize {
55+
model.resize(end as usize, 0);
56+
}
57+
implementation.write(offset, &*data).await.expect("Writes should be successful.");
58+
model[offset as usize..end as usize].copy_from_slice(data);
59+
},
60+
}
5961
}
60-
}
61-
true
62+
true
63+
})
6264
}
6365
}

tests/regression.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,47 @@
11
use random_access_memory as ram;
22
use random_access_storage::RandomAccess;
33

4-
#[test]
4+
#[async_std::test]
55
// Postmortem: looks like we were reading out of bounds by accidentally
66
// adding an offset to the index while reading.
7-
fn regress_1() {
7+
async fn regress_1() {
88
let mut file = ram::RandomAccessMemory::new(50);
9-
file.write(30, &[30]).unwrap();
10-
file.read(15, 15).unwrap();
9+
file.write(30, &[30]).await.unwrap();
10+
file.read(15, 15).await.unwrap();
1111
}
1212

13-
#[test]
13+
#[async_std::test]
1414
// Postmortem: our buffers weren't zero-filled. Intead we were relying on
1515
// uninitialized (but claimed!) memory, which caused all sorts of weirdness.
16-
fn regress_2() {
16+
async fn regress_2() {
1717
let mut file = ram::RandomAccessMemory::new(50);
18-
file.write(22, &[22, 22, 22, 22]).unwrap();
19-
let buf = file.read(1, 4).unwrap();
18+
file.write(22, &[22, 22, 22, 22]).await.unwrap();
19+
let buf = file.read(1, 4).await.unwrap();
2020
assert_eq!(buf, vec![0, 0, 0, 0]);
2121

2222
let mut file = ram::RandomAccessMemory::new(50);
23-
file.write(48, &[48, 48, 48, 48]).unwrap();
24-
let buf = file.read(39, 9).unwrap();
23+
file.write(48, &[48, 48, 48, 48]).await.unwrap();
24+
let buf = file.read(39, 9).await.unwrap();
2525
assert_eq!(buf, vec![0, 0, 0, 0, 0, 0, 0, 0, 0]);
2626
}
2727

28-
#[test]
28+
#[async_std::test]
2929
// Postmortem: the way we were reading was off. We were messing up both reading
3030
// and writing. We now keep two cursors, and compute the bounds of every loop
3131
// ahead of time. Also simplified our allocation logic.
32-
fn regress_3() {
32+
async fn regress_3() {
3333
let mut file = ram::RandomAccessMemory::new(50);
34-
file.write(45, &[56, 46, 14, 93, 15, 54, 2]).unwrap();
35-
let buf = file.read(42, 10).unwrap();
34+
file.write(45, &[56, 46, 14, 93, 15, 54, 2]).await.unwrap();
35+
let buf = file.read(42, 10).await.unwrap();
3636
assert_eq!(buf, vec![0, 0, 0, 56, 46, 14, 93, 15, 54, 2]);
3737
}
3838

39-
#[test]
39+
#[async_std::test]
4040
// Postmortem: we were having trouble when we were reading with an index that's
4141
// larger than the page size. Turned out we weren't doing some math properly,
4242
// which caused a cursor to jump.
43-
fn regress_4() {
43+
async fn regress_4() {
4444
let mut file = ram::RandomAccessMemory::new(10);
45-
file.write(44, &[54, 59]).unwrap();
46-
file.read(13, 3).unwrap();
45+
file.write(44, &[54, 59]).await.unwrap();
46+
file.read(13, 3).await.unwrap();
4747
}

tests/test.rs

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
11
use random_access_memory as ram;
22
use random_access_storage::RandomAccess;
33

4-
#[test]
5-
fn can_call_new() {
4+
#[async_std::test]
5+
async fn can_call_new() {
66
let _file = ram::RandomAccessMemory::default();
77
}
88

9-
#[test]
10-
fn can_open_buffer() {
9+
#[async_std::test]
10+
async fn can_open_buffer() {
1111
let mut file = ram::RandomAccessMemory::default();
12-
file.write(0, b"hello").unwrap();
12+
file.write(0, b"hello").await.unwrap();
1313
}
1414

15-
#[test]
16-
fn can_write() {
15+
#[async_std::test]
16+
async fn can_write() {
1717
let mut file = ram::RandomAccessMemory::default();
18-
file.write(0, b"hello").unwrap();
19-
file.write(5, b" world").unwrap();
18+
file.write(0, b"hello").await.unwrap();
19+
file.write(5, b" world").await.unwrap();
2020
}
2121

22-
#[test]
23-
fn can_read() {
22+
#[async_std::test]
23+
async fn can_read() {
2424
let mut file = ram::RandomAccessMemory::default();
25-
file.write(0, b"hello").unwrap();
26-
file.write(5, b" world").unwrap();
27-
let text = file.read(0, 11).unwrap();
25+
file.write(0, b"hello").await.unwrap();
26+
file.write(5, b" world").await.unwrap();
27+
let text = file.read(0, 11).await.unwrap();
2828
let text = String::from_utf8(text.to_vec()).unwrap();
2929
assert_eq!(text, "hello world");
3030
}
3131

32-
#[test]
33-
fn can_len() {
32+
#[async_std::test]
33+
async fn can_len() {
3434
let mut file = ram::RandomAccessMemory::default();
35-
assert_eq!(file.len().unwrap(), 0);
36-
file.write(0, b"hello").unwrap();
37-
assert_eq!(file.len().unwrap(), 5);
38-
file.write(5, b" world").unwrap();
39-
assert_eq!(file.len().unwrap(), 11);
35+
assert_eq!(file.len().await.unwrap(), 0);
36+
file.write(0, b"hello").await.unwrap();
37+
assert_eq!(file.len().await.unwrap(), 5);
38+
file.write(5, b" world").await.unwrap();
39+
assert_eq!(file.len().await.unwrap(), 11);
4040
}
4141

42-
#[test]
43-
fn can_is_empty() {
42+
#[async_std::test]
43+
async fn can_is_empty() {
4444
let mut file = ram::RandomAccessMemory::default();
45-
assert_eq!(file.is_empty().unwrap(), true);
46-
file.write(0, b"hello").unwrap();
47-
assert_eq!(file.is_empty().unwrap(), false);
45+
assert_eq!(file.is_empty().await.unwrap(), true);
46+
file.write(0, b"hello").await.unwrap();
47+
assert_eq!(file.is_empty().await.unwrap(), false);
4848
}

0 commit comments

Comments
 (0)