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 bb8_postgres::tokio_postgres::types::Json;
|
||||
use log::error;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub type ListenersCache = Arc<RwLock<HashSet<XsdAnyUri>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeCache {
|
||||
db: Db,
|
||||
listeners: ListenersCache,
|
||||
nodes: Arc<RwLock<HashMap<XsdAnyUri, Node>>>,
|
||||
}
|
||||
|
||||
impl NodeCache {
|
||||
pub fn new(listeners: ListenersCache) -> Self {
|
||||
pub fn new(db: Db, listeners: ListenersCache) -> Self {
|
||||
NodeCache {
|
||||
db,
|
||||
listeners,
|
||||
nodes: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
|
@ -38,67 +45,247 @@ impl NodeCache {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub async fn set_info(
|
||||
&self,
|
||||
listener: XsdAnyUri,
|
||||
software: String,
|
||||
version: String,
|
||||
reg: bool,
|
||||
) {
|
||||
if !self.listeners.read().await.contains(&listener) {
|
||||
let mut nodes = self.nodes.write().await;
|
||||
nodes.remove(&listener);
|
||||
return;
|
||||
pub async fn is_nodeinfo_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.info.as_ref() {
|
||||
Some(nodeinfo) => nodeinfo.outdated(),
|
||||
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 node = write_guard
|
||||
.entry(listener.clone())
|
||||
.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);
|
||||
self.save(listener, &*node).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_instance(
|
||||
&self,
|
||||
listener: XsdAnyUri,
|
||||
listener: &XsdAnyUri,
|
||||
title: String,
|
||||
description: String,
|
||||
version: String,
|
||||
reg: bool,
|
||||
requires_approval: bool,
|
||||
) {
|
||||
if !self.listeners.read().await.contains(&listener) {
|
||||
) -> Result<(), MyError> {
|
||||
if !self.listeners.read().await.contains(listener) {
|
||||
let mut nodes = self.nodes.write().await;
|
||||
nodes.remove(&listener);
|
||||
return;
|
||||
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));
|
||||
.or_insert(Node::new(listener.clone()));
|
||||
node.set_instance(title, description, version, reg, requires_approval);
|
||||
self.save(listener, &*node).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_contact(
|
||||
&self,
|
||||
listener: XsdAnyUri,
|
||||
listener: &XsdAnyUri,
|
||||
username: String,
|
||||
display_name: String,
|
||||
url: XsdAnyUri,
|
||||
avatar: XsdAnyUri,
|
||||
) {
|
||||
if !self.listeners.read().await.contains(&listener) {
|
||||
) -> Result<(), MyError> {
|
||||
if !self.listeners.read().await.contains(listener) {
|
||||
let mut nodes = self.nodes.write().await;
|
||||
nodes.remove(&listener);
|
||||
return;
|
||||
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));
|
||||
.or_insert(Node::new(listener.clone()));
|
||||
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,
|
||||
version,
|
||||
reg,
|
||||
updated: SystemTime::now(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
@ -148,6 +336,7 @@ impl Node {
|
|||
version,
|
||||
reg,
|
||||
requires_approval,
|
||||
updated: SystemTime::now(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
@ -164,31 +353,55 @@ impl Node {
|
|||
display_name,
|
||||
url,
|
||||
avatar,
|
||||
updated: SystemTime::now(),
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Info {
|
||||
pub software: String,
|
||||
pub version: String,
|
||||
pub reg: bool,
|
||||
pub updated: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Instance {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub version: String,
|
||||
pub reg: bool,
|
||||
pub requires_approval: bool,
|
||||
pub updated: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Contact {
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub url: 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)),
|
||||
whitelists: Arc::new(RwLock::new(whitelists)),
|
||||
listeners: listeners.clone(),
|
||||
node_cache: NodeCache::new(listeners),
|
||||
node_cache: NodeCache::new(db.clone(), listeners),
|
||||
};
|
||||
|
||||
state.spawn_rehydrate(db.clone());
|
||||
|
|
|
@ -136,10 +136,12 @@ pub async fn listen(client: &Client) -> Result<(), Error> {
|
|||
LISTEN new_whitelists;
|
||||
LISTEN new_listeners;
|
||||
LISTEN new_actors;
|
||||
LISTEN new_nodes;
|
||||
LISTEN rm_blocks;
|
||||
LISTEN rm_whitelists;
|
||||
LISTEN rm_listeners;
|
||||
LISTEN rm_actors;",
|
||||
LISTEN rm_actors;
|
||||
LISTEN rm_nodes",
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::jobs::JobState;
|
|||
use activitystreams::primitives::XsdAnyUri;
|
||||
use anyhow::Error;
|
||||
use background_jobs::{Job, Processor};
|
||||
use futures::join;
|
||||
use std::{future::Future, pin::Pin};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
|
@ -18,6 +19,15 @@ impl QueryInstance {
|
|||
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
||||
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();
|
||||
url.set_fragment(None);
|
||||
url.set_query(None);
|
||||
|
@ -38,26 +48,26 @@ impl QueryInstance {
|
|||
state
|
||||
.node_cache
|
||||
.set_contact(
|
||||
listener.clone(),
|
||||
&listener,
|
||||
contact.username,
|
||||
contact.display_name,
|
||||
contact.url,
|
||||
contact.avatar,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
}
|
||||
|
||||
state
|
||||
.node_cache
|
||||
.set_instance(
|
||||
listener,
|
||||
&listener,
|
||||
instance.title,
|
||||
description,
|
||||
instance.version,
|
||||
instance.registrations,
|
||||
instance.approval_required,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -22,38 +22,28 @@ use crate::{
|
|||
},
|
||||
requests::Requests,
|
||||
};
|
||||
use background_jobs::{memory_storage::Storage as MemoryStorage, Job, QueueHandle, WorkerConfig};
|
||||
use background_jobs::{Job, QueueHandle, WorkerConfig};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn create_server(db: Db) -> JobServer {
|
||||
let local = background_jobs::create_server(MemoryStorage::new());
|
||||
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) {
|
||||
let state2 = state.clone();
|
||||
let actors2 = actors.clone();
|
||||
let job_server2 = job_server.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()))
|
||||
.register(DeliverProcessor)
|
||||
.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(InstanceProcessor)
|
||||
.register(ListenersProcessor)
|
||||
.set_processor_count("default", 4)
|
||||
.start(local_handle);
|
||||
.start(remote_handle);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -68,7 +58,6 @@ pub struct JobState {
|
|||
#[derive(Clone)]
|
||||
pub struct JobServer {
|
||||
remote: QueueHandle,
|
||||
local: QueueHandle,
|
||||
}
|
||||
|
||||
impl JobState {
|
||||
|
@ -84,10 +73,9 @@ impl JobState {
|
|||
}
|
||||
|
||||
impl JobServer {
|
||||
fn new(remote_handle: QueueHandle, local_handle: QueueHandle) -> Self {
|
||||
fn new(remote_handle: QueueHandle) -> Self {
|
||||
JobServer {
|
||||
remote: remote_handle,
|
||||
local: local_handle,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,11 +85,4 @@ impl JobServer {
|
|||
{
|
||||
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> {
|
||||
let listener = self.listener.clone();
|
||||
|
||||
if !state.node_cache.is_nodeinfo_outdated(&listener).await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let url = self.listener.as_url_mut();
|
||||
url.set_fragment(None);
|
||||
url.set_query(None);
|
||||
|
@ -39,12 +43,12 @@ impl QueryNodeinfo {
|
|||
state
|
||||
.node_cache
|
||||
.set_info(
|
||||
listener,
|
||||
&listener,
|
||||
nodeinfo.software.name,
|
||||
nodeinfo.software.version,
|
||||
nodeinfo.open_registrations,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ impl Listeners {
|
|||
for listener in state.state.listeners().await {
|
||||
state
|
||||
.job_server
|
||||
.queue_local(QueryInstance::new(listener.clone()))?;
|
||||
state.job_server.queue_local(QueryNodeinfo::new(listener))?;
|
||||
.queue(QueryInstance::new(listener.clone()))?;
|
||||
state.job_server.queue(QueryNodeinfo::new(listener))?;
|
||||
}
|
||||
|
||||
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 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() {
|
||||
for _ in 0..num_cpus::get() {
|
||||
|
|
365
src/notify.rs
365
src/notify.rs
|
@ -1,131 +1,264 @@
|
|||
use crate::{
|
||||
data::{ActorCache, State},
|
||||
data::{ActorCache, NodeCache, State},
|
||||
db::listen,
|
||||
error::MyError,
|
||||
jobs::{JobServer, QueryInstance, QueryNodeinfo},
|
||||
};
|
||||
use activitystreams::primitives::XsdAnyUri;
|
||||
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::{
|
||||
future::ready,
|
||||
stream::{poll_fn, StreamExt},
|
||||
};
|
||||
use log::{debug, error, info, warn};
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn handle_notification(
|
||||
state: State,
|
||||
actors: ActorCache,
|
||||
job_server: JobServer,
|
||||
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 trait Listener {
|
||||
fn key(&self) -> &str;
|
||||
|
||||
fn execute(&self, payload: &str);
|
||||
}
|
||||
|
||||
pub fn spawn(
|
||||
state: State,
|
||||
actors: ActorCache,
|
||||
job_server: JobServer,
|
||||
config: &crate::config::Config,
|
||||
) -> Result<(), MyError> {
|
||||
let config: Config = config.database_url().parse()?;
|
||||
|
||||
actix::spawn(async move {
|
||||
loop {
|
||||
let (new_client, mut conn) = match config.connect(NoTls).await {
|
||||
Ok((client, conn)) => (client, conn),
|
||||
Err(e) => {
|
||||
error!("Error establishing DB Connection, {}", e);
|
||||
delay_for(Duration::new(5, 0)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let client = Arc::new(new_client);
|
||||
let new_client = client.clone();
|
||||
|
||||
actix::spawn(async move {
|
||||
if let Err(e) = listen(&new_client).await {
|
||||
error!("Error listening for updates, {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let mut stream = poll_fn(move |cx| conn.poll_message(cx)).filter_map(|m| match m {
|
||||
Ok(AsyncMessage::Notification(n)) => {
|
||||
debug!("Handling Notification, {:?}", n);
|
||||
ready(Some(n))
|
||||
}
|
||||
Ok(AsyncMessage::Notice(e)) => {
|
||||
debug!("Handling Notice, {:?}", e);
|
||||
ready(None)
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Handling Error, {:?}", e);
|
||||
ready(None)
|
||||
}
|
||||
_ => {
|
||||
debug!("Handling rest");
|
||||
ready(None)
|
||||
}
|
||||
});
|
||||
|
||||
while let Some(n) = stream.next().await {
|
||||
actix::spawn(handle_notification(
|
||||
state.clone(),
|
||||
actors.clone(),
|
||||
job_server.clone(),
|
||||
n,
|
||||
));
|
||||
}
|
||||
|
||||
drop(client);
|
||||
warn!("Restarting listener task");
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
pub struct Notifier {
|
||||
config: Config,
|
||||
listeners: HashMap<String, Vec<Box<dyn Listener + Send + Sync + 'static>>>,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new(config: Config) -> Self {
|
||||
Notifier {
|
||||
config,
|
||||
listeners: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<L>(mut self, l: L) -> Self
|
||||
where
|
||||
L: Listener + Send + Sync + 'static,
|
||||
{
|
||||
let v = self
|
||||
.listeners
|
||||
.entry(l.key().to_owned())
|
||||
.or_insert(Vec::new());
|
||||
v.push(Box::new(l));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn start(self) {
|
||||
actix::spawn(async move {
|
||||
let Notifier { config, listeners } = self;
|
||||
|
||||
loop {
|
||||
let (new_client, mut conn) = match config.connect(NoTls).await {
|
||||
Ok((client, conn)) => (client, conn),
|
||||
Err(e) => {
|
||||
error!("Error establishing DB Connection, {}", e);
|
||||
delay_for(Duration::new(5, 0)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let client = Arc::new(new_client);
|
||||
let new_client = client.clone();
|
||||
|
||||
actix::spawn(async move {
|
||||
if let Err(e) = listen(&new_client).await {
|
||||
error!("Error listening for updates, {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let mut stream = poll_fn(move |cx| conn.poll_message(cx)).filter_map(|m| match m {
|
||||
Ok(AsyncMessage::Notification(n)) => {
|
||||
debug!("Handling Notification, {:?}", n);
|
||||
ready(Some(n))
|
||||
}
|
||||
Ok(AsyncMessage::Notice(e)) => {
|
||||
debug!("Handling Notice, {:?}", e);
|
||||
ready(None)
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Handling Error, {:?}", e);
|
||||
ready(None)
|
||||
}
|
||||
_ => {
|
||||
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