Skip to content

Commit 83c1996

Browse files
committed
Added better async support
# Conflicts: # Cargo.lock # common/src/archive.rs # common/src/header.rs # common/src/lib.rs # common/src/store.rs # server/Cargo.toml # server/src/main.rs
1 parent 5792ed4 commit 83c1996

9 files changed

Lines changed: 280 additions & 144 deletions

File tree

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
root=true
2+
3+
[*.rs]
4+
indent_style = tab
5+
indent_size = 4

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use common::{
77
RawEntryData, HEADER,
88
},
99
object_body::Object as OtherObject,
10-
read_header_and_body, read_header_from_file, read_header_from_slice, read_object_into_headers,
11-
Hash, Header, Mode, ObjectType, BLOB_KEY, INDEX_KEY, TREE_KEY,
10+
read_header_and_body, read_header_from_file, read_header_from_slice,
11+
read_object_into_headers_sync, Hash, Header, Mode, ObjectType, BLOB_KEY, INDEX_KEY, TREE_KEY,
1212
};
1313
use sha2::{Digest, Sha512};
1414
use std::{
@@ -919,7 +919,7 @@ fn pack_archive(
919919

920920
let mut headers: HashMap<Hash, Header> = HashMap::new();
921921

922-
read_object_into_headers(cache, &mut headers, &index.tree)?;
922+
read_object_into_headers_sync(cache, &mut headers, &index.tree)?;
923923

924924
//TODO: Surely there is an algorithm to more efficiently lay out this data
925925
let mut i = 0;

common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
anyhow = "1.0.100"
8+
bytes = "1.10.1"
89
chrono = "0.4.41"
910
flate2 = { version = "1.1.5", features = ["zlib-rs"] }
1011
futures = "0.3.31"

common/src/archive.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ use std::{
77
};
88

99
use anyhow::{anyhow, Error};
10+
use futures::AsyncReadExt;
1011
use sha2::{Digest, Sha512};
1112

1213
use crate::{
1314
object_body::{Index, Object},
14-
pipe, Hash,
15+
pipe,
16+
store::Store,
17+
Hash,
1518
};
1619

1720
pub const HEADER: [u8; 4] = [b'a', b'r', b'x', b'a'];
@@ -156,6 +159,34 @@ where
156159
}
157160
}
158161

162+
// /// Create a new `Body` from a [`Stream`].
163+
// ///
164+
// /// [`Stream`]: https://docs.rs/futures-core/latest/futures_core/stream/trait.Stream.html
165+
// pub fn from_stream<S>(stream: S) -> Self
166+
// where
167+
// S: TryStream + Send + 'static,
168+
// S::Ok: Into<Bytes>,
169+
// S::Error: Into<BoxError>,
170+
// {
171+
// Self::new(StreamBody {
172+
// stream: SyncWrapper::new(stream),
173+
// })
174+
// }
175+
176+
// impl<T> Stream for Archive<T>
177+
// where
178+
// T: ArchiveEntryData + Unpin,
179+
// {
180+
// type Item = Result<Bytes, Error>;
181+
182+
// fn poll_next(
183+
// self: std::pin::Pin<&mut Self>,
184+
// cx: &mut std::task::Context<'_>,
185+
// ) -> std::task::Poll<Option<Self::Item>> {
186+
187+
// }
188+
// }
189+
159190
pub struct ArchiveHeaderEntry {
160191
pub hash: Hash,
161192
pub index: u64,
@@ -173,7 +204,6 @@ impl ArchiveEntryData for RawEntryData {
173204
self.0
174205
}
175206
}
176-
177207
pub struct ReaderEntryData<T>(T)
178208
where
179209
T: Read;
@@ -211,6 +241,23 @@ impl ArchiveEntryData for FileEntryData {
211241
}
212242
}
213243

244+
pub struct StoreEntryData {
245+
pub store: Store,
246+
pub hash: Hash,
247+
}
248+
249+
impl<'a> ArchiveEntryData for StoreEntryData {
250+
fn turn_into_vec(self) -> Vec<u8> {
251+
let mut object = futures::executor::block_on(self.store.get_object(&self.hash))
252+
.expect("Object to be available in store");
253+
254+
let mut data: Vec<u8> = Vec::new();
255+
futures::executor::block_on(object.read_to_end(&mut data)).expect("Reading to work");
256+
257+
data
258+
}
259+
}
260+
214261
pub struct ArchiveBody<T>
215262
where
216263
T: ArchiveEntryData,

common/src/lib.rs

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ use std::{
66
str::from_utf8,
77
};
88

9+
use futures::AsyncReadExt;
10+
911
pub use crate::constants::{BLOB_KEY, INDEX_KEY, TREE_KEY};
1012
pub use crate::hash::Hash;
1113
pub use crate::header::Header;
1214
pub use crate::object::Object;
13-
use crate::object_body::Object as ObjectTrait;
1415
pub use crate::primitives::{Mode, ObjectType};
16+
use crate::{object_body::Object as ObjectTrait, store::Store};
1517

1618
pub mod archive;
1719
mod constants;
@@ -59,42 +61,89 @@ pub fn read_header_from_file(reader: &mut BufReader<File>) -> Option<Header> {
5961
read_header_from_slice(&vec[..vec.len() - 1])
6062
}
6163

62-
pub fn read_object_into_headers(
63-
cache: &PathBuf,
64+
pub async fn read_object_into_headers(
65+
store: &Store,
6466
headers: &mut HashMap<Hash, Header>,
6567
object_hash: &Hash,
6668
) -> anyhow::Result<()> {
67-
let object_path = object_hash.get_path(&cache);
68-
assert!(object_path.exists());
69+
let mut stack = vec![object_hash.clone()];
6970

70-
// We assume that if our hash already exists, We probably have already collected all children
71-
if headers.contains_key(object_hash) {
72-
return Ok(());
73-
}
71+
while let Some(current_hash) = stack.pop() {
72+
if headers.contains_key(&current_hash) {
73+
continue;
74+
}
7475

75-
let file = File::open(object_path).expect("file to exist");
76-
let mut reader = BufReader::new(file);
77-
let mut data = Vec::new();
78-
let bytes_read = reader
79-
.read_until(0, &mut data)
80-
.expect("File to be readable");
76+
let mut object = store.get_object(&current_hash).await?;
8177

82-
let header =
83-
read_header_from_slice(&data[..bytes_read - 1]).expect("File to be correctly formatted");
84-
assert!(header.object_type != ObjectType::Index);
78+
if object.header.object_type == ObjectType::Index {
79+
return Err(anyhow::anyhow!(
80+
"Indexes cannot exist within a tree. Likely a hash collision 😳"
81+
));
82+
}
83+
84+
headers.insert(current_hash.clone(), object.header.clone());
85+
86+
if object.header.object_type == ObjectType::Blob {
87+
continue;
88+
}
8589

86-
headers.insert(object_hash.clone(), header.clone());
87-
if header.object_type == ObjectType::Blob {
88-
return Ok(());
90+
let mut data = Vec::new();
91+
let bytes_read = object.read_to_end(&mut data).await?;
92+
93+
assert!(
94+
bytes_read as u64 == object.header.size,
95+
"Read size must match header size"
96+
);
97+
98+
let tree = crate::object_body::Tree::from_data(&data);
99+
100+
for entry in &tree.contents {
101+
stack.push(entry.hash.clone());
102+
}
89103
}
90104

91-
reader.read_to_end(&mut data)?;
105+
Ok(())
106+
}
107+
108+
pub fn read_object_into_headers_sync(
109+
cache: &PathBuf,
110+
headers: &mut HashMap<Hash, Header>,
111+
object_hash: &Hash,
112+
) -> anyhow::Result<()> {
113+
let mut stack = vec![object_hash.clone()];
92114

93-
// println!("Reading Tree {object_hash}");
94-
let tree = crate::object_body::Tree::from_data(&data[bytes_read..]);
115+
while let Some(current_hash) = stack.pop() {
116+
if headers.contains_key(&current_hash) {
117+
continue;
118+
}
119+
120+
let object_path = current_hash.get_path(cache);
121+
let file = File::open(object_path)?;
122+
let mut reader = BufReader::new(file);
123+
let mut data = Vec::new();
124+
let bytes_read = reader.read_until(0, &mut data)?;
95125

96-
for entry in &tree.contents {
97-
read_object_into_headers(cache, headers, &entry.hash)?;
126+
let header = read_header_from_slice(&data[..bytes_read - 1])
127+
.ok_or_else(|| anyhow::anyhow!("Invalid header"))?;
128+
129+
if header.object_type == ObjectType::Index {
130+
return Err(anyhow::anyhow!("Indexes cannot exist within a tree"));
131+
}
132+
133+
headers.insert(current_hash.clone(), header.clone());
134+
135+
if header.object_type == ObjectType::Blob {
136+
continue;
137+
}
138+
139+
data.clear();
140+
reader.read_to_end(&mut data)?;
141+
142+
let tree = crate::object_body::Tree::from_data(&data);
143+
144+
for entry in &tree.contents {
145+
stack.push(entry.hash.clone());
146+
}
98147
}
99148

100149
Ok(())

common/src/store.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ use anyhow::Result;
33
use futures::io::copy;
44
use futures::AsyncReadExt;
55
use futures::{AsyncBufRead, AsyncRead, AsyncWriteExt};
6-
use opendal::{FuturesAsyncReader, Operator};
6+
use opendal::{Builder, FuturesAsyncReader, Operator};
77

8-
pub struct StoreObject<T> {
9-
header: Header,
8+
pub struct StoreObject<T>
9+
where
10+
T: AsyncBufRead + AsyncRead + Unpin,
11+
{
12+
pub header: Header,
1013
body: T,
1114
}
1215

@@ -65,8 +68,8 @@ where
6568
cx: &mut std::task::Context<'_>,
6669
buf: &mut [u8],
6770
) -> std::task::Poll<std::io::Result<usize>> {
68-
let body = unsafe { self.map_unchecked_mut(|s| &mut s.body) };
69-
body.poll_read(cx, buf)
71+
let this = self.get_mut();
72+
std::pin::Pin::new(&mut this.body).poll_read(cx, buf)
7073
}
7174
}
7275

@@ -78,13 +81,13 @@ where
7881
self: std::pin::Pin<&mut Self>,
7982
cx: &mut std::task::Context<'_>,
8083
) -> std::task::Poll<std::io::Result<&[u8]>> {
81-
let body = unsafe { self.map_unchecked_mut(|s| &mut s.body) };
82-
body.poll_fill_buf(cx)
84+
let this = self.get_mut();
85+
std::pin::Pin::new(&mut this.body).poll_fill_buf(cx)
8386
}
8487

8588
fn consume(self: std::pin::Pin<&mut Self>, amt: usize) {
86-
let body = unsafe { self.map_unchecked_mut(|s| &mut s.body) };
87-
body.consume(amt);
89+
let this = self.get_mut();
90+
std::pin::Pin::new(&mut this.body).consume(amt);
8891
}
8992
}
9093

@@ -98,6 +101,10 @@ impl Store {
98101
Self { operator }
99102
}
100103

104+
pub fn from_builder(builder: impl Builder) -> Result<Self> {
105+
Ok(Self::new(Operator::new(builder)?.finish()))
106+
}
107+
101108
pub async fn exists(&self, hash: &Hash) -> Result<bool> {
102109
Ok(self.operator.exists(hash.as_str()).await?)
103110
}

server/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ edition = "2021"
77
axum = { version = "0.8.4", features = ["macros"] }
88
clap = { version = "4.5.51", features = ["derive"] }
99
common = { path = "../common" }
10-
futures = "0.3"
10+
futures = "0.3.31"
11+
futures-core = "0.3.31"
1112
http-body = "1.0.1"
1213
lazy_static = "1.5.0"
1314
opendal = { version = "0.54.1", features = ["services-fs", "services-s3"] }

0 commit comments

Comments
 (0)