forked from mirrors/relay
Move notify to registration-based triggers, store nodes in db
This commit is contained in:
parent
0a43fd3a22
commit
78a359c403
13 changed files with 576 additions and 178 deletions
|
@ -0,0 +1,3 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TRIGGER IF EXISTS nodes_notify ON nodes;
|
||||||
|
DROP FUNCTION IF EXISTS invoke_nodes_trigger();
|
37
migrations/2020-03-25-211630_add-node-notifications/up.sql
Normal file
37
migrations/2020-03-25-211630_add-node-notifications/up.sql
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
CREATE OR REPLACE FUNCTION invoke_nodes_trigger ()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
rec RECORD;
|
||||||
|
channel TEXT;
|
||||||
|
payload TEXT;
|
||||||
|
BEGIN
|
||||||
|
case TG_OP
|
||||||
|
WHEN 'INSERT' THEN
|
||||||
|
rec := NEW;
|
||||||
|
channel := 'new_nodes';
|
||||||
|
payload := NEW.listener_id;
|
||||||
|
WHEN 'UPDATE' THEN
|
||||||
|
rec := NEW;
|
||||||
|
channel := 'new_nodes';
|
||||||
|
payload := NEW.listener_id;
|
||||||
|
WHEN 'DELETE' THEN
|
||||||
|
rec := OLD;
|
||||||
|
channel := 'rm_nodes';
|
||||||
|
payload := OLD.listener_id;
|
||||||
|
ELSE
|
||||||
|
RAISE EXCEPTION 'Unknown TG_OP: "%". Should not occur!', TG_OP;
|
||||||
|
END CASE;
|
||||||
|
|
||||||
|
PERFORM pg_notify(channel, payload::TEXT);
|
||||||
|
RETURN rec;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE TRIGGER nodes_notify
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE
|
||||||
|
ON nodes
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE invoke_nodes_trigger();
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE nodes DROP CONSTRAINT nodes_listener_ids_unique;
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE nodes ADD CONSTRAINT nodes_listener_ids_unique UNIQUE (listener_id);
|
267
src/data/node.rs
267
src/data/node.rs
|
@ -1,21 +1,28 @@
|
||||||
|
use crate::{db::Db, error::MyError};
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
|
use bb8_postgres::tokio_postgres::types::Json;
|
||||||
|
use log::error;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub type ListenersCache = Arc<RwLock<HashSet<XsdAnyUri>>>;
|
pub type ListenersCache = Arc<RwLock<HashSet<XsdAnyUri>>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NodeCache {
|
pub struct NodeCache {
|
||||||
|
db: Db,
|
||||||
listeners: ListenersCache,
|
listeners: ListenersCache,
|
||||||
nodes: Arc<RwLock<HashMap<XsdAnyUri, Node>>>,
|
nodes: Arc<RwLock<HashMap<XsdAnyUri, Node>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeCache {
|
impl NodeCache {
|
||||||
pub fn new(listeners: ListenersCache) -> Self {
|
pub fn new(db: Db, listeners: ListenersCache) -> Self {
|
||||||
NodeCache {
|
NodeCache {
|
||||||
|
db,
|
||||||
listeners,
|
listeners,
|
||||||
nodes: Arc::new(RwLock::new(HashMap::new())),
|
nodes: Arc::new(RwLock::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
|
@ -38,67 +45,247 @@ impl NodeCache {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_info(
|
pub async fn is_nodeinfo_outdated(&self, listener: &XsdAnyUri) -> bool {
|
||||||
&self,
|
let read_guard = self.nodes.read().await;
|
||||||
listener: XsdAnyUri,
|
|
||||||
software: String,
|
let node = match read_guard.get(listener) {
|
||||||
version: String,
|
None => return true,
|
||||||
reg: bool,
|
Some(node) => node,
|
||||||
) {
|
};
|
||||||
if !self.listeners.read().await.contains(&listener) {
|
|
||||||
let mut nodes = self.nodes.write().await;
|
match node.info.as_ref() {
|
||||||
nodes.remove(&listener);
|
Some(nodeinfo) => nodeinfo.outdated(),
|
||||||
return;
|
None => true,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_contact_outdated(&self, listener: &XsdAnyUri) -> bool {
|
||||||
|
let read_guard = self.nodes.read().await;
|
||||||
|
|
||||||
|
let node = match read_guard.get(listener) {
|
||||||
|
None => return true,
|
||||||
|
Some(node) => node,
|
||||||
|
};
|
||||||
|
|
||||||
|
match node.contact.as_ref() {
|
||||||
|
Some(contact) => contact.outdated(),
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_instance_outdated(&self, listener: &XsdAnyUri) -> bool {
|
||||||
|
let read_guard = self.nodes.read().await;
|
||||||
|
|
||||||
|
let node = match read_guard.get(listener) {
|
||||||
|
None => return true,
|
||||||
|
Some(node) => node,
|
||||||
|
};
|
||||||
|
|
||||||
|
match node.instance.as_ref() {
|
||||||
|
Some(instance) => instance.outdated(),
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cache_by_id(&self, id: Uuid) {
|
||||||
|
if let Err(e) = self.do_cache_by_id(id).await {
|
||||||
|
error!("Error loading node into cache, {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bust_by_id(&self, id: Uuid) {
|
||||||
|
if let Err(e) = self.do_bust_by_id(id).await {
|
||||||
|
error!("Error busting node cache, {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_bust_by_id(&self, id: Uuid) -> Result<(), MyError> {
|
||||||
|
let conn = self.db.pool().get().await?;
|
||||||
|
|
||||||
|
let row_opt = conn
|
||||||
|
.query_opt(
|
||||||
|
"SELECT ls.actor_id
|
||||||
|
FROM listeners AS ls
|
||||||
|
INNER JOIN nodes AS nd ON nd.listener_id = ls.id
|
||||||
|
WHERE nd.id = $1::UUID
|
||||||
|
LIMIT 1;",
|
||||||
|
&[&id],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let row = if let Some(row) = row_opt {
|
||||||
|
row
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let listener: String = row.try_get(0)?;
|
||||||
|
let listener: XsdAnyUri = listener.parse()?;
|
||||||
|
|
||||||
|
let mut write_guard = self.nodes.write().await;
|
||||||
|
write_guard.remove(&listener);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_cache_by_id(&self, id: Uuid) -> Result<(), MyError> {
|
||||||
|
let conn = self.db.pool().get().await?;
|
||||||
|
|
||||||
|
let row_opt = conn
|
||||||
|
.query_opt(
|
||||||
|
"SELECT ls.actor_id, nd.nodeinfo, nd.instance, nd.contact
|
||||||
|
FROM nodes AS nd
|
||||||
|
INNER JOIN listeners AS ls ON nd.listener_id = ls.id
|
||||||
|
WHERE nd.id = $1::UUID
|
||||||
|
LIMIT 1;",
|
||||||
|
&[&id],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let row = if let Some(row) = row_opt {
|
||||||
|
row
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let listener: String = row.try_get(0)?;
|
||||||
|
let listener: XsdAnyUri = listener.parse()?;
|
||||||
|
let info: Option<Json<Info>> = row.try_get(1)?;
|
||||||
|
let instance: Option<Json<Instance>> = row.try_get(2)?;
|
||||||
|
let contact: Option<Json<Contact>> = row.try_get(3)?;
|
||||||
|
|
||||||
let mut write_guard = self.nodes.write().await;
|
let mut write_guard = self.nodes.write().await;
|
||||||
let node = write_guard
|
let node = write_guard
|
||||||
.entry(listener.clone())
|
.entry(listener.clone())
|
||||||
.or_insert(Node::new(listener));
|
.or_insert(Node::new(listener));
|
||||||
|
|
||||||
|
if let Some(info) = info {
|
||||||
|
node.info = Some(info.0);
|
||||||
|
}
|
||||||
|
if let Some(instance) = instance {
|
||||||
|
node.instance = Some(instance.0);
|
||||||
|
}
|
||||||
|
if let Some(contact) = contact {
|
||||||
|
node.contact = Some(contact.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_info(
|
||||||
|
&self,
|
||||||
|
listener: &XsdAnyUri,
|
||||||
|
software: String,
|
||||||
|
version: String,
|
||||||
|
reg: bool,
|
||||||
|
) -> Result<(), MyError> {
|
||||||
|
if !self.listeners.read().await.contains(listener) {
|
||||||
|
let mut nodes = self.nodes.write().await;
|
||||||
|
nodes.remove(listener);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write_guard = self.nodes.write().await;
|
||||||
|
let node = write_guard
|
||||||
|
.entry(listener.clone())
|
||||||
|
.or_insert(Node::new(listener.clone()));
|
||||||
node.set_info(software, version, reg);
|
node.set_info(software, version, reg);
|
||||||
|
self.save(listener, &*node).await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_instance(
|
pub async fn set_instance(
|
||||||
&self,
|
&self,
|
||||||
listener: XsdAnyUri,
|
listener: &XsdAnyUri,
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
version: String,
|
version: String,
|
||||||
reg: bool,
|
reg: bool,
|
||||||
requires_approval: bool,
|
requires_approval: bool,
|
||||||
) {
|
) -> Result<(), MyError> {
|
||||||
if !self.listeners.read().await.contains(&listener) {
|
if !self.listeners.read().await.contains(listener) {
|
||||||
let mut nodes = self.nodes.write().await;
|
let mut nodes = self.nodes.write().await;
|
||||||
nodes.remove(&listener);
|
nodes.remove(listener);
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut write_guard = self.nodes.write().await;
|
let mut write_guard = self.nodes.write().await;
|
||||||
let node = write_guard
|
let node = write_guard
|
||||||
.entry(listener.clone())
|
.entry(listener.clone())
|
||||||
.or_insert(Node::new(listener));
|
.or_insert(Node::new(listener.clone()));
|
||||||
node.set_instance(title, description, version, reg, requires_approval);
|
node.set_instance(title, description, version, reg, requires_approval);
|
||||||
|
self.save(listener, &*node).await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_contact(
|
pub async fn set_contact(
|
||||||
&self,
|
&self,
|
||||||
listener: XsdAnyUri,
|
listener: &XsdAnyUri,
|
||||||
username: String,
|
username: String,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
url: XsdAnyUri,
|
url: XsdAnyUri,
|
||||||
avatar: XsdAnyUri,
|
avatar: XsdAnyUri,
|
||||||
) {
|
) -> Result<(), MyError> {
|
||||||
if !self.listeners.read().await.contains(&listener) {
|
if !self.listeners.read().await.contains(listener) {
|
||||||
let mut nodes = self.nodes.write().await;
|
let mut nodes = self.nodes.write().await;
|
||||||
nodes.remove(&listener);
|
nodes.remove(listener);
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut write_guard = self.nodes.write().await;
|
let mut write_guard = self.nodes.write().await;
|
||||||
let node = write_guard
|
let node = write_guard
|
||||||
.entry(listener.clone())
|
.entry(listener.clone())
|
||||||
.or_insert(Node::new(listener));
|
.or_insert(Node::new(listener.clone()));
|
||||||
node.set_contact(username, display_name, url, avatar);
|
node.set_contact(username, display_name, url, avatar);
|
||||||
|
self.save(listener, &*node).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save(&self, listener: &XsdAnyUri, node: &Node) -> Result<(), MyError> {
|
||||||
|
let conn = self.db.pool().get().await?;
|
||||||
|
|
||||||
|
let row_opt = conn
|
||||||
|
.query_opt(
|
||||||
|
"SELECT id FROM listeners WHERE actor_id = $1::TEXT LIMIT 1;",
|
||||||
|
&[&listener.as_str()],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let id: Uuid = if let Some(row) = row_opt {
|
||||||
|
row.try_get(0)?
|
||||||
|
} else {
|
||||||
|
return Err(MyError::NotSubscribed(listener.as_str().to_owned()));
|
||||||
|
};
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO nodes (
|
||||||
|
listener_id,
|
||||||
|
nodeinfo,
|
||||||
|
instance,
|
||||||
|
contact,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1::UUID,
|
||||||
|
$2::JSONB,
|
||||||
|
$3::JSONB,
|
||||||
|
$4::JSONB,
|
||||||
|
'now',
|
||||||
|
'now'
|
||||||
|
) ON CONFLICT (listener_id)
|
||||||
|
DO UPDATE SET
|
||||||
|
nodeinfo = $2::JSONB,
|
||||||
|
instance = $3::JSONB,
|
||||||
|
contact = $4::JSONB;",
|
||||||
|
&[
|
||||||
|
&id,
|
||||||
|
&Json(&node.info),
|
||||||
|
&Json(&node.instance),
|
||||||
|
&Json(&node.contact),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +317,7 @@ impl Node {
|
||||||
software,
|
software,
|
||||||
version,
|
version,
|
||||||
reg,
|
reg,
|
||||||
|
updated: SystemTime::now(),
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -148,6 +336,7 @@ impl Node {
|
||||||
version,
|
version,
|
||||||
reg,
|
reg,
|
||||||
requires_approval,
|
requires_approval,
|
||||||
|
updated: SystemTime::now(),
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -164,31 +353,55 @@ impl Node {
|
||||||
display_name,
|
display_name,
|
||||||
url,
|
url,
|
||||||
avatar,
|
avatar,
|
||||||
|
updated: SystemTime::now(),
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct Info {
|
pub struct Info {
|
||||||
pub software: String,
|
pub software: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub reg: bool,
|
pub reg: bool,
|
||||||
|
pub updated: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub reg: bool,
|
pub reg: bool,
|
||||||
pub requires_approval: bool,
|
pub requires_approval: bool,
|
||||||
|
pub updated: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct Contact {
|
pub struct Contact {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub url: XsdAnyUri,
|
pub url: XsdAnyUri,
|
||||||
pub avatar: XsdAnyUri,
|
pub avatar: XsdAnyUri,
|
||||||
|
pub updated: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
static TEN_MINUTES: Duration = Duration::from_secs(60 * 10);
|
||||||
|
|
||||||
|
impl Info {
|
||||||
|
pub fn outdated(&self) -> bool {
|
||||||
|
self.updated + TEN_MINUTES < SystemTime::now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instance {
|
||||||
|
pub fn outdated(&self) -> bool {
|
||||||
|
self.updated + TEN_MINUTES < SystemTime::now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Contact {
|
||||||
|
pub fn outdated(&self) -> bool {
|
||||||
|
self.updated + TEN_MINUTES < SystemTime::now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@ impl State {
|
||||||
blocks: Arc::new(RwLock::new(blocks)),
|
blocks: Arc::new(RwLock::new(blocks)),
|
||||||
whitelists: Arc::new(RwLock::new(whitelists)),
|
whitelists: Arc::new(RwLock::new(whitelists)),
|
||||||
listeners: listeners.clone(),
|
listeners: listeners.clone(),
|
||||||
node_cache: NodeCache::new(listeners),
|
node_cache: NodeCache::new(db.clone(), listeners),
|
||||||
};
|
};
|
||||||
|
|
||||||
state.spawn_rehydrate(db.clone());
|
state.spawn_rehydrate(db.clone());
|
||||||
|
|
|
@ -136,10 +136,12 @@ pub async fn listen(client: &Client) -> Result<(), Error> {
|
||||||
LISTEN new_whitelists;
|
LISTEN new_whitelists;
|
||||||
LISTEN new_listeners;
|
LISTEN new_listeners;
|
||||||
LISTEN new_actors;
|
LISTEN new_actors;
|
||||||
|
LISTEN new_nodes;
|
||||||
LISTEN rm_blocks;
|
LISTEN rm_blocks;
|
||||||
LISTEN rm_whitelists;
|
LISTEN rm_whitelists;
|
||||||
LISTEN rm_listeners;
|
LISTEN rm_listeners;
|
||||||
LISTEN rm_actors;",
|
LISTEN rm_actors;
|
||||||
|
LISTEN rm_nodes",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::jobs::JobState;
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use background_jobs::{Job, Processor};
|
use background_jobs::{Job, Processor};
|
||||||
|
use futures::join;
|
||||||
use std::{future::Future, pin::Pin};
|
use std::{future::Future, pin::Pin};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
@ -18,6 +19,15 @@ impl QueryInstance {
|
||||||
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
||||||
let listener = self.listener.clone();
|
let listener = self.listener.clone();
|
||||||
|
|
||||||
|
let (o1, o2) = join!(
|
||||||
|
state.node_cache.is_contact_outdated(&listener),
|
||||||
|
state.node_cache.is_instance_outdated(&listener),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !(o1 || o2) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let url = self.listener.as_url_mut();
|
let url = self.listener.as_url_mut();
|
||||||
url.set_fragment(None);
|
url.set_fragment(None);
|
||||||
url.set_query(None);
|
url.set_query(None);
|
||||||
|
@ -38,26 +48,26 @@ impl QueryInstance {
|
||||||
state
|
state
|
||||||
.node_cache
|
.node_cache
|
||||||
.set_contact(
|
.set_contact(
|
||||||
listener.clone(),
|
&listener,
|
||||||
contact.username,
|
contact.username,
|
||||||
contact.display_name,
|
contact.display_name,
|
||||||
contact.url,
|
contact.url,
|
||||||
contact.avatar,
|
contact.avatar,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
.node_cache
|
.node_cache
|
||||||
.set_instance(
|
.set_instance(
|
||||||
listener,
|
&listener,
|
||||||
instance.title,
|
instance.title,
|
||||||
description,
|
description,
|
||||||
instance.version,
|
instance.version,
|
||||||
instance.registrations,
|
instance.registrations,
|
||||||
instance.approval_required,
|
instance.approval_required,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,38 +22,28 @@ use crate::{
|
||||||
},
|
},
|
||||||
requests::Requests,
|
requests::Requests,
|
||||||
};
|
};
|
||||||
use background_jobs::{memory_storage::Storage as MemoryStorage, Job, QueueHandle, WorkerConfig};
|
use background_jobs::{Job, QueueHandle, WorkerConfig};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn create_server(db: Db) -> JobServer {
|
pub fn create_server(db: Db) -> JobServer {
|
||||||
let local = background_jobs::create_server(MemoryStorage::new());
|
|
||||||
let shared = background_jobs::create_server(Storage::new(db));
|
let shared = background_jobs::create_server(Storage::new(db));
|
||||||
|
|
||||||
local.every(Duration::from_secs(60 * 5), Listeners);
|
shared.every(Duration::from_secs(60 * 5), Listeners);
|
||||||
|
|
||||||
JobServer::new(shared, local)
|
JobServer::new(shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_workers(state: State, actors: ActorCache, job_server: JobServer) {
|
pub fn create_workers(state: State, actors: ActorCache, job_server: JobServer) {
|
||||||
let state2 = state.clone();
|
|
||||||
let actors2 = actors.clone();
|
|
||||||
let job_server2 = job_server.clone();
|
|
||||||
|
|
||||||
let remote_handle = job_server.remote.clone();
|
let remote_handle = job_server.remote.clone();
|
||||||
let local_handle = job_server.local.clone();
|
|
||||||
|
|
||||||
WorkerConfig::new(move || JobState::new(state.clone(), actors.clone(), job_server.clone()))
|
WorkerConfig::new(move || JobState::new(state.clone(), actors.clone(), job_server.clone()))
|
||||||
.register(DeliverProcessor)
|
.register(DeliverProcessor)
|
||||||
.register(DeliverManyProcessor)
|
.register(DeliverManyProcessor)
|
||||||
.set_processor_count("default", 4)
|
|
||||||
.start(remote_handle);
|
|
||||||
|
|
||||||
WorkerConfig::new(move || JobState::new(state2.clone(), actors2.clone(), job_server2.clone()))
|
|
||||||
.register(NodeinfoProcessor)
|
.register(NodeinfoProcessor)
|
||||||
.register(InstanceProcessor)
|
.register(InstanceProcessor)
|
||||||
.register(ListenersProcessor)
|
.register(ListenersProcessor)
|
||||||
.set_processor_count("default", 4)
|
.set_processor_count("default", 4)
|
||||||
.start(local_handle);
|
.start(remote_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -68,7 +58,6 @@ pub struct JobState {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct JobServer {
|
pub struct JobServer {
|
||||||
remote: QueueHandle,
|
remote: QueueHandle,
|
||||||
local: QueueHandle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobState {
|
impl JobState {
|
||||||
|
@ -84,10 +73,9 @@ impl JobState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobServer {
|
impl JobServer {
|
||||||
fn new(remote_handle: QueueHandle, local_handle: QueueHandle) -> Self {
|
fn new(remote_handle: QueueHandle) -> Self {
|
||||||
JobServer {
|
JobServer {
|
||||||
remote: remote_handle,
|
remote: remote_handle,
|
||||||
local: local_handle,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,11 +85,4 @@ impl JobServer {
|
||||||
{
|
{
|
||||||
self.remote.queue(job).map_err(MyError::Queue)
|
self.remote.queue(job).map_err(MyError::Queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_local<J>(&self, job: J) -> Result<(), MyError>
|
|
||||||
where
|
|
||||||
J: Job,
|
|
||||||
{
|
|
||||||
self.local.queue(job).map_err(MyError::Queue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,10 @@ impl QueryNodeinfo {
|
||||||
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
||||||
let listener = self.listener.clone();
|
let listener = self.listener.clone();
|
||||||
|
|
||||||
|
if !state.node_cache.is_nodeinfo_outdated(&listener).await {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let url = self.listener.as_url_mut();
|
let url = self.listener.as_url_mut();
|
||||||
url.set_fragment(None);
|
url.set_fragment(None);
|
||||||
url.set_query(None);
|
url.set_query(None);
|
||||||
|
@ -39,12 +43,12 @@ impl QueryNodeinfo {
|
||||||
state
|
state
|
||||||
.node_cache
|
.node_cache
|
||||||
.set_info(
|
.set_info(
|
||||||
listener,
|
&listener,
|
||||||
nodeinfo.software.name,
|
nodeinfo.software.name,
|
||||||
nodeinfo.software.version,
|
nodeinfo.software.version,
|
||||||
nodeinfo.open_registrations,
|
nodeinfo.open_registrations,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ impl Listeners {
|
||||||
for listener in state.state.listeners().await {
|
for listener in state.state.listeners().await {
|
||||||
state
|
state
|
||||||
.job_server
|
.job_server
|
||||||
.queue_local(QueryInstance::new(listener.clone()))?;
|
.queue(QueryInstance::new(listener.clone()))?;
|
||||||
state.job_server.queue_local(QueryNodeinfo::new(listener))?;
|
state.job_server.queue(QueryNodeinfo::new(listener))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -66,7 +66,18 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
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());
|
||||||
|
|
||||||
notify::spawn(state.clone(), actors.clone(), job_server.clone(), &config)?;
|
notify::Notifier::new(config.database_url().parse()?)
|
||||||
|
.register(notify::NewBlocks(state.clone()))
|
||||||
|
.register(notify::NewWhitelists(state.clone()))
|
||||||
|
.register(notify::NewListeners(state.clone(), job_server.clone()))
|
||||||
|
.register(notify::NewActors(actors.clone()))
|
||||||
|
.register(notify::NewNodes(state.node_cache()))
|
||||||
|
.register(notify::RmBlocks(state.clone()))
|
||||||
|
.register(notify::RmWhitelists(state.clone()))
|
||||||
|
.register(notify::RmListeners(state.clone()))
|
||||||
|
.register(notify::RmActors(actors.clone()))
|
||||||
|
.register(notify::RmNodes(state.node_cache()))
|
||||||
|
.start();
|
||||||
|
|
||||||
if args.jobs_only() {
|
if args.jobs_only() {
|
||||||
for _ in 0..num_cpus::get() {
|
for _ in 0..num_cpus::get() {
|
||||||
|
|
365
src/notify.rs
365
src/notify.rs
|
@ -1,131 +1,264 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{ActorCache, State},
|
data::{ActorCache, NodeCache, State},
|
||||||
db::listen,
|
db::listen,
|
||||||
error::MyError,
|
|
||||||
jobs::{JobServer, QueryInstance, QueryNodeinfo},
|
jobs::{JobServer, QueryInstance, QueryNodeinfo},
|
||||||
};
|
};
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
use actix::clock::{delay_for, Duration};
|
use actix::clock::{delay_for, Duration};
|
||||||
use bb8_postgres::tokio_postgres::{tls::NoTls, AsyncMessage, Config, Notification};
|
use bb8_postgres::tokio_postgres::{tls::NoTls, AsyncMessage, Config};
|
||||||
use futures::{
|
use futures::{
|
||||||
future::ready,
|
future::ready,
|
||||||
stream::{poll_fn, StreamExt},
|
stream::{poll_fn, StreamExt},
|
||||||
};
|
};
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use std::sync::Arc;
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
async fn handle_notification(
|
pub trait Listener {
|
||||||
state: State,
|
fn key(&self) -> &str;
|
||||||
actors: ActorCache,
|
|
||||||
job_server: JobServer,
|
fn execute(&self, payload: &str);
|
||||||
notif: Notification,
|
|
||||||
) {
|
|
||||||
match notif.channel() {
|
|
||||||
"new_blocks" => {
|
|
||||||
info!("Caching block of {}", notif.payload());
|
|
||||||
state.cache_block(notif.payload().to_owned()).await;
|
|
||||||
}
|
|
||||||
"new_whitelists" => {
|
|
||||||
info!("Caching whitelist of {}", notif.payload());
|
|
||||||
state.cache_whitelist(notif.payload().to_owned()).await;
|
|
||||||
}
|
|
||||||
"new_listeners" => {
|
|
||||||
if let Ok(uri) = notif.payload().parse::<XsdAnyUri>() {
|
|
||||||
info!("Caching listener {}", uri);
|
|
||||||
state.cache_listener(uri.clone()).await;
|
|
||||||
let _ = job_server.queue_local(QueryInstance::new(uri.clone()));
|
|
||||||
let _ = job_server.queue_local(QueryNodeinfo::new(uri));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"new_actors" => {
|
|
||||||
if let Ok(uri) = notif.payload().parse::<XsdAnyUri>() {
|
|
||||||
info!("Caching follower {}", uri);
|
|
||||||
actors.cache_follower(uri).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rm_blocks" => {
|
|
||||||
info!("Busting block cache for {}", notif.payload());
|
|
||||||
state.bust_block(notif.payload()).await;
|
|
||||||
}
|
|
||||||
"rm_whitelists" => {
|
|
||||||
info!("Busting whitelist cache for {}", notif.payload());
|
|
||||||
state.bust_whitelist(notif.payload()).await;
|
|
||||||
}
|
|
||||||
"rm_listeners" => {
|
|
||||||
if let Ok(uri) = notif.payload().parse::<XsdAnyUri>() {
|
|
||||||
info!("Busting listener cache for {}", uri);
|
|
||||||
state.bust_listener(&uri).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rm_actors" => {
|
|
||||||
if let Ok(uri) = notif.payload().parse::<XsdAnyUri>() {
|
|
||||||
info!("Busting follower cache for {}", uri);
|
|
||||||
actors.bust_follower(&uri).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(
|
pub struct Notifier {
|
||||||
state: State,
|
config: Config,
|
||||||
actors: ActorCache,
|
listeners: HashMap<String, Vec<Box<dyn Listener + Send + Sync + 'static>>>,
|
||||||
job_server: JobServer,
|
}
|
||||||
config: &crate::config::Config,
|
|
||||||
) -> Result<(), MyError> {
|
impl Notifier {
|
||||||
let config: Config = config.database_url().parse()?;
|
pub fn new(config: Config) -> Self {
|
||||||
|
Notifier {
|
||||||
actix::spawn(async move {
|
config,
|
||||||
loop {
|
listeners: HashMap::new(),
|
||||||
let (new_client, mut conn) = match config.connect(NoTls).await {
|
}
|
||||||
Ok((client, conn)) => (client, conn),
|
}
|
||||||
Err(e) => {
|
|
||||||
error!("Error establishing DB Connection, {}", e);
|
pub fn register<L>(mut self, l: L) -> Self
|
||||||
delay_for(Duration::new(5, 0)).await;
|
where
|
||||||
continue;
|
L: Listener + Send + Sync + 'static,
|
||||||
}
|
{
|
||||||
};
|
let v = self
|
||||||
|
.listeners
|
||||||
let client = Arc::new(new_client);
|
.entry(l.key().to_owned())
|
||||||
let new_client = client.clone();
|
.or_insert(Vec::new());
|
||||||
|
v.push(Box::new(l));
|
||||||
actix::spawn(async move {
|
self
|
||||||
if let Err(e) = listen(&new_client).await {
|
}
|
||||||
error!("Error listening for updates, {}", e);
|
|
||||||
}
|
pub fn start(self) {
|
||||||
});
|
actix::spawn(async move {
|
||||||
|
let Notifier { config, listeners } = self;
|
||||||
let mut stream = poll_fn(move |cx| conn.poll_message(cx)).filter_map(|m| match m {
|
|
||||||
Ok(AsyncMessage::Notification(n)) => {
|
loop {
|
||||||
debug!("Handling Notification, {:?}", n);
|
let (new_client, mut conn) = match config.connect(NoTls).await {
|
||||||
ready(Some(n))
|
Ok((client, conn)) => (client, conn),
|
||||||
}
|
Err(e) => {
|
||||||
Ok(AsyncMessage::Notice(e)) => {
|
error!("Error establishing DB Connection, {}", e);
|
||||||
debug!("Handling Notice, {:?}", e);
|
delay_for(Duration::new(5, 0)).await;
|
||||||
ready(None)
|
continue;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
};
|
||||||
debug!("Handling Error, {:?}", e);
|
|
||||||
ready(None)
|
let client = Arc::new(new_client);
|
||||||
}
|
let new_client = client.clone();
|
||||||
_ => {
|
|
||||||
debug!("Handling rest");
|
actix::spawn(async move {
|
||||||
ready(None)
|
if let Err(e) = listen(&new_client).await {
|
||||||
}
|
error!("Error listening for updates, {}", e);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
while let Some(n) = stream.next().await {
|
|
||||||
actix::spawn(handle_notification(
|
let mut stream = poll_fn(move |cx| conn.poll_message(cx)).filter_map(|m| match m {
|
||||||
state.clone(),
|
Ok(AsyncMessage::Notification(n)) => {
|
||||||
actors.clone(),
|
debug!("Handling Notification, {:?}", n);
|
||||||
job_server.clone(),
|
ready(Some(n))
|
||||||
n,
|
}
|
||||||
));
|
Ok(AsyncMessage::Notice(e)) => {
|
||||||
}
|
debug!("Handling Notice, {:?}", e);
|
||||||
|
ready(None)
|
||||||
drop(client);
|
}
|
||||||
warn!("Restarting listener task");
|
Err(e) => {
|
||||||
}
|
debug!("Handling Error, {:?}", e);
|
||||||
});
|
ready(None)
|
||||||
Ok(())
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("Handling rest");
|
||||||
|
ready(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(n) = stream.next().await {
|
||||||
|
if let Some(v) = listeners.get(n.channel()) {
|
||||||
|
for l in v {
|
||||||
|
l.execute(n.payload());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(client);
|
||||||
|
warn!("Restarting listener task");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NewBlocks(pub State);
|
||||||
|
pub struct NewWhitelists(pub State);
|
||||||
|
pub struct NewListeners(pub State, pub JobServer);
|
||||||
|
pub struct NewActors(pub ActorCache);
|
||||||
|
pub struct NewNodes(pub NodeCache);
|
||||||
|
pub struct RmBlocks(pub State);
|
||||||
|
pub struct RmWhitelists(pub State);
|
||||||
|
pub struct RmListeners(pub State);
|
||||||
|
pub struct RmActors(pub ActorCache);
|
||||||
|
pub struct RmNodes(pub NodeCache);
|
||||||
|
|
||||||
|
impl Listener for NewBlocks {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"new_blocks"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
info!("Caching block of {}", payload);
|
||||||
|
let state = self.0.clone();
|
||||||
|
let payload = payload.to_owned();
|
||||||
|
actix::spawn(async move { state.cache_block(payload).await });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for NewWhitelists {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"new_whitelists"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
info!("Caching whitelist of {}", payload);
|
||||||
|
let state = self.0.clone();
|
||||||
|
let payload = payload.to_owned();
|
||||||
|
actix::spawn(async move { state.cache_whitelist(payload.to_owned()).await });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for NewListeners {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"new_listeners"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
if let Ok(uri) = payload.parse::<XsdAnyUri>() {
|
||||||
|
info!("Caching listener {}", uri);
|
||||||
|
let state = self.0.clone();
|
||||||
|
let _ = self.1.queue(QueryInstance::new(uri.clone()));
|
||||||
|
let _ = self.1.queue(QueryNodeinfo::new(uri.clone()));
|
||||||
|
actix::spawn(async move { state.cache_listener(uri).await });
|
||||||
|
} else {
|
||||||
|
warn!("Not caching listener {}, parse error", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for NewActors {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"new_actors"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
if let Ok(uri) = payload.parse::<XsdAnyUri>() {
|
||||||
|
info!("Caching actor {}", uri);
|
||||||
|
let actors = self.0.clone();
|
||||||
|
actix::spawn(async move { actors.cache_follower(uri).await });
|
||||||
|
} else {
|
||||||
|
warn!("Not caching actor {}, parse error", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for NewNodes {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"new_nodes"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
if let Ok(uuid) = payload.parse::<Uuid>() {
|
||||||
|
info!("Caching node {}", uuid);
|
||||||
|
let nodes = self.0.clone();
|
||||||
|
actix::spawn(async move { nodes.cache_by_id(uuid).await });
|
||||||
|
} else {
|
||||||
|
warn!("Not caching node {}, parse error", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for RmBlocks {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"rm_blocks"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
info!("Busting block cache for {}", payload);
|
||||||
|
let state = self.0.clone();
|
||||||
|
let payload = payload.to_owned();
|
||||||
|
actix::spawn(async move { state.bust_block(&payload).await });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for RmWhitelists {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"rm_whitelists"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
info!("Busting whitelist cache for {}", payload);
|
||||||
|
let state = self.0.clone();
|
||||||
|
let payload = payload.to_owned();
|
||||||
|
actix::spawn(async move { state.bust_whitelist(&payload).await });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for RmListeners {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"rm_listeners"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
if let Ok(uri) = payload.parse::<XsdAnyUri>() {
|
||||||
|
info!("Busting listener cache for {}", uri);
|
||||||
|
let state = self.0.clone();
|
||||||
|
actix::spawn(async move { state.bust_listener(&uri).await });
|
||||||
|
} else {
|
||||||
|
warn!("Not busting listener cache for {}", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for RmActors {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"rm_actors"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
if let Ok(uri) = payload.parse::<XsdAnyUri>() {
|
||||||
|
info!("Busting actor cache for {}", uri);
|
||||||
|
let actors = self.0.clone();
|
||||||
|
actix::spawn(async move { actors.bust_follower(&uri).await });
|
||||||
|
} else {
|
||||||
|
warn!("Not busting actor cache for {}", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener for RmNodes {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
"rm_nodes"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self, payload: &str) {
|
||||||
|
if let Ok(uuid) = payload.parse::<Uuid>() {
|
||||||
|
info!("Caching node {}", uuid);
|
||||||
|
let nodes = self.0.clone();
|
||||||
|
actix::spawn(async move { nodes.bust_by_id(uuid).await });
|
||||||
|
} else {
|
||||||
|
warn!("Not caching node {}, parse error", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue