2018-06-04 10:19:50 +02:00
|
|
|
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
|
2017-01-04 18:00:12 +01:00
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
//! Pending request set.
|
|
|
|
//!
|
|
|
|
//! Stores pending requests and does timeout computation according to the rule
|
|
|
|
//! that only the earliest submitted request within the structure may time out.
|
|
|
|
//!
|
|
|
|
//! Whenever a request becomes the earliest, its timeout period begins at that moment.
|
|
|
|
|
|
|
|
use std::collections::{BTreeMap, HashMap};
|
|
|
|
use std::iter::FromIterator;
|
2018-03-14 12:29:52 +01:00
|
|
|
use std::time::{Duration, Instant};
|
2017-01-04 18:00:12 +01:00
|
|
|
|
2017-03-08 18:01:41 +01:00
|
|
|
use request::Request;
|
2017-04-05 15:02:44 +02:00
|
|
|
use request::NetworkRequests as Requests;
|
2017-01-04 18:00:12 +01:00
|
|
|
use net::{timeout, ReqId};
|
2018-01-10 13:35:18 +01:00
|
|
|
use ethereum_types::U256;
|
2017-01-04 18:00:12 +01:00
|
|
|
|
2017-03-22 19:26:51 +01:00
|
|
|
// Request set entry: requests + cost.
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct Entry(Requests, U256);
|
|
|
|
|
2017-01-04 18:00:12 +01:00
|
|
|
/// Request set.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct RequestSet {
|
|
|
|
counter: u64,
|
2017-03-22 19:26:51 +01:00
|
|
|
cumulative_cost: U256,
|
2018-03-14 12:29:52 +01:00
|
|
|
base: Option<Instant>,
|
2017-01-04 18:00:12 +01:00
|
|
|
ids: HashMap<ReqId, u64>,
|
2017-03-22 19:26:51 +01:00
|
|
|
reqs: BTreeMap<u64, Entry>,
|
2017-01-04 18:00:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for RequestSet {
|
|
|
|
fn default() -> Self {
|
|
|
|
RequestSet {
|
|
|
|
counter: 0,
|
2017-03-22 19:26:51 +01:00
|
|
|
cumulative_cost: 0.into(),
|
2017-01-04 18:00:12 +01:00
|
|
|
base: None,
|
|
|
|
ids: HashMap::new(),
|
|
|
|
reqs: BTreeMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RequestSet {
|
2017-03-08 17:37:07 +01:00
|
|
|
/// Push requests onto the stack.
|
2018-03-14 12:29:52 +01:00
|
|
|
pub fn insert(&mut self, req_id: ReqId, req: Requests, cost: U256, now: Instant) {
|
2017-01-04 18:00:12 +01:00
|
|
|
let counter = self.counter;
|
2017-03-22 19:26:51 +01:00
|
|
|
self.cumulative_cost = self.cumulative_cost + cost;
|
|
|
|
|
2017-01-04 18:00:12 +01:00
|
|
|
self.ids.insert(req_id, counter);
|
2017-03-22 19:26:51 +01:00
|
|
|
self.reqs.insert(counter, Entry(req, cost));
|
2017-01-04 18:00:12 +01:00
|
|
|
|
|
|
|
if self.reqs.keys().next().map_or(true, |x| *x == counter) {
|
|
|
|
self.base = Some(now);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.counter += 1;
|
|
|
|
}
|
|
|
|
|
2017-03-08 17:37:07 +01:00
|
|
|
/// Remove a set of requests from the stack.
|
2018-03-14 12:29:52 +01:00
|
|
|
pub fn remove(&mut self, req_id: &ReqId, now: Instant) -> Option<Requests> {
|
2017-01-04 18:00:12 +01:00
|
|
|
let id = match self.ids.remove(&req_id) {
|
|
|
|
Some(id) => id,
|
|
|
|
None => return None,
|
|
|
|
};
|
|
|
|
|
2017-03-22 19:26:51 +01:00
|
|
|
let Entry(req, cost) = self.reqs.remove(&id).expect("entry in `ids` implies entry in `reqs`; qed");
|
2017-01-04 18:00:12 +01:00
|
|
|
|
|
|
|
match self.reqs.keys().next() {
|
|
|
|
Some(k) if *k > id => self.base = Some(now),
|
|
|
|
None => self.base = None,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
2017-03-22 19:26:51 +01:00
|
|
|
self.cumulative_cost = self.cumulative_cost - cost;
|
2017-01-04 18:00:12 +01:00
|
|
|
Some(req)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Check for timeout against the given time. Returns true if
|
|
|
|
/// has timed out, false otherwise.
|
2018-03-14 12:29:52 +01:00
|
|
|
pub fn check_timeout(&self, now: Instant) -> bool {
|
2017-01-04 18:00:12 +01:00
|
|
|
let base = match self.base.as_ref().cloned() {
|
|
|
|
Some(base) => base,
|
|
|
|
None => return false,
|
|
|
|
};
|
|
|
|
|
2017-03-08 17:37:07 +01:00
|
|
|
let first_req = self.reqs.values().next()
|
|
|
|
.expect("base existing implies `reqs` non-empty; qed");
|
|
|
|
|
2017-03-22 19:26:51 +01:00
|
|
|
base + compute_timeout(&first_req.0) <= now
|
2017-01-04 18:00:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Collect all pending request ids.
|
|
|
|
pub fn collect_ids<F>(&self) -> F where F: FromIterator<ReqId> {
|
|
|
|
self.ids.keys().cloned().collect()
|
|
|
|
}
|
2017-02-17 21:38:43 +01:00
|
|
|
|
|
|
|
/// Number of requests in the set.
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.ids.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Whether the set is empty.
|
|
|
|
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
2017-03-22 19:26:51 +01:00
|
|
|
|
|
|
|
/// The cumulative cost of all requests in the set.
|
2017-05-23 12:31:09 +02:00
|
|
|
// this may be useful later for load balancing.
|
|
|
|
#[allow(dead_code)]
|
2017-03-22 19:26:51 +01:00
|
|
|
pub fn cumulative_cost(&self) -> U256 { self.cumulative_cost }
|
2017-01-04 18:00:12 +01:00
|
|
|
}
|
2017-01-04 18:43:11 +01:00
|
|
|
|
2017-03-08 18:27:29 +01:00
|
|
|
// helper to calculate timeout for a specific set of requests.
|
|
|
|
// it's a base amount + some amount per request.
|
|
|
|
fn compute_timeout(reqs: &Requests) -> Duration {
|
2018-03-14 12:29:52 +01:00
|
|
|
Duration::from_millis(reqs.requests().iter().fold(timeout::BASE, |tm, req| {
|
2017-03-08 18:27:29 +01:00
|
|
|
tm + match *req {
|
|
|
|
Request::Headers(_) => timeout::HEADERS,
|
|
|
|
Request::HeaderProof(_) => timeout::HEADER_PROOF,
|
2017-07-03 12:25:10 +02:00
|
|
|
Request::TransactionIndex(_) => timeout::TRANSACTION_INDEX,
|
2017-03-08 18:27:29 +01:00
|
|
|
Request::Receipts(_) => timeout::RECEIPT,
|
|
|
|
Request::Body(_) => timeout::BODY,
|
|
|
|
Request::Account(_) => timeout::PROOF,
|
|
|
|
Request::Storage(_) => timeout::PROOF,
|
|
|
|
Request::Code(_) => timeout::CONTRACT_CODE,
|
|
|
|
Request::Execution(_) => timeout::TRANSACTION_PROOF,
|
2017-09-05 17:54:05 +02:00
|
|
|
Request::Signal(_) => timeout::EPOCH_SIGNAL,
|
2017-03-08 18:27:29 +01:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2017-01-04 18:43:11 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-03-08 18:27:29 +01:00
|
|
|
use net::ReqId;
|
2017-09-24 19:18:17 +02:00
|
|
|
use request::Builder;
|
2018-03-14 12:29:52 +01:00
|
|
|
use std::time::{Instant, Duration};
|
2017-03-08 18:27:29 +01:00
|
|
|
use super::{RequestSet, compute_timeout};
|
2017-01-04 18:43:11 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn multi_timeout() {
|
2018-03-14 12:29:52 +01:00
|
|
|
let test_begin = Instant::now();
|
2017-01-04 18:43:11 +01:00
|
|
|
let mut req_set = RequestSet::default();
|
|
|
|
|
2017-09-24 19:18:17 +02:00
|
|
|
let the_req = Builder::default().build();
|
2017-03-08 18:27:29 +01:00
|
|
|
let req_time = compute_timeout(&the_req);
|
2017-03-22 19:26:51 +01:00
|
|
|
req_set.insert(ReqId(0), the_req.clone(), 0.into(), test_begin);
|
2018-03-14 12:29:52 +01:00
|
|
|
req_set.insert(ReqId(1), the_req, 0.into(), test_begin + Duration::from_secs(1));
|
2017-01-04 18:43:11 +01:00
|
|
|
|
|
|
|
assert_eq!(req_set.base, Some(test_begin));
|
|
|
|
|
2017-03-08 18:27:29 +01:00
|
|
|
let test_end = test_begin + req_time;
|
2017-01-04 18:43:11 +01:00
|
|
|
assert!(req_set.check_timeout(test_end));
|
|
|
|
|
2018-03-14 12:29:52 +01:00
|
|
|
req_set.remove(&ReqId(0), test_begin + Duration::from_secs(1)).unwrap();
|
2017-01-04 18:43:11 +01:00
|
|
|
assert!(!req_set.check_timeout(test_end));
|
2018-03-14 12:29:52 +01:00
|
|
|
assert!(req_set.check_timeout(test_end + Duration::from_secs(1)));
|
2017-01-04 18:43:11 +01:00
|
|
|
}
|
2017-03-22 19:26:51 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn cumulative_cost() {
|
2017-09-24 19:18:17 +02:00
|
|
|
let the_req = Builder::default().build();
|
2018-03-14 12:29:52 +01:00
|
|
|
let test_begin = Instant::now();
|
|
|
|
let test_end = test_begin + Duration::from_secs(1);
|
2017-03-22 19:26:51 +01:00
|
|
|
let mut req_set = RequestSet::default();
|
|
|
|
|
|
|
|
for i in 0..5 {
|
|
|
|
req_set.insert(ReqId(i), the_req.clone(), 1.into(), test_begin);
|
|
|
|
assert_eq!(req_set.cumulative_cost, (i + 1).into());
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in (0..5).rev() {
|
|
|
|
assert!(req_set.remove(&ReqId(i), test_end).is_some());
|
|
|
|
assert_eq!(req_set.cumulative_cost, i.into());
|
|
|
|
}
|
|
|
|
}
|
2017-01-04 18:43:11 +01:00
|
|
|
}
|