Remove direct dependencies on tokio, bytes, reduce lock contention for circuitbreaker

This commit is contained in:
asonix 2020-12-29 11:27:14 -06:00
parent 9923d4d107
commit d7e68190c4
9 changed files with 82 additions and 58 deletions

37
Cargo.lock generated
View file

@ -363,9 +363,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.36" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68803225a7b13e47191bab76f2687382b60d259e8cf37f6e1893658b84bb9479" checksum = "ee67c11feeac938fae061b232e38e0b6d94f97a9df10e6271319325ac4c56a86"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
@ -382,6 +382,16 @@ dependencies = [
"event-listener", "event-listener",
] ]
[[package]]
name = "async-rwlock"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261803dcc39ba9e72760ba6e16d0199b1eef9fc44e81bffabbebb9f5aea3906c"
dependencies = [
"async-mutex",
"event-listener",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.42" version = "0.1.42"
@ -1247,9 +1257,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
@ -1989,10 +1999,10 @@ dependencies = [
"ammonia", "ammonia",
"anyhow", "anyhow",
"async-mutex", "async-mutex",
"async-rwlock",
"async-trait", "async-trait",
"background-jobs", "background-jobs",
"base64 0.13.0", "base64 0.13.0",
"bytes",
"chrono", "chrono",
"config", "config",
"deadpool", "deadpool",
@ -2016,7 +2026,6 @@ dependencies = [
"sha2", "sha2",
"structopt", "structopt",
"thiserror", "thiserror",
"tokio",
"tokio-postgres", "tokio-postgres",
"ttl_cache", "ttl_cache",
"uuid", "uuid",
@ -2241,9 +2250,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.60" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2495,9 +2504,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.55" version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a" checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2547,18 +2556,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.22" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.22" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -21,9 +21,9 @@ activitystreams = "0.7.0-alpha.4"
activitystreams-ext = "0.1.0-alpha.2" activitystreams-ext = "0.1.0-alpha.2"
ammonia = "3.1.0" ammonia = "3.1.0"
async-mutex = "1.0.1" async-mutex = "1.0.1"
async-rwlock = "1.3.0"
async-trait = "0.1.24" async-trait = "0.1.24"
background-jobs = "0.8.0" background-jobs = "0.8.0"
bytes = "0.5.4"
base64 = "0.13" base64 = "0.13"
chrono = "0.4.19" chrono = "0.4.19"
config = "0.10.1" config = "0.10.1"
@ -47,7 +47,6 @@ serde_json = "1.0"
sha2 = "0.9" sha2 = "0.9"
structopt = "0.3.12" structopt = "0.3.12"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "0.2.13", features = ["sync"] }
tokio-postgres = { version = "0.5.1", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] } tokio-postgres = { version = "0.5.1", features = ["with-serde_json-1", "with-uuid-0_8", "with-chrono-0_4"] }
ttl_cache = "0.5.1" ttl_cache = "0.5.1"
uuid = { version = "0.8", features = ["v4", "serde"] } uuid = { version = "0.8", features = ["v4", "serde"] }

View file

@ -1,8 +1,8 @@
use crate::{apub::AcceptedActors, db::Db, error::MyError, requests::Requests}; use crate::{apub::AcceptedActors, db::Db, error::MyError, requests::Requests};
use activitystreams::{prelude::*, uri, url::Url}; use activitystreams::{prelude::*, uri, url::Url};
use async_rwlock::RwLock;
use log::error; use log::error;
use std::{collections::HashSet, sync::Arc, time::Duration}; use std::{collections::HashSet, sync::Arc, time::Duration};
use tokio::sync::RwLock;
use ttl_cache::TtlCache; use ttl_cache::TtlCache;
use uuid::Uuid; use uuid::Uuid;

View file

@ -1,11 +1,11 @@
use crate::{db::Db, error::MyError}; use crate::{db::Db, error::MyError};
use activitystreams::url::Url; use activitystreams::url::Url;
use actix_web::web::Bytes;
use async_mutex::Mutex; use async_mutex::Mutex;
use bytes::Bytes; use async_rwlock::RwLock;
use futures::join; use futures::join;
use lru::LruCache; use lru::LruCache;
use std::{collections::HashMap, sync::Arc, time::Duration}; use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::RwLock;
use ttl_cache::TtlCache; use ttl_cache::TtlCache;
use uuid::Uuid; use uuid::Uuid;

View file

@ -1,12 +1,12 @@
use crate::{db::Db, error::MyError}; use crate::{db::Db, error::MyError};
use activitystreams::{uri, url::Url}; use activitystreams::{uri, url::Url};
use async_rwlock::RwLock;
use log::{debug, error}; use log::{debug, error};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
sync::Arc, sync::Arc,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use tokio::sync::RwLock;
use tokio_postgres::types::Json; use tokio_postgres::types::Json;
use uuid::Uuid; use uuid::Uuid;

View file

@ -11,13 +11,13 @@ use actix_rt::{
time::{interval_at, Instant}, time::{interval_at, Instant},
}; };
use actix_web::web; use actix_web::web;
use async_rwlock::RwLock;
use futures::{join, try_join}; use futures::{join, try_join};
use log::{error, info}; use log::{error, info};
use lru::LruCache; use lru::LruCache;
use rand::thread_rng; use rand::thread_rng;
use rsa::{RSAPrivateKey, RSAPublicKey}; use rsa::{RSAPrivateKey, RSAPublicKey};
use std::{collections::HashSet, sync::Arc, time::Duration}; use std::{collections::HashSet, sync::Arc, time::Duration};
use tokio::sync::RwLock;
#[derive(Clone)] #[derive(Clone)]
pub struct State { pub struct State {

View file

@ -1,16 +1,17 @@
use actix_web::{ use actix_web::{
dev::{Payload, Service, ServiceRequest, Transform}, dev::{Payload, Service, ServiceRequest, Transform},
http::StatusCode, http::StatusCode,
web::BytesMut,
HttpMessage, HttpResponse, ResponseError, HttpMessage, HttpResponse, ResponseError,
}; };
use bytes::BytesMut;
use futures::{ use futures::{
channel::mpsc::channel,
future::{ok, try_join, LocalBoxFuture, Ready}, future::{ok, try_join, LocalBoxFuture, Ready},
sink::SinkExt,
stream::StreamExt, stream::StreamExt,
}; };
use log::{error, info}; use log::{error, info};
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tokio::sync::mpsc::channel;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DebugPayload(pub bool); pub struct DebugPayload(pub bool);
@ -68,7 +69,7 @@ where
fn call(&mut self, mut req: S::Request) -> Self::Future { fn call(&mut self, mut req: S::Request) -> Self::Future {
if self.0 { if self.0 {
let (mut tx, rx) = channel(1); let (mut tx, rx) = channel(0);
let mut pl = req.take_payload(); let mut pl = req.take_payload();
req.set_payload(Payload::Stream(Box::pin(rx))); req.set_payload(Payload::Stream(Box::pin(rx)));

View file

@ -1,7 +1,8 @@
use crate::error::MyError; use crate::error::MyError;
use activitystreams::url::Url; use activitystreams::url::Url;
use actix_web::{client::Client, http::header::Date}; use actix_web::{client::Client, http::header::Date, web::Bytes};
use bytes::Bytes; use async_mutex::Mutex;
use async_rwlock::RwLock;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use http_signature_normalization_actix::prelude::*; use http_signature_normalization_actix::prelude::*;
use log::{debug, info, warn}; use log::{debug, info, warn};
@ -13,14 +14,14 @@ use std::{
rc::Rc, rc::Rc,
sync::{ sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
Arc, Mutex, Arc,
}, },
time::SystemTime, time::SystemTime,
}; };
#[derive(Clone)] #[derive(Clone)]
pub struct Breakers { pub struct Breakers {
inner: Arc<Mutex<HashMap<String, Breaker>>>, inner: Arc<RwLock<HashMap<String, Arc<Mutex<Breaker>>>>>,
} }
impl Breakers { impl Breakers {
@ -28,32 +29,47 @@ impl Breakers {
Self::default() Self::default()
} }
fn should_try(&self, url: &Url) -> bool { async fn should_try(&self, url: &Url) -> bool {
if let Some(domain) = url.domain() { if let Some(domain) = url.domain() {
self.inner if let Some(breaker) = self.inner.read().await.get(domain) {
.lock() breaker.lock().await.should_try()
.expect("Breakers poisoned") } else {
.get(domain) true
.map(|breaker| breaker.should_try()) }
.unwrap_or(true)
} else { } else {
false false
} }
} }
fn fail(&self, url: &Url) { async fn fail(&self, url: &Url) {
if let Some(domain) = url.domain() { if let Some(domain) = url.domain() {
let mut hm = self.inner.lock().expect("Breakers poisoned"); if let Some(breaker) = self.inner.read().await.get(domain) {
let entry = hm.entry(domain.to_owned()).or_insert(Breaker::default()); let owned_breaker = Arc::clone(&breaker);
entry.fail(); drop(breaker);
owned_breaker.lock().await.fail();
} else {
let mut hm = self.inner.write().await;
let breaker = hm
.entry(domain.to_owned())
.or_insert(Arc::new(Mutex::new(Breaker::default())));
breaker.lock().await.fail();
}
} }
} }
fn succeed(&self, url: &Url) { async fn succeed(&self, url: &Url) {
if let Some(domain) = url.domain() { if let Some(domain) = url.domain() {
let mut hm = self.inner.lock().expect("Breakers poisoned"); if let Some(breaker) = self.inner.read().await.get(domain) {
let entry = hm.entry(domain.to_owned()).or_insert(Breaker::default()); let owned_breaker = Arc::clone(&breaker);
entry.succeed(); drop(breaker);
owned_breaker.lock().await.succeed();
} else {
let mut hm = self.inner.write().await;
let breaker = hm
.entry(domain.to_owned())
.or_insert(Arc::new(Mutex::new(Breaker::default())));
breaker.lock().await.succeed();
}
} }
} }
} }
@ -61,7 +77,7 @@ impl Breakers {
impl Default for Breakers { impl Default for Breakers {
fn default() -> Self { fn default() -> Self {
Breakers { Breakers {
inner: Arc::new(Mutex::new(HashMap::new())), inner: Arc::new(RwLock::new(HashMap::new())),
} }
} }
} }
@ -180,7 +196,7 @@ impl Requests {
{ {
let parsed_url = url.parse::<Url>()?; let parsed_url = url.parse::<Url>()?;
if !self.breakers.should_try(&parsed_url) { if !self.breakers.should_try(&parsed_url).await {
return Err(MyError::Breaker); return Err(MyError::Breaker);
} }
@ -202,7 +218,7 @@ impl Requests {
if res.is_err() { if res.is_err() {
self.count_err(); self.count_err();
self.breakers.fail(&parsed_url); self.breakers.fail(&parsed_url).await;
} }
let mut res = res.map_err(|e| MyError::SendRequest(url.to_string(), e.to_string()))?; let mut res = res.map_err(|e| MyError::SendRequest(url.to_string(), e.to_string()))?;
@ -218,12 +234,12 @@ impl Requests {
} }
} }
self.breakers.fail(&parsed_url); self.breakers.fail(&parsed_url).await;
return Err(MyError::Status(url.to_string(), res.status())); return Err(MyError::Status(url.to_string(), res.status()));
} }
self.breakers.succeed(&parsed_url); self.breakers.succeed(&parsed_url).await;
let body = res let body = res
.body() .body()
@ -236,7 +252,7 @@ impl Requests {
pub async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), MyError> { pub async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), MyError> {
let parsed_url = url.parse::<Url>()?; let parsed_url = url.parse::<Url>()?;
if !self.breakers.should_try(&parsed_url) { if !self.breakers.should_try(&parsed_url).await {
return Err(MyError::Breaker); return Err(MyError::Breaker);
} }
@ -258,7 +274,7 @@ impl Requests {
.await; .await;
if res.is_err() { if res.is_err() {
self.breakers.fail(&parsed_url); self.breakers.fail(&parsed_url).await;
self.count_err(); self.count_err();
} }
@ -285,12 +301,12 @@ impl Requests {
} }
} }
self.breakers.fail(&parsed_url); self.breakers.fail(&parsed_url).await;
return Err(MyError::Status(url.to_string(), res.status())); return Err(MyError::Status(url.to_string(), res.status()));
} }
self.breakers.succeed(&parsed_url); self.breakers.succeed(&parsed_url).await;
let bytes = match res.body().limit(1024 * 1024 * 4).await { let bytes = match res.body().limit(1024 * 1024 * 4).await {
Err(e) => { Err(e) => {
@ -306,7 +322,7 @@ impl Requests {
where where
T: serde::ser::Serialize, T: serde::ser::Serialize,
{ {
if !self.breakers.should_try(&inbox) { if !self.breakers.should_try(&inbox).await {
return Err(MyError::Breaker); return Err(MyError::Breaker);
} }
@ -332,7 +348,7 @@ impl Requests {
if res.is_err() { if res.is_err() {
self.count_err(); self.count_err();
self.breakers.fail(&inbox); self.breakers.fail(&inbox).await;
} }
let mut res = res.map_err(|e| MyError::SendRequest(inbox.to_string(), e.to_string()))?; let mut res = res.map_err(|e| MyError::SendRequest(inbox.to_string(), e.to_string()))?;
@ -348,11 +364,11 @@ impl Requests {
} }
} }
self.breakers.fail(&inbox); self.breakers.fail(&inbox).await;
return Err(MyError::Status(inbox.to_string(), res.status())); return Err(MyError::Status(inbox.to_string(), res.status()));
} }
self.breakers.succeed(&inbox); self.breakers.succeed(&inbox).await;
Ok(()) Ok(())
} }

View file

@ -3,7 +3,6 @@ use actix_web::{
http::header::{CacheControl, CacheDirective}, http::header::{CacheControl, CacheDirective},
web, HttpResponse, web, HttpResponse,
}; };
use bytes::Bytes;
use uuid::Uuid; use uuid::Uuid;
pub async fn route( pub async fn route(
@ -30,7 +29,7 @@ pub async fn route(
Ok(HttpResponse::NotFound().finish()) Ok(HttpResponse::NotFound().finish())
} }
fn cached(content_type: String, bytes: Bytes) -> HttpResponse { fn cached(content_type: String, bytes: web::Bytes) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.set(CacheControl(vec![ .set(CacheControl(vec![
CacheDirective::Public, CacheDirective::Public,