fix/hosepipe

This commit is contained in:
Zomatree
2026-03-05 04:24:54 +00:00
parent 24d0d2b726
commit c5844b6281
10 changed files with 215 additions and 67 deletions

2
Cargo.lock generated
View File

@@ -6838,8 +6838,10 @@ dependencies = [
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.11.5" version = "0.11.5"
dependencies = [ dependencies = [
"amqprs",
"async-channel 2.5.0", "async-channel 2.5.0",
"async-std", "async-std",
"async-trait",
"async-tungstenite", "async-tungstenite",
"authifier", "authifier",
"bincode", "bincode",

View File

@@ -36,6 +36,7 @@ async-std = { version = "1.8.0", features = [
"tokio02", "tokio02",
"attributes", "attributes",
] } ] }
async-trait = "0.1.89"
# core # core
authifier = { version = "1.0.16" } authifier = { version = "1.0.16" }
@@ -48,3 +49,6 @@ revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis # redis
fred = { version = "8.0.1", features = ["subscriber-client"] } fred = { version = "8.0.1", features = ["subscriber-client"] }
# Redis
amqprs = { version = "1.7.0" }

View File

@@ -1,6 +1,18 @@
use std::env; use std::env;
use amqprs::{
channel::{
BasicConsumeArguments, Channel, ExchangeDeclareArguments, QueueBindArguments,
QueueDeclareArguments,
},
connection::{Connection, OpenConnectionArguments},
consumer::AsyncConsumer,
BasicProperties, Deliver,
};
use async_std::net::TcpListener; use async_std::net::TcpListener;
use async_trait::async_trait;
use redis_kiss::AsyncCommands;
use revolt_database::util::rabbit::set_rabbitmq_connection;
use revolt_presence::clear_region; use revolt_presence::clear_region;
#[macro_use] #[macro_use]
@@ -31,6 +43,53 @@ async fn main() {
let try_socket = TcpListener::bind(bind).await; let try_socket = TcpListener::bind(bind).await;
let listener = try_socket.expect("Failed to bind"); let listener = try_socket.expect("Failed to bind");
let config = revolt_config::config().await;
let rmq_conn = Connection::open(&OpenConnectionArguments::new(
&config.rabbit.host,
config.rabbit.port,
&config.rabbit.username,
&config.rabbit.password,
))
.await
.expect("Failed to connect to RabbitMQ");
set_rabbitmq_connection(rmq_conn.clone());
let channel = rmq_conn
.open_channel(None)
.await
.expect("Failed to open RabbitMQ channel.");
channel
.exchange_declare(
ExchangeDeclareArguments::new("events", "fanout")
.durable(true)
.finish(),
)
.await
.expect("wires");
channel
.queue_declare(QueueDeclareArguments::new("events").durable(true).finish())
.await
.expect("wires 2");
channel
.queue_bind(QueueBindArguments::new("events", "events", "events"))
.await
.expect("wires 3");
channel
.basic_consume(
RabbitToRedisConsumer,
BasicConsumeArguments::new("events", "")
.manual_ack(false)
.finish(),
)
.await
.expect("wires 4");
// Start accepting new connections and spawn a client for each connection. // Start accepting new connections and spawn a client for each connection.
while let Ok((stream, addr)) = listener.accept().await { while let Ok((stream, addr)) = listener.accept().await {
async_std::task::spawn(async move { async_std::task::spawn(async move {
@@ -40,3 +99,32 @@ async fn main() {
}); });
} }
} }
struct RabbitToRedisConsumer;
#[async_trait]
impl AsyncConsumer for RabbitToRedisConsumer {
async fn consume(
&mut self,
_channel: &Channel,
_deliver: Deliver,
basic_properties: BasicProperties,
content: Vec<u8>,
) {
let mut redis_conn = redis_kiss::get_connection()
.await
.expect("Failed to connect to Redis.");
let pubsub_channel = basic_properties
.headers()
.expect("No headers")
.get(&"c".try_into().unwrap())
.expect("No channel header")
.to_string();
redis_conn
.publish(pubsub_channel, content)
.await
.expect("failed to publish")
}
}

View File

@@ -464,30 +464,30 @@ async fn worker(
match payload { match payload {
// disabled events // disabled events
// ClientMessage::BeginTyping { channel } => { ClientMessage::BeginTyping { channel } => {
// if !subscribed.read().await.contains(&channel) { if !subscribed.read().await.contains(&channel) {
// continue; continue;
// } }
// EventV1::ChannelStartTyping { EventV1::ChannelStartTyping {
// id: channel.clone(), id: channel.clone(),
// user: user_id.clone(), user: user_id.clone(),
// } }
// .p(channel.clone()) .p(channel.clone())
// .await; .await;
// } }
// ClientMessage::EndTyping { channel } => { ClientMessage::EndTyping { channel } => {
// if !subscribed.read().await.contains(&channel) { if !subscribed.read().await.contains(&channel) {
// continue; continue;
// } }
// EventV1::ChannelStopTyping { EventV1::ChannelStopTyping {
// id: channel.clone(), id: channel.clone(),
// user: user_id.clone(), user: user_id.clone(),
// } }
// .p(channel.clone()) .p(channel.clone())
// .await; .await;
// } }
ClientMessage::Subscribe { server_id } => { ClientMessage::Subscribe { server_id } => {
let mut servers = active_servers.lock().await; let mut servers = active_servers.lock().await;
let has_item = servers.contains_key(&server_id); let has_item = servers.contains_key(&server_id);

View File

@@ -11,6 +11,8 @@ use revolt_presence::filter_online;
use serde_json::to_string; use serde_json::to_string;
// TODO: move away from storing Connection and Channel to using thread local singletons - #659
#[derive(Clone)] #[derive(Clone)]
pub struct AMQP { pub struct AMQP {
#[allow(unused)] #[allow(unused)]

View File

@@ -1,12 +1,20 @@
use amqprs::{
channel::{BasicPublishArguments},
BasicProperties, FieldTable,
};
use authifier::AuthifierEvent; use authifier::AuthifierEvent;
use revolt_result::Error; use revolt_result::Error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use revolt_models::v0::{ use revolt_models::v0::{
AppendMessage, Channel, ChannelUnread, ChannelVoiceState, Emoji, FieldsChannel, FieldsMember, FieldsMessage, FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, Member, MemberCompositeKey, Message, PartialChannel, PartialMember, PartialMessage, PartialRole, PartialServer, PartialUser, PartialUserVoiceState, PartialWebhook, PolicyChange, RemovalIntention, Report, Server, User, UserSettings, UserVoiceState, Webhook AppendMessage, Channel, ChannelUnread, ChannelVoiceState, Emoji, FieldsChannel, FieldsMember,
FieldsMessage, FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, Member, MemberCompositeKey,
Message, PartialChannel, PartialMember, PartialMessage, PartialRole, PartialServer,
PartialUser, PartialUserVoiceState, PartialWebhook, PolicyChange, RemovalIntention, Report,
Server, User, UserSettings, UserVoiceState, Webhook,
}; };
use crate::Database; use crate::{util::rabbit::get_channel, Database};
/// Ping Packet /// Ping Packet
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -304,14 +312,23 @@ pub enum EventV1 {
impl EventV1 { impl EventV1 {
/// Publish helper wrapper /// Publish helper wrapper
pub async fn p(self, channel: String) { pub async fn p(self, channel: String) {
#[cfg(not(debug_assertions))]
redis_kiss::p(channel, self).await;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
info!("Publishing event to {channel}: {self:?}"); info!("Publishing event to {channel}: {self:?}");
#[cfg(debug_assertions)] let rmq = get_channel().await;
redis_kiss::publish(channel, self).await.unwrap();
let mut headers = FieldTable::new();
headers.insert("c".try_into().unwrap(), channel.clone().into());
let mut properties = BasicProperties::default();
properties.with_headers(headers);
rmq.basic_publish(
properties,
serde_json::to_string(&self).unwrap().into_bytes(),
BasicPublishArguments::new("events", "events"),
)
.await
.unwrap();
} }
/// Publish user event /// Publish user event

View File

@@ -5,5 +5,6 @@ pub mod idempotency;
pub mod permissions; pub mod permissions;
pub mod reference; pub mod reference;
pub mod test_fixtures; pub mod test_fixtures;
pub mod rabbit;
pub use funcs::*; pub use funcs::*;

View File

@@ -0,0 +1,48 @@
use amqprs::{channel::Channel, connection::Connection};
use once_cell::sync::OnceCell;
use std::{
collections::HashMap, future::ready, sync::{LazyLock, RwLock}, thread::{ThreadId, current}
};
static RABBIT_CONNECTION: OnceCell<Connection> = OnceCell::new();
static RABBIT_CHANNELS: LazyLock<RwLock<HashMap<ThreadId, Channel>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub async fn get_channel_with_init<F: AsyncFnOnce(Channel) -> Channel>(init: F) -> Channel {
let conn = RABBIT_CONNECTION
.get()
.expect("Rabbit connection is not initialised.");
let thread_id = current().id();
let channel = RABBIT_CHANNELS
.read()
.expect("Channels poisioned")
.get(&thread_id)
.cloned();
if let Some(channel) = channel {
channel
} else {
let mut channel =
conn.open_channel(None)
.await
.expect("Failed to open rabbitmq channel");
channel = init(channel).await;
RABBIT_CHANNELS
.write()
.expect("Channels poisioned")
.insert(thread_id, channel.clone());
channel
}
}
pub async fn get_channel() -> Channel {
get_channel_with_init(ready).await
}
pub fn set_rabbitmq_connection(connection: Connection) -> bool {
RABBIT_CONNECTION.set(connection).is_ok()
}

View File

@@ -9,8 +9,7 @@ pub mod routes;
pub mod util; pub mod util;
use revolt_config::config; use revolt_config::config;
use revolt_database::events::client::EventV1; use revolt_database::{AMQP, util::rabbit::{get_channel, get_channel_with_init, set_rabbitmq_connection}};
use revolt_database::AMQP;
use revolt_ratelimits::rocket as ratelimiter; use revolt_ratelimits::rocket as ratelimiter;
use rocket::{Build, Rocket}; use rocket::{Build, Rocket};
use rocket_cors::{AllowedOrigins, CorsOptions}; use rocket_cors::{AllowedOrigins, CorsOptions};
@@ -19,11 +18,9 @@ use std::net::Ipv4Addr;
use std::str::FromStr; use std::str::FromStr;
use amqprs::{ use amqprs::{
channel::ExchangeDeclareArguments, channel::{Channel, ExchangeDeclareArguments},
connection::{Connection, OpenConnectionArguments}, connection::{Connection, OpenConnectionArguments},
}; };
use async_std::channel::unbounded;
use authifier::AuthifierEvent;
use rocket::data::ToByteUnit; use rocket::data::ToByteUnit;
use revolt_database::voice::VoiceClient; use revolt_database::voice::VoiceClient;
@@ -39,28 +36,9 @@ pub async fn web() -> Rocket<Build> {
log::info!("database_here {db:?}"); log::info!("database_here {db:?}");
db.migrate_database().await.unwrap(); db.migrate_database().await.unwrap();
// Setup Authifier event channel
let (_, receiver) = unbounded();
// Setup Authifier // Setup Authifier
let authifier = db.clone().to_authifier().await; let authifier = db.clone().to_authifier().await;
// Launch a listener for Authifier events
async_std::task::spawn(async move {
while let Ok(event) = receiver.recv().await {
match &event {
AuthifierEvent::CreateSession { .. } | AuthifierEvent::CreateAccount { .. } => {
EventV1::Auth(event).global().await
}
AuthifierEvent::DeleteSession { user_id, .. }
| AuthifierEvent::DeleteAllSessions { user_id, .. } => {
let id = user_id.to_string();
EventV1::Auth(event).private(id).await
}
}
}
});
// Configure CORS // Configure CORS
let cors = CorsOptions { let cors = CorsOptions {
allowed_origins: AllowedOrigins::All, allowed_origins: AllowedOrigins::All,
@@ -121,19 +99,19 @@ pub async fn web() -> Rocket<Build> {
.await .await
.expect("Failed to connect to RabbitMQ"); .expect("Failed to connect to RabbitMQ");
let channel = connection set_rabbitmq_connection(connection.clone());
.open_channel(None) let channel = get_channel_with_init(|channel: Channel| async {
.await channel
.expect("Failed to open RabbitMQ channel"); .exchange_declare(
ExchangeDeclareArguments::new(&config.pushd.exchange, "direct")
.durable(true)
.finish(),
)
.await
.expect("Failed to declare exchange");
channel channel
.exchange_declare( }).await;
ExchangeDeclareArguments::new(&config.pushd.exchange, "direct")
.durable(true)
.finish(),
)
.await
.expect("Failed to declare exchange");
let amqp = AMQP::new(connection, channel); let amqp = AMQP::new(connection, channel);

View File

@@ -6,7 +6,9 @@ use futures::StreamExt;
use rand::Rng; use rand::Rng;
use redis_kiss::redis::aio::PubSub; use redis_kiss::redis::aio::PubSub;
use revolt_database::{ use revolt_database::{
events::client::EventV1, Channel, Database, Member, Message, PartialRole, Server, User, AMQP, events::client::EventV1,
util::rabbit::{get_channel, set_rabbitmq_connection},
Channel, Database, Member, Message, PartialRole, Server, User, AMQP,
}; };
use revolt_database::{util::idempotency::IdempotencyKey, Role}; use revolt_database::{util::idempotency::IdempotencyKey, Role};
use revolt_models::v0; use revolt_models::v0;
@@ -59,10 +61,16 @@ impl TestHarness {
) )
.await .await
.unwrap(); .unwrap();
let channel = connection.open_channel(None).await.unwrap();
set_rabbitmq_connection(connection.clone());
let channel = get_channel().await;
let amqp = AMQP::new(connection, channel); let amqp = AMQP::new(connection, channel);
async_std::task::spawn(async {
});
TestHarness { TestHarness {
client, client,
authifier, authifier,