Skip to content

Commit 11a0237

Browse files
committed
feat: implement a resource pool
1 parent ead9a29 commit 11a0237

File tree

3 files changed

+295
-11
lines changed

3 files changed

+295
-11
lines changed

mithril-aggregator/src/services/prover.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@ use rayon::prelude::*;
33
use std::{
44
collections::{BTreeMap, BTreeSet, HashMap},
55
sync::Arc,
6+
time::Duration,
67
};
7-
use tokio::sync::Mutex;
88

99
use mithril_common::{
1010
crypto_helper::{MKMap, MKMapNode, MKTree},
1111
entities::{
1212
BlockRange, CardanoDbBeacon, CardanoTransaction, CardanoTransactionsSetProof,
1313
TransactionHash,
1414
},
15+
resource_pool::ResourcePool,
1516
signable_builder::BlockRangeRootRetriever,
1617
StdResult,
1718
};
@@ -62,7 +63,7 @@ pub trait TransactionsRetriever: Sync + Send {
6263
pub struct MithrilProverService {
6364
transaction_retriever: Arc<dyn TransactionsRetriever>,
6465
block_range_root_retriever: Arc<dyn BlockRangeRootRetriever>,
65-
mk_map_cache: Mutex<Option<MKMap<BlockRange, MKMapNode<BlockRange>>>>,
66+
mk_map_cache: ResourcePool<MKMap<BlockRange, MKMapNode<BlockRange>>>,
6667
}
6768

6869
impl MithrilProverService {
@@ -74,7 +75,7 @@ impl MithrilProverService {
7475
Self {
7576
transaction_retriever,
7677
block_range_root_retriever,
77-
mk_map_cache: Mutex::new(None),
78+
mk_map_cache: ResourcePool::default(),
7879
}
7980
}
8081

@@ -139,9 +140,11 @@ impl ProverService for MithrilProverService {
139140
let mk_trees = BTreeMap::from_iter(mk_trees?);
140141

141142
// 3 - Compute block range roots Merkle map
143+
// TODO: the cache computation should be done in the state machine only when new artifact is produced and at node startup
142144
self.compute_cache(up_to).await?;
143-
let mut mk_map = self.mk_map_cache.lock().await;
144-
let mk_map = mk_map.as_mut().unwrap();
145+
let mut mk_map = self
146+
.mk_map_cache
147+
.acquire_resource(Duration::from_millis(1000))?;
145148

146149
// 4 - Enrich the Merkle map with the block ranges Merkle trees
147150
for (block_range, mk_tree) in mk_trees {
@@ -150,6 +153,9 @@ impl ProverService for MithrilProverService {
150153

151154
// 5 - Compute the proof for all transactions
152155
if let Ok(mk_proof) = mk_map.compute_proof(transaction_hashes) {
156+
self.mk_map_cache
157+
.return_resource(mk_map.into_inner(), mk_map.discriminant());
158+
153159
let transaction_hashes_certified: Vec<TransactionHash> = transaction_hashes
154160
.iter()
155161
.filter(|hash| mk_proof.contains(&hash.as_str().into()).is_ok())
@@ -166,22 +172,30 @@ impl ProverService for MithrilProverService {
166172
}
167173

168174
async fn compute_cache(&self, up_to: &CardanoDbBeacon) -> StdResult<()> {
169-
let mut mk_map = self.mk_map_cache.lock().await;
170-
if mk_map.is_none() {
171-
println!("Computing Merkle map from block range roots");
175+
if self.mk_map_cache.count() == 0 {
176+
println!("Computing Merkle map cache from block range roots");
177+
172178
let mk_map_cache = self
173179
.block_range_root_retriever
174180
.compute_merkle_map_from_block_range_roots(up_to.immutable_file_number)
175181
.await?;
176-
mk_map.replace(mk_map_cache);
182+
let discriminant_new = self.mk_map_cache.discriminant() + 1;
183+
self.mk_map_cache.set_discriminant(discriminant_new);
184+
for i in 0..10 {
185+
println!("Computing Merkle map cache from block range roots: {}", i);
186+
self.mk_map_cache
187+
.return_resource(mk_map_cache.clone(), discriminant_new);
188+
}
189+
self.mk_map_cache
190+
.return_resource(mk_map_cache, discriminant_new);
191+
println!("Done computing Merkle map cache from block range roots");
177192
}
178193

179194
Ok(())
180195
}
181196

182197
async fn clear_cache(&self) -> StdResult<()> {
183-
let mut mk_map = self.mk_map_cache.lock().await;
184-
mk_map.take();
198+
self.mk_map_cache.drain();
185199

186200
Ok(())
187201
}

mithril-common/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub mod entities;
5959
pub mod era;
6060
pub mod messages;
6161
pub mod protocol;
62+
pub mod resource_pool;
6263
pub mod signable_builder;
6364

6465
cfg_test_tools! {

mithril-common/src/resource_pool.rs

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
//! Resource pool implementation
2+
// TODO: better error implementation
3+
use anyhow::anyhow;
4+
use std::{
5+
collections::VecDeque,
6+
ops::{Deref, DerefMut},
7+
sync::{Condvar, Mutex},
8+
time::Duration,
9+
};
10+
11+
use crate::StdResult;
12+
13+
/// Resource pool FIFO implementation
14+
pub struct ResourcePool<T: Send + Sync> {
15+
/// The size of the pool
16+
size: usize,
17+
18+
/// Discriminant for the resource pool to check if a returned resource is stale
19+
discriminant: Mutex<u64>,
20+
21+
/// Resources in the pool
22+
resources: Mutex<VecDeque<T>>,
23+
24+
/// Condition variable to notify when a resource is available
25+
not_empty: Condvar,
26+
}
27+
28+
impl<T: Send + Sync> ResourcePool<T> {
29+
/// Create a new resource pool
30+
pub fn new(pool_size: usize, resources: Vec<T>) -> Self {
31+
Self {
32+
size: pool_size,
33+
discriminant: Mutex::new(0),
34+
resources: Mutex::new(resources.into()),
35+
not_empty: Condvar::new(),
36+
}
37+
}
38+
39+
/// Acquire a resource from the pool with a timeout
40+
pub fn acquire_resource(&self, timeout: Duration) -> StdResult<ResourcePoolItem<'_, T>> {
41+
let mut resources = self.resources.lock().unwrap();
42+
while resources.is_empty() {
43+
let (resources_locked, timeout) =
44+
self.not_empty.wait_timeout(resources, timeout).unwrap();
45+
if timeout.timed_out() {
46+
return Err(anyhow!("Acquire resource has timed out"));
47+
}
48+
resources = resources_locked;
49+
}
50+
51+
Ok(ResourcePoolItem::new(self, resources.pop_front().unwrap()))
52+
}
53+
54+
/// Return a resource to the pool
55+
/// A resource is returned to the pool only if the discriminant matches
56+
/// and if the pool is not already full
57+
pub fn return_resource(&self, resource: T, discriminant: u64) {
58+
if self.count() == self.size {
59+
return;
60+
}
61+
let mut resources = self.resources.lock().unwrap();
62+
if self.discriminant() != discriminant {
63+
return;
64+
}
65+
resources.push_back(resource);
66+
self.not_empty.notify_one();
67+
}
68+
69+
/// Drain the pool
70+
pub fn drain(&self) {
71+
let mut resources = self.resources.lock().unwrap();
72+
let _ = resources.drain(..).collect::<Vec<_>>();
73+
}
74+
75+
/// Get the discriminant of the resource pool item
76+
pub fn discriminant(&self) -> u64 {
77+
*self.discriminant.lock().unwrap()
78+
}
79+
80+
/// Set the discriminant of the resource pool item
81+
pub fn set_discriminant(&self, discriminant: u64) {
82+
*self.discriminant.lock().unwrap() = discriminant;
83+
}
84+
85+
/// Count the resources in the pool
86+
pub fn count(&self) -> usize {
87+
self.resources.lock().unwrap().len()
88+
}
89+
90+
/// Size of the resource pool
91+
pub fn size(&self) -> usize {
92+
self.size
93+
}
94+
}
95+
96+
impl<T: Send + Sync> Default for ResourcePool<T> {
97+
fn default() -> Self {
98+
Self::new(10, vec![])
99+
}
100+
}
101+
102+
/// Resource pool item which will return the resource to the pool when dropped
103+
pub struct ResourcePoolItem<'a, T: Send + Sync> {
104+
resource_pool: &'a ResourcePool<T>,
105+
discriminant: u64,
106+
resource: Option<T>,
107+
}
108+
109+
impl<'a, T: Send + Sync> ResourcePoolItem<'a, T> {
110+
/// Create a new resource pool item
111+
pub fn new(resource_pool: &'a ResourcePool<T>, resource: T) -> Self {
112+
let discriminant = *resource_pool.discriminant.lock().unwrap();
113+
Self {
114+
resource_pool,
115+
discriminant,
116+
resource: Some(resource),
117+
}
118+
}
119+
120+
/// Get the discriminant of the resource pool item
121+
pub fn discriminant(&self) -> u64 {
122+
self.discriminant
123+
}
124+
125+
/// Get a reference to the inner resource
126+
pub fn resource(&self) -> &T {
127+
self.resource.as_ref().unwrap()
128+
}
129+
130+
/// Take the inner resource
131+
pub fn into_inner(&mut self) -> T {
132+
self.resource.take().unwrap()
133+
}
134+
}
135+
136+
impl<T: Send + Sync> Deref for ResourcePoolItem<'_, T> {
137+
type Target = T;
138+
139+
fn deref(&self) -> &T {
140+
self.resource.as_ref().unwrap()
141+
}
142+
}
143+
144+
impl<T: Send + Sync> DerefMut for ResourcePoolItem<'_, T> {
145+
fn deref_mut(&mut self) -> &mut T {
146+
self.resource.as_mut().unwrap()
147+
}
148+
}
149+
150+
impl<T: Send + Sync> Drop for ResourcePoolItem<'_, T> {
151+
fn drop(&mut self) {
152+
if self.resource.is_some() {
153+
let resource = self.into_inner();
154+
self.resource_pool
155+
.return_resource(resource, self.discriminant);
156+
}
157+
}
158+
}
159+
160+
#[cfg(test)]
161+
mod tests {
162+
use std::time::Duration;
163+
164+
use super::*;
165+
166+
#[test]
167+
fn test_resource_pool_acquire_returns_resource_when_available() {
168+
let pool_size = 10;
169+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
170+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
171+
172+
let mut resources_items = vec![];
173+
for _ in 0..pool_size {
174+
let resource_item = pool.acquire_resource(Duration::from_millis(1000)).unwrap();
175+
resources_items.push(resource_item);
176+
}
177+
let resources_result = resources_items
178+
.iter_mut()
179+
.map(|resource_item| resource_item.resource().to_owned())
180+
.collect::<Vec<_>>();
181+
182+
assert_eq!(resources_expected, resources_result);
183+
assert_eq!(pool.count(), 0);
184+
}
185+
186+
#[tokio::test]
187+
async fn test_resource_pool_acquire_locks_until_timeout_when_no_resource_available() {
188+
let pool_size = 10;
189+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
190+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
191+
192+
let mut resources_items = vec![];
193+
for _ in 0..pool_size {
194+
let resource_item = pool.acquire_resource(Duration::from_millis(1000)).unwrap();
195+
resources_items.push(resource_item);
196+
}
197+
198+
assert!(pool.acquire_resource(Duration::from_millis(1000)).is_err());
199+
}
200+
201+
#[tokio::test]
202+
async fn test_resource_pool_drains_successfully() {
203+
let pool_size = 10;
204+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
205+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
206+
assert_eq!(pool.count(), pool_size);
207+
208+
pool.drain();
209+
210+
assert_eq!(pool.count(), 0);
211+
}
212+
213+
#[tokio::test]
214+
async fn test_resource_pool_returns_fresh_resource() {
215+
let pool_size = 10;
216+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
217+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
218+
assert_eq!(pool.count(), pool_size);
219+
220+
let mut resource_item = pool.acquire_resource(Duration::from_millis(1000)).unwrap();
221+
assert_eq!(pool.count(), pool_size - 1);
222+
pool.return_resource(resource_item.into_inner(), pool.discriminant());
223+
224+
assert_eq!(pool.count(), pool_size);
225+
}
226+
227+
#[tokio::test]
228+
async fn test_resource_pool_returnsy_resource_automaticall() {
229+
let pool_size = 10;
230+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
231+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
232+
assert_eq!(pool.count(), pool_size);
233+
234+
{
235+
let _resource_item = pool.acquire_resource(Duration::from_millis(1000)).unwrap();
236+
assert_eq!(pool.count(), pool_size - 1);
237+
}
238+
239+
assert_eq!(pool.count(), pool_size);
240+
}
241+
242+
#[tokio::test]
243+
async fn test_resource_pool_does_not_return_resource_when_pool_is_full() {
244+
let pool_size = 10;
245+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
246+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
247+
assert_eq!(pool.count(), pool_size);
248+
249+
pool.return_resource("resource".to_string(), pool.discriminant());
250+
251+
assert_eq!(pool.count(), pool_size);
252+
}
253+
254+
#[tokio::test]
255+
async fn test_resource_pool_does_not_return_stale_resource() {
256+
let pool_size = 10;
257+
let resources_expected: Vec<String> = (0..pool_size).map(|i| i.to_string()).collect();
258+
let pool = ResourcePool::<String>::new(pool_size, resources_expected.clone());
259+
assert_eq!(pool.count(), pool_size);
260+
261+
let mut resource_item = pool.acquire_resource(Duration::from_millis(1000)).unwrap();
262+
assert_eq!(pool.count(), pool_size - 1);
263+
let discriminant_stale = pool.discriminant();
264+
pool.set_discriminant(pool.discriminant() + 1);
265+
pool.return_resource(resource_item.into_inner(), discriminant_stale);
266+
267+
assert_eq!(pool.count(), pool_size - 1);
268+
}
269+
}

0 commit comments

Comments
 (0)