Skip to content

Commit 56ffb67

Browse files
authored
Added basic Rust request tracker implementation (#9743)
* Added basic single threaded request tracker implementation without invalidations * slightly cleaner
1 parent 5705e40 commit 56ffb67

File tree

8 files changed

+326
-0
lines changed

8 files changed

+326
-0
lines changed

Cargo.lock

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/parcel_core/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ serde_repr = "0.1.19"
1919
serde-value = "0.7.0"
2020
xxhash-rust = { version = "0.8.2", features = ["xxh3"] }
2121
anyhow = "1.0.82"
22+
dyn-hash = "0.x"
23+
petgraph = "0.x"

crates/parcel_core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
pub mod hash;
55
pub mod plugin;
6+
pub mod request_tracker;
67
pub mod types;
78

89
// pub use parcel::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use std::sync::atomic::AtomicUsize;
2+
use std::sync::atomic::Ordering;
3+
use std::sync::Arc;
4+
use std::sync::Mutex;
5+
6+
use super::*;
7+
8+
#[test]
9+
fn should_run_request() {
10+
let rt = RequestTracker::<Vec<String>>::new();
11+
12+
let request_c = TestRequest::new("C", &[]);
13+
let request_b = TestRequest::new("B", &[&request_c]);
14+
let request_a = TestRequest::new("A", &[&request_b]);
15+
16+
let result = rt.run_request(Box::new(&request_a)).unwrap();
17+
18+
assert_eq!(result[0], "A");
19+
assert_eq!(result[1], "B");
20+
assert_eq!(result[2], "C");
21+
}
22+
23+
#[test]
24+
fn should_reuse_previously_run_request() {
25+
let rt = RequestTracker::<Vec<String>>::new();
26+
27+
let request_c = TestRequest::new("C", &[]);
28+
let request_b = TestRequest::new("B", &[&request_c]);
29+
let request_a = TestRequest::new("A", &[&request_b]);
30+
31+
let result = rt.run_request(Box::new(&request_a)).unwrap();
32+
33+
assert_eq!(result[0], "A");
34+
assert_eq!(result[1], "B");
35+
assert_eq!(result[2], "C");
36+
37+
let result = rt.run_request(Box::new(&request_a)).unwrap();
38+
assert_eq!(result[0], "A");
39+
assert_eq!(result[1], "B");
40+
assert_eq!(result[2], "C");
41+
}
42+
43+
#[test]
44+
fn should_run_request_once() {
45+
let rt = RequestTracker::<Vec<String>>::new();
46+
47+
let request_a = TestRequest::new("A", &[]);
48+
49+
let result = rt.run_request(Box::new(&request_a)).unwrap();
50+
51+
assert_eq!(result[0], "A");
52+
assert_eq!(request_a.run_count(), 1);
53+
54+
let result = rt.run_request(Box::new(&request_a)).unwrap();
55+
assert_eq!(result[0], "A");
56+
assert_eq!(request_a.run_count(), 1);
57+
}
58+
59+
#[test]
60+
fn should_run_request_once_2() {
61+
let rt = RequestTracker::<Vec<String>>::new();
62+
63+
let request_b = TestRequest::new("B", &[]);
64+
let request_a = TestRequest::new("A", &[&request_b]);
65+
66+
let result = rt.run_request(Box::new(&request_a)).unwrap();
67+
68+
assert_eq!(result[0], "A");
69+
assert_eq!(result[1], "B");
70+
assert_eq!(request_a.run_count(), 1);
71+
assert_eq!(request_b.run_count(), 1);
72+
73+
let result = rt.run_request(Box::new(&request_a)).unwrap();
74+
assert_eq!(result[0], "A");
75+
assert_eq!(result[1], "B");
76+
assert_eq!(request_a.run_count(), 1);
77+
assert_eq!(request_b.run_count(), 1);
78+
}
79+
80+
/// This is a universal "Request" that can be instructed
81+
/// to run subrequests via the constructor
82+
#[derive(Clone, Default)]
83+
pub struct TestRequest<'a> {
84+
pub runs: Arc<AtomicUsize>,
85+
pub name: String,
86+
pub subrequets: Arc<Mutex<Vec<&'a TestRequest<'a>>>>,
87+
}
88+
89+
impl<'a> std::fmt::Debug for TestRequest<'a> {
90+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91+
f.debug_struct(&format!("TestRequest({})", self.name))
92+
.finish()
93+
}
94+
}
95+
96+
impl<'a> TestRequest<'a> {
97+
pub fn new<T: AsRef<str>>(name: T, subrequests: &[&'a TestRequest<'a>]) -> Self {
98+
Self {
99+
runs: Default::default(),
100+
name: name.as_ref().to_string(),
101+
subrequets: Arc::new(Mutex::new(subrequests.to_owned())),
102+
}
103+
}
104+
105+
pub fn run_count(&self) -> usize {
106+
self.runs.load(Ordering::Relaxed)
107+
}
108+
}
109+
110+
impl<'a> std::hash::Hash for TestRequest<'a> {
111+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
112+
self.name.hash(state);
113+
}
114+
}
115+
116+
impl<'a> Request<Vec<String>> for TestRequest<'a> {
117+
fn run(
118+
&self,
119+
rt: RequestTracker<Vec<String>>,
120+
) -> Result<RequestResult<Vec<String>>, Vec<RequestError>> {
121+
self.runs.fetch_add(1, Ordering::Relaxed);
122+
123+
let name = self.name.clone();
124+
let mut subrequets = self.subrequets.lock().unwrap().clone();
125+
126+
let mut result = vec![name];
127+
128+
while let Some(subrequest) = subrequets.pop() {
129+
let req = subrequest.clone();
130+
let subrequest_result = rt.run_request(Box::new(&req))?;
131+
result.extend(subrequest_result);
132+
}
133+
134+
Ok(RequestResult {
135+
result,
136+
invalidations: vec![],
137+
})
138+
}
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
mod request;
2+
mod request_graph;
3+
mod request_tracker;
4+
5+
#[cfg(test)]
6+
mod _test;
7+
8+
pub use self::request::*;
9+
pub use self::request_graph::*;
10+
pub use self::request_tracker::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::fmt::Debug;
2+
use std::hash::DefaultHasher;
3+
use std::hash::Hash;
4+
use std::hash::Hasher;
5+
6+
use dyn_hash::DynHash;
7+
8+
use super::RequestTracker;
9+
10+
pub trait Request<T: Clone>: DynHash {
11+
fn id(&self) -> u64 {
12+
let mut hasher = DefaultHasher::default();
13+
std::any::type_name::<Self>().hash(&mut hasher);
14+
self.dyn_hash(&mut hasher);
15+
hasher.finish()
16+
}
17+
18+
fn run(&self, request_tracker: RequestTracker<T>) -> Result<RequestResult<T>, Vec<RequestError>>;
19+
}
20+
21+
dyn_hash::hash_trait_object!(<T: Clone> Request<T>);
22+
23+
pub struct RequestResult<Req> {
24+
pub result: Req,
25+
pub invalidations: Vec<Invalidation>,
26+
}
27+
28+
#[derive(Debug, Clone)]
29+
pub enum RequestError {
30+
Impossible,
31+
}
32+
33+
#[derive(Debug)]
34+
pub enum Invalidation {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use petgraph::stable_graph::StableDiGraph;
2+
3+
use super::RequestError;
4+
5+
pub type RequestGraph<T> = StableDiGraph<RequestNode<T>, RequestEdgeType>;
6+
7+
#[derive(Debug)]
8+
pub enum RequestNode<T> {
9+
Error(Vec<RequestError>),
10+
Root,
11+
Incomplete,
12+
Valid(T),
13+
}
14+
15+
#[derive(Debug)]
16+
pub enum RequestEdgeType {
17+
SubRequest,
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use std::cell::RefCell;
2+
use std::collections::HashMap;
3+
use std::rc::Rc;
4+
5+
use petgraph::graph::NodeIndex;
6+
use petgraph::stable_graph::StableDiGraph;
7+
8+
use super::Request;
9+
use super::RequestEdgeType;
10+
use super::RequestError;
11+
use super::RequestGraph;
12+
use super::RequestNode;
13+
use super::RequestResult;
14+
15+
#[derive(Clone)]
16+
pub struct RequestTracker<T: Clone> {
17+
parent_request_hash: Option<u64>,
18+
graph: Rc<RefCell<RequestGraph<T>>>,
19+
request_index: Rc<RefCell<HashMap<u64, NodeIndex>>>,
20+
}
21+
22+
impl<T: Clone> RequestTracker<T> {
23+
pub fn new() -> Self {
24+
let mut graph = StableDiGraph::<RequestNode<T>, RequestEdgeType>::new();
25+
graph.add_node(RequestNode::Root);
26+
RequestTracker {
27+
parent_request_hash: None,
28+
graph: Rc::new(RefCell::new(graph)),
29+
request_index: Rc::new(RefCell::new(HashMap::new())),
30+
}
31+
}
32+
33+
pub fn run_request(&self, request: Box<&dyn Request<T>>) -> Result<T, Vec<RequestError>> {
34+
let request_id = request.id();
35+
36+
if self.prepare_request(request_id.clone()) {
37+
let mut rt = self.clone();
38+
rt.parent_request_hash.replace(request_id.clone());
39+
self.store_request(&request_id, request.run(rt));
40+
}
41+
42+
self.get_request(&request_id)
43+
}
44+
45+
fn prepare_request(&self, request_id: u64) -> bool {
46+
let mut graph = self.graph.borrow_mut();
47+
let mut request_index = self.request_index.borrow_mut();
48+
49+
let node_index = request_index
50+
.entry(request_id)
51+
.or_insert_with(|| graph.add_node(RequestNode::Incomplete));
52+
53+
let request_node = graph.node_weight_mut(*node_index).unwrap();
54+
55+
// Don't run if already run
56+
if let RequestNode::<T>::Valid(_) = request_node {
57+
return false;
58+
}
59+
60+
*request_node = RequestNode::Incomplete;
61+
true
62+
}
63+
64+
fn store_request(&self, request_id: &u64, result: Result<RequestResult<T>, Vec<RequestError>>) {
65+
let request_index = self.request_index.borrow();
66+
let mut graph = self.graph.borrow_mut();
67+
68+
let node_index = request_index.get(&request_id).unwrap();
69+
70+
let request_node = graph.node_weight_mut(*node_index).unwrap();
71+
if let RequestNode::<T>::Valid(_) = request_node {
72+
return;
73+
}
74+
*request_node = match result {
75+
Ok(result) => RequestNode::Valid(result.result),
76+
Err(error) => RequestNode::Error(error),
77+
};
78+
}
79+
80+
fn get_request(&self, request_id: &u64) -> Result<T, Vec<RequestError>> {
81+
let mut graph = self.graph.borrow_mut();
82+
let request_index = self.request_index.borrow();
83+
84+
let Some(node_index) = request_index.get(&request_id) else {
85+
return Err(vec![RequestError::Impossible]);
86+
};
87+
88+
if let Some(parent_request_id) = self.parent_request_hash {
89+
let parent_node_index = request_index.get(&parent_request_id).unwrap();
90+
graph.add_edge(
91+
parent_node_index.clone(),
92+
node_index.clone(),
93+
RequestEdgeType::SubRequest,
94+
);
95+
} else {
96+
graph.add_edge(
97+
NodeIndex::new(0),
98+
node_index.clone(),
99+
RequestEdgeType::SubRequest,
100+
);
101+
}
102+
103+
let Some(request_node) = graph.node_weight(node_index.clone()) else {
104+
return Err(vec![RequestError::Impossible]);
105+
};
106+
107+
match request_node {
108+
RequestNode::Root => Err(vec![RequestError::Impossible]),
109+
RequestNode::Incomplete => Err(vec![RequestError::Impossible]),
110+
RequestNode::Error(errors) => Err(errors.to_owned()),
111+
RequestNode::Valid(value) => Ok(value.clone()),
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)