Store media uuid mappings, be mindful of locks

This commit is contained in:
asonix 2020-03-26 13:21:05 -05:00
parent d445177c69
commit f11043e57d
10 changed files with 311 additions and 135 deletions

View file

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE media;

View file

@ -0,0 +1,10 @@
-- Your SQL goes here
CREATE TABLE media (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
media_id UUID UNIQUE NOT NULL,
url TEXT UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
SELECT diesel_manage_updated_at('media');

View file

@ -92,11 +92,15 @@ impl ActorCache {
} }
pub async fn unfollower(&self, actor: &Actor) -> Result<Option<Uuid>, MyError> { pub async fn unfollower(&self, actor: &Actor) -> Result<Option<Uuid>, MyError> {
let conn = self.db.pool().get().await?; let row_opt = self
.db
let row_opt = conn .pool()
.get()
.await?
.query_opt( .query_opt(
"DELETE FROM actors WHERE actor_id = $1::TEXT RETURNING listener_id;", "DELETE FROM actors
WHERE actor_id = $1::TEXT
RETURNING listener_id;",
&[&actor.id.as_str()], &[&actor.id.as_str()],
) )
.await?; .await?;
@ -109,9 +113,14 @@ impl ActorCache {
let listener_id: Uuid = row.try_get(0)?; let listener_id: Uuid = row.try_get(0)?;
let row_opt = conn let row_opt = self
.db
.pool()
.get()
.await?
.query_opt( .query_opt(
"SELECT FROM actors WHERE listener_id = $1::UUID;", "SELECT FROM actors
WHERE listener_id = $1::UUID;",
&[&listener_id], &[&listener_id],
) )
.await?; .await?;
@ -124,9 +133,11 @@ impl ActorCache {
} }
async fn lookup(&self, id: &XsdAnyUri) -> Result<Option<Actor>, MyError> { async fn lookup(&self, id: &XsdAnyUri) -> Result<Option<Actor>, MyError> {
let conn = self.db.pool().get().await?; let row_opt = self
.db
let row_opt = conn .pool()
.get()
.await?
.query_opt( .query_opt(
"SELECT listeners.actor_id, actors.public_key, actors.public_key_id "SELECT listeners.actor_id, actors.public_key, actors.public_key_id
FROM listeners FROM listeners
@ -158,9 +169,11 @@ impl ActorCache {
} }
async fn save(&self, actor: Actor) -> Result<(), MyError> { async fn save(&self, actor: Actor) -> Result<(), MyError> {
let conn = self.db.pool().get().await?; let row_opt = self
.db
let row_opt = conn .pool()
.get()
.await?
.query_opt( .query_opt(
"SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;", "SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;",
&[&actor.inbox.as_str()], &[&actor.inbox.as_str()],
@ -175,14 +188,35 @@ impl ActorCache {
let listener_id: Uuid = row.try_get(0)?; let listener_id: Uuid = row.try_get(0)?;
conn.execute( self.db
"INSERT INTO actors (actor_id, public_key, public_key_id, listener_id, created_at, updated_at) .pool()
VALUES ($1::TEXT, $2::TEXT, $3::TEXT, $4::UUID, 'now', 'now') .get()
ON CONFLICT (actor_id) .await?
DO UPDATE SET public_key = $2::TEXT;", .execute(
&[&actor.id.as_str(), &actor.public_key, &actor.public_key_id.as_str(), &listener_id], "INSERT INTO actors (
) actor_id,
.await?; public_key,
public_key_id,
listener_id,
created_at,
updated_at
) VALUES (
$1::TEXT,
$2::TEXT,
$3::TEXT,
$4::UUID,
'now',
'now'
) ON CONFLICT (actor_id)
DO UPDATE SET public_key = $2::TEXT;",
&[
&actor.id.as_str(),
&actor.public_key,
&actor.public_key_id.as_str(),
&listener_id,
],
)
.await?;
Ok(()) Ok(())
} }
@ -192,15 +226,17 @@ impl ActorCache {
public_key: &str, public_key: &str,
public_key_id: &XsdAnyUri, public_key_id: &XsdAnyUri,
) -> Result<(), MyError> { ) -> Result<(), MyError> {
let conn = self.db.pool().get().await?; self.db
.pool()
conn.execute( .get()
"UPDATE actors .await?
SET public_key = $2::TEXT, public_key_id = $3::TEXT .execute(
WHERE actor_id = $1::TEXT;", "UPDATE actors
&[&id.as_str(), &public_key, &public_key_id.as_str()], SET public_key = $2::TEXT, public_key_id = $3::TEXT
) WHERE actor_id = $1::TEXT;",
.await?; &[&id.as_str(), &public_key, &public_key_id.as_str()],
)
.await?;
Ok(()) Ok(())
} }
@ -223,9 +259,13 @@ impl ActorCache {
} }
async fn rehydrate(&self) -> Result<(), MyError> { async fn rehydrate(&self) -> Result<(), MyError> {
let conn = self.db.pool().get().await?; let rows = self
.db
let rows = conn.query("SELECT actor_id FROM actors;", &[]).await?; .pool()
.get()
.await?
.query("SELECT actor_id FROM actors;", &[])
.await?;
let actor_ids = rows let actor_ids = rows
.into_iter() .into_iter()

View file

@ -1,5 +1,7 @@
use crate::{db::Db, error::MyError};
use activitystreams::primitives::XsdAnyUri; use activitystreams::primitives::XsdAnyUri;
use bytes::Bytes; use bytes::Bytes;
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::{Mutex, RwLock}; use tokio::sync::{Mutex, RwLock};
@ -10,45 +12,154 @@ static MEDIA_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 2);
#[derive(Clone)] #[derive(Clone)]
pub struct Media { pub struct Media {
db: Db,
inverse: Arc<Mutex<HashMap<XsdAnyUri, Uuid>>>, inverse: Arc<Mutex<HashMap<XsdAnyUri, Uuid>>>,
url_cache: Arc<Mutex<LruCache<Uuid, XsdAnyUri>>>, url_cache: Arc<Mutex<LruCache<Uuid, XsdAnyUri>>>,
byte_cache: Arc<RwLock<TtlCache<Uuid, (String, Bytes)>>>, byte_cache: Arc<RwLock<TtlCache<Uuid, (String, Bytes)>>>,
} }
impl Media { impl Media {
pub fn new() -> Self { pub fn new(db: Db) -> Self {
Media { Media {
db,
inverse: Arc::new(Mutex::new(HashMap::new())), inverse: Arc::new(Mutex::new(HashMap::new())),
url_cache: Arc::new(Mutex::new(LruCache::new(128))), url_cache: Arc::new(Mutex::new(LruCache::new(128))),
byte_cache: Arc::new(RwLock::new(TtlCache::new(128))), byte_cache: Arc::new(RwLock::new(TtlCache::new(128))),
} }
} }
pub async fn get_uuid(&self, url: &XsdAnyUri) -> Option<Uuid> { pub async fn get_uuid(&self, url: &XsdAnyUri) -> Result<Option<Uuid>, MyError> {
let uuid = self.inverse.lock().await.get(url).cloned()?; let res = self.inverse.lock().await.get(url).cloned();
let uuid = match res {
Some(uuid) => uuid,
_ => {
let row_opt = self
.db
.pool()
.get()
.await?
.query_opt(
"SELECT media_id
FROM media
WHERE url = $1::TEXT
LIMIT 1;",
&[&url.as_str()],
)
.await?;
if let Some(row) = row_opt {
let uuid: Uuid = row.try_get(0)?;
self.inverse.lock().await.insert(url.clone(), uuid);
uuid
} else {
return Ok(None);
}
}
};
if self.url_cache.lock().await.contains(&uuid) { if self.url_cache.lock().await.contains(&uuid) {
return Some(uuid); return Ok(Some(uuid));
}
let row_opt = self
.db
.pool()
.get()
.await?
.query_opt(
"SELECT id
FROM media
WHERE
url = $1::TEXT
AND
media_id = $2::UUID
LIMIT 1;",
&[&url.as_str(), &uuid],
)
.await?;
if row_opt.is_some() {
self.url_cache.lock().await.put(uuid, url.clone());
return Ok(Some(uuid));
} }
self.inverse.lock().await.remove(url); self.inverse.lock().await.remove(url);
None Ok(None)
} }
pub async fn get_url(&self, uuid: Uuid) -> Option<XsdAnyUri> { pub async fn get_url(&self, uuid: Uuid) -> Result<Option<XsdAnyUri>, MyError> {
self.url_cache.lock().await.get(&uuid).cloned() match self.url_cache.lock().await.get(&uuid).cloned() {
Some(url) => return Ok(Some(url)),
_ => (),
}
let row_opt = self
.db
.pool()
.get()
.await?
.query_opt(
"SELECT url
FROM media
WHERE media_id = $1::UUID
LIMIT 1;",
&[&uuid],
)
.await?;
if let Some(row) = row_opt {
let url: String = row.try_get(0)?;
let url: XsdAnyUri = url.parse()?;
return Ok(Some(url));
}
Ok(None)
} }
pub async fn get_bytes(&self, uuid: Uuid) -> Option<(String, Bytes)> { pub async fn get_bytes(&self, uuid: Uuid) -> Option<(String, Bytes)> {
self.byte_cache.read().await.get(&uuid).cloned() self.byte_cache.read().await.get(&uuid).cloned()
} }
pub async fn store_url(&self, url: &XsdAnyUri) -> Uuid { pub async fn store_url(&self, url: &XsdAnyUri) -> Result<Uuid, MyError> {
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
self.inverse.lock().await.insert(url.clone(), uuid);
self.url_cache.lock().await.put(uuid, url.clone()); let (_, _, res) = join!(
uuid async {
self.inverse.lock().await.insert(url.clone(), uuid);
},
async {
self.url_cache.lock().await.put(uuid, url.clone());
},
async {
self.db
.pool()
.get()
.await?
.execute(
"INSERT INTO media (
media_id,
url,
created_at,
updated_at
) VALUES (
$1::UUID,
$2::TEXT,
'now',
'now'
) ON CONFLICT (media_id)
DO UPDATE SET url = $2::TEXT;",
&[&uuid, &url.as_str()],
)
.await?;
Ok(()) as Result<(), MyError>
}
);
res?;
Ok(uuid)
} }
pub async fn store_bytes(&self, uuid: Uuid, content_type: String, bytes: Bytes) { pub async fn store_bytes(&self, uuid: Uuid, content_type: String, bytes: Bytes) {

View file

@ -118,9 +118,11 @@ impl NodeCache {
} }
async fn do_bust_by_id(&self, id: Uuid) -> Result<(), MyError> { async fn do_bust_by_id(&self, id: Uuid) -> Result<(), MyError> {
let conn = self.db.pool().get().await?; let row_opt = self
.db
let row_opt = conn .pool()
.get()
.await?
.query_opt( .query_opt(
"SELECT ls.actor_id "SELECT ls.actor_id
FROM listeners AS ls FROM listeners AS ls
@ -140,16 +142,17 @@ impl NodeCache {
let listener: String = row.try_get(0)?; let listener: String = row.try_get(0)?;
let listener: XsdAnyUri = listener.parse()?; let listener: XsdAnyUri = listener.parse()?;
let mut write_guard = self.nodes.write().await; self.nodes.write().await.remove(&listener);
write_guard.remove(&listener);
Ok(()) Ok(())
} }
async fn do_cache_by_id(&self, id: Uuid) -> Result<(), MyError> { async fn do_cache_by_id(&self, id: Uuid) -> Result<(), MyError> {
let conn = self.db.pool().get().await?; let row_opt = self
.db
let row_opt = conn .pool()
.get()
.await?
.query_opt( .query_opt(
"SELECT ls.actor_id, nd.nodeinfo, nd.instance, nd.contact "SELECT ls.actor_id, nd.nodeinfo, nd.instance, nd.contact
FROM nodes AS nd FROM nodes AS nd
@ -172,19 +175,21 @@ impl NodeCache {
let instance: Option<Json<Instance>> = row.try_get(2)?; let instance: Option<Json<Instance>> = row.try_get(2)?;
let contact: Option<Json<Contact>> = row.try_get(3)?; let contact: Option<Json<Contact>> = row.try_get(3)?;
let mut write_guard = self.nodes.write().await; {
let node = write_guard let mut write_guard = self.nodes.write().await;
.entry(listener.clone()) let node = write_guard
.or_insert(Node::new(listener)); .entry(listener.clone())
.or_insert(Node::new(listener));
if let Some(info) = info { if let Some(info) = info {
node.info = Some(info.0); node.info = Some(info.0);
} }
if let Some(instance) = instance { if let Some(instance) = instance {
node.instance = Some(instance.0); node.instance = Some(instance.0);
} }
if let Some(contact) = contact { if let Some(contact) = contact {
node.contact = Some(contact.0); node.contact = Some(contact.0);
}
} }
Ok(()) Ok(())
@ -203,12 +208,15 @@ impl NodeCache {
return Ok(()); return Ok(());
} }
let mut write_guard = self.nodes.write().await; let node = {
let node = write_guard let mut write_guard = self.nodes.write().await;
.entry(listener.clone()) let node = write_guard
.or_insert(Node::new(listener.clone())); .entry(listener.clone())
node.set_info(software, version, reg); .or_insert(Node::new(listener.clone()));
self.save(listener, node).await?; node.set_info(software, version, reg);
node.clone()
};
self.save(listener, &node).await?;
Ok(()) Ok(())
} }
@ -227,12 +235,15 @@ impl NodeCache {
return Ok(()); return Ok(());
} }
let mut write_guard = self.nodes.write().await; let node = {
let node = write_guard let mut write_guard = self.nodes.write().await;
.entry(listener.clone()) let node = write_guard
.or_insert(Node::new(listener.clone())); .entry(listener.clone())
node.set_instance(title, description, version, reg, requires_approval); .or_insert(Node::new(listener.clone()));
self.save(listener, node).await?; node.set_instance(title, description, version, reg, requires_approval);
node.clone()
};
self.save(listener, &node).await?;
Ok(()) Ok(())
} }
@ -250,19 +261,24 @@ impl NodeCache {
return Ok(()); return Ok(());
} }
let mut write_guard = self.nodes.write().await; let node = {
let node = write_guard let mut write_guard = self.nodes.write().await;
.entry(listener.clone()) let node = write_guard
.or_insert(Node::new(listener.clone())); .entry(listener.clone())
node.set_contact(username, display_name, url, avatar); .or_insert(Node::new(listener.clone()));
self.save(listener, node).await?; node.set_contact(username, display_name, url, avatar);
node.clone()
};
self.save(listener, &node).await?;
Ok(()) Ok(())
} }
pub async fn save(&self, listener: &XsdAnyUri, node: &Node) -> Result<(), MyError> { pub async fn save(&self, listener: &XsdAnyUri, node: &Node) -> Result<(), MyError> {
let conn = self.db.pool().get().await?; let row_opt = self
.db
let row_opt = conn .pool()
.get()
.await?
.query_opt( .query_opt(
"SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;", "SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;",
&[&listener.as_str()], &[&listener.as_str()],
@ -275,8 +291,12 @@ impl NodeCache {
return Err(MyError::NotSubscribed(listener.as_str().to_owned())); return Err(MyError::NotSubscribed(listener.as_str().to_owned()));
}; };
conn.execute( self.db
"INSERT INTO nodes ( .pool()
.get()
.await?
.execute(
"INSERT INTO nodes (
listener_id, listener_id,
nodeinfo, nodeinfo,
instance, instance,
@ -295,14 +315,14 @@ impl NodeCache {
nodeinfo = $2::JSONB, nodeinfo = $2::JSONB,
instance = $3::JSONB, instance = $3::JSONB,
contact = $4::JSONB;", contact = $4::JSONB;",
&[ &[
&id, &id,
&Json(&node.info), &Json(&node.info),
&Json(&node.instance), &Json(&node.instance),
&Json(&node.contact), &Json(&node.contact),
], ],
) )
.await?; .await?;
Ok(()) Ok(())
} }
} }

View file

@ -47,34 +47,29 @@ impl State {
} }
pub async fn bust_whitelist(&self, whitelist: &str) { pub async fn bust_whitelist(&self, whitelist: &str) {
let mut write_guard = self.whitelists.write().await; self.whitelists.write().await.remove(whitelist);
write_guard.remove(whitelist);
} }
pub async fn bust_block(&self, block: &str) { pub async fn bust_block(&self, block: &str) {
let mut write_guard = self.blocks.write().await; self.blocks.write().await.remove(block);
write_guard.remove(block);
} }
pub async fn bust_listener(&self, inbox: &XsdAnyUri) { pub async fn bust_listener(&self, inbox: &XsdAnyUri) {
let mut write_guard = self.listeners.write().await; self.listeners.write().await.remove(inbox);
write_guard.remove(inbox);
} }
pub async fn listeners(&self) -> Vec<XsdAnyUri> { pub async fn listeners(&self) -> Vec<XsdAnyUri> {
let read_guard = self.listeners.read().await; self.listeners.read().await.iter().cloned().collect()
read_guard.iter().cloned().collect()
} }
pub async fn blocks(&self) -> Vec<String> { pub async fn blocks(&self) -> Vec<String> {
let read_guard = self.blocks.read().await; self.blocks.read().await.iter().cloned().collect()
read_guard.iter().cloned().collect()
} }
pub async fn listeners_without(&self, inbox: &XsdAnyUri, domain: &str) -> Vec<XsdAnyUri> { pub async fn listeners_without(&self, inbox: &XsdAnyUri, domain: &str) -> Vec<XsdAnyUri> {
let read_guard = self.listeners.read().await; self.listeners
.read()
read_guard .await
.iter() .iter()
.filter_map(|listener| { .filter_map(|listener| {
if let Some(dom) = listener.as_url().domain() { if let Some(dom) = listener.as_url().domain() {
@ -94,8 +89,7 @@ impl State {
} }
if let Some(host) = actor_id.as_url().host() { if let Some(host) = actor_id.as_url().host() {
let read_guard = self.whitelists.read().await; self.whitelists.read().await.contains(&host.to_string());
return read_guard.contains(&host.to_string());
} }
false false
@ -103,43 +97,34 @@ impl State {
pub async fn is_blocked(&self, actor_id: &XsdAnyUri) -> bool { pub async fn is_blocked(&self, actor_id: &XsdAnyUri) -> bool {
if let Some(host) = actor_id.as_url().host() { if let Some(host) = actor_id.as_url().host() {
let read_guard = self.blocks.read().await; self.blocks.read().await.contains(&host.to_string());
return read_guard.contains(&host.to_string());
} }
true true
} }
pub async fn is_listener(&self, actor_id: &XsdAnyUri) -> bool { pub async fn is_listener(&self, actor_id: &XsdAnyUri) -> bool {
let read_guard = self.listeners.read().await; self.listeners.read().await.contains(actor_id)
read_guard.contains(actor_id)
} }
pub async fn is_cached(&self, object_id: &XsdAnyUri) -> bool { pub async fn is_cached(&self, object_id: &XsdAnyUri) -> bool {
let cache = self.actor_id_cache.clone(); self.actor_id_cache.read().await.contains(object_id)
let read_guard = cache.read().await;
read_guard.contains(object_id)
} }
pub async fn cache(&self, object_id: XsdAnyUri, actor_id: XsdAnyUri) { pub async fn cache(&self, object_id: XsdAnyUri, actor_id: XsdAnyUri) {
let mut write_guard = self.actor_id_cache.write().await; self.actor_id_cache.write().await.put(object_id, actor_id);
write_guard.put(object_id, actor_id);
} }
pub async fn cache_block(&self, host: String) { pub async fn cache_block(&self, host: String) {
let mut write_guard = self.blocks.write().await; self.blocks.write().await.insert(host);
write_guard.insert(host);
} }
pub async fn cache_whitelist(&self, host: String) { pub async fn cache_whitelist(&self, host: String) {
let mut write_guard = self.whitelists.write().await; self.whitelists.write().await.insert(host);
write_guard.insert(host);
} }
pub async fn cache_listener(&self, listener: XsdAnyUri) { pub async fn cache_listener(&self, listener: XsdAnyUri) {
let mut write_guard = self.listeners.write().await; self.listeners.write().await.insert(listener);
write_guard.insert(listener);
} }
pub async fn rehydrate(&self, db: &Db) -> Result<(), MyError> { pub async fn rehydrate(&self, db: &Db) -> Result<(), MyError> {
@ -151,16 +136,13 @@ impl State {
join!( join!(
async move { async move {
let mut write_guard = self.listeners.write().await; *self.listeners.write().await = listeners;
*write_guard = listeners;
}, },
async move { async move {
let mut write_guard = self.whitelists.write().await; *self.whitelists.write().await = whitelists;
*write_guard = whitelists;
}, },
async move { async move {
let mut write_guard = self.blocks.write().await; *self.blocks.write().await = blocks;
*write_guard = blocks;
} }
); );

View file

@ -45,10 +45,10 @@ impl QueryInstance {
}; };
if let Some(mut contact) = instance.contact { if let Some(mut contact) = instance.contact {
if let Some(uuid) = state.media.get_uuid(&contact.avatar).await { if let Some(uuid) = state.media.get_uuid(&contact.avatar).await? {
contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?; contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?;
} else { } else {
let uuid = state.media.store_url(&contact.avatar).await; let uuid = state.media.store_url(&contact.avatar).await?;
contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?; contact.avatar = state.config.generate_url(UrlKind::Media(uuid)).parse()?;
} }

View file

@ -68,7 +68,7 @@ async fn main() -> Result<(), anyhow::Error> {
return Ok(()); return Ok(());
} }
let media = Media::new(); let media = Media::new(db.clone());
let state = State::hydrate(config.clone(), &db).await?; let state = State::hydrate(config.clone(), &db).await?;
let actors = ActorCache::new(db.clone()); let actors = ActorCache::new(db.clone());
let job_server = create_server(db.clone()); let job_server = create_server(db.clone());

View file

@ -13,7 +13,7 @@ pub async fn route(
return Ok(HttpResponse::Ok().content_type(content_type).body(bytes)); return Ok(HttpResponse::Ok().content_type(content_type).body(bytes));
} }
if let Some(url) = media.get_url(uuid).await { if let Some(url) = media.get_url(uuid).await? {
let (content_type, bytes) = requests.fetch_bytes(url.as_str()).await?; let (content_type, bytes) = requests.fetch_bytes(url.as_str()).await?;
media media

View file

@ -43,6 +43,16 @@ table! {
} }
} }
table! {
media (id) {
id -> Uuid,
media_id -> Uuid,
url -> Text,
created_at -> Timestamp,
updated_at -> Timestamp,
}
}
table! { table! {
nodes (id) { nodes (id) {
id -> Uuid, id -> Uuid,
@ -82,6 +92,7 @@ allow_tables_to_appear_in_same_query!(
blocks, blocks,
jobs, jobs,
listeners, listeners,
media,
nodes, nodes,
settings, settings,
whitelists, whitelists,