Merge pull request #961 from AppFlowy-IO/remove-realitime-shared-state

chore: remove realtime shared state
This commit is contained in:
Bartosz Sypytkowski 2024-11-05 07:32:18 +01:00 committed by GitHub
commit e31e541d07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 2 additions and 217 deletions

View File

@ -167,9 +167,6 @@ pub trait CollabStorage: Send + Sync + 'static {
/// Returns list of snapshots for given object_id in descending order of creation time.
async fn get_collab_snapshot_list(&self, oid: &str) -> AppResult<AFSnapshotMetas>;
async fn add_connected_user(&self, uid: i64, device_id: &str);
async fn remove_connected_user(&self, uid: i64, device_id: &str);
}
#[async_trait]
@ -306,14 +303,6 @@ where
async fn get_collab_snapshot_list(&self, oid: &str) -> AppResult<AFSnapshotMetas> {
self.as_ref().get_collab_snapshot_list(oid).await
}
async fn add_connected_user(&self, uid: i64, device_id: &str) {
self.as_ref().add_connected_user(uid, device_id).await
}
async fn remove_connected_user(&self, uid: i64, device_id: &str) {
self.as_ref().remove_connected_user(uid, device_id).await
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]

View File

@ -115,15 +115,6 @@ async fn post_realtime_message_stream_handler(
event!(tracing::Level::INFO, "message len: {}", bytes.len());
let device_id = device_id.to_string();
// Only send message to websocket server when the user is connected
if !state
.realtime_shared_state
.is_user_connected(&uid, &device_id)
.await
.unwrap_or(false)
{
return Ok(Json(AppResponse::Ok()));
}
let message = parser_realtime_msg(bytes.freeze(), req.clone()).await?;
let stream_message = ClientStreamMessage {

View File

@ -14,7 +14,7 @@ use database::collab::cache::CollabCache;
use secrecy::ExposeSecret;
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
use tracing::{info, warn};
use tracing::info;
use crate::actix_ws::server::RealtimeServerActor;
use crate::collab::access_control::CollabStorageAccessControlImpl;
@ -28,7 +28,6 @@ use crate::command::{CLCommandReceiver, CLCommandSender};
use crate::config::{Config, DatabaseSetting};
use crate::indexer::IndexerProvider;
use crate::pg_listener::PgListeners;
use crate::shared_state::RealtimeSharedState;
use crate::snapshot::SnapshotControl;
use crate::state::{AppMetrics, AppState, UserCache};
use crate::CollaborationServer;
@ -108,10 +107,6 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
info!("Connecting to Redis...");
let redis_conn_manager = get_redis_client(config.redis_uri.expose_secret()).await?;
let realtime_shared_state = RealtimeSharedState::new(redis_conn_manager.clone());
if let Err(err) = realtime_shared_state.remove_all_connected_users().await {
warn!("Failed to remove all connected users: {:?}", err);
}
// Pg listeners
info!("Setting up Pg listeners...");
@ -158,7 +153,6 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
access_control,
collab_access_control_storage: collab_storage,
metrics,
realtime_shared_state,
indexer_provider,
};
Ok(app_state)

View File

@ -17,7 +17,6 @@ use tracing::{error, instrument, trace};
use validator::Validate;
use crate::command::{CLCommandSender, CollaborationCommand};
use crate::shared_state::RealtimeSharedState;
use app_error::AppError;
use database::collab::{
insert_into_af_collab_bulk_for_user, AppResult, CollabMetadata, CollabStorage,
@ -47,7 +46,6 @@ pub struct CollabStorageImpl<AC> {
snapshot_control: SnapshotControl,
rt_cmd_sender: CLCommandSender,
queue: Arc<StorageQueue>,
shared_state: RealtimeSharedState,
}
impl<AC> CollabStorageImpl<AC>
@ -62,7 +60,6 @@ where
redis_conn_manager: RedisConnectionManager,
metrics: Arc<CollabMetrics>,
) -> Self {
let shared_state = RealtimeSharedState::new(redis_conn_manager.clone());
let queue = Arc::new(StorageQueue::new_with_metrics(
cache.clone(),
redis_conn_manager,
@ -75,7 +72,6 @@ where
snapshot_control,
rt_cmd_sender,
queue,
shared_state,
}
}
@ -488,22 +484,6 @@ where
self.snapshot_control.get_collab_snapshot_list(oid).await
}
async fn add_connected_user(&self, uid: i64, device_id: &str) {
if let Err(err) = self.shared_state.add_connected_user(uid, device_id).await {
error!("Failed to add connected user: {}", err);
}
}
async fn remove_connected_user(&self, uid: i64, device_id: &str) {
if let Err(err) = self
.shared_state
.remove_connected_user(uid, device_id)
.await
{
error!("Failed to remove connected user: {}", err);
}
}
async fn broadcast_encode_collab(
&self,
object_id: String,

View File

@ -14,7 +14,6 @@ pub mod metrics;
mod permission;
mod pg_listener;
mod rt_server;
pub mod shared_state;
pub mod snapshot;
mod state;
pub mod telemetry;

View File

@ -35,7 +35,6 @@ pub struct CollaborationServer<S> {
group_manager: Arc<GroupManager<S>>,
connect_state: ConnectState,
group_sender_by_object_id: Arc<DashMap<String, GroupCommandSender>>,
storage: Arc<S>,
#[allow(dead_code)]
metrics: Arc<CollabRealtimeMetrics>,
enable_custom_runtime: bool,
@ -95,10 +94,9 @@ where
spawn_metrics(metrics.clone(), storage.clone());
spawn_handle_unindexed_collabs(indexer_provider, storage.clone());
spawn_handle_unindexed_collabs(indexer_provider, storage);
Ok(Self {
storage,
group_manager,
connect_state,
group_sender_by_object_id,
@ -123,13 +121,8 @@ where
let group_manager = self.group_manager.clone();
let connect_state = self.connect_state.clone();
let metrics_calculate = self.metrics.clone();
let storage = self.storage.clone();
Box::pin(async move {
storage
.add_connected_user(connected_user.uid, &connected_user.device_id)
.await;
if let Some(old_user) = connect_state.handle_user_connect(connected_user, new_client_router) {
// Remove the old user from all collaboration groups.
group_manager.remove_user(&old_user).await;
@ -155,16 +148,11 @@ where
let group_manager = self.group_manager.clone();
let connect_state = self.connect_state.clone();
let metrics_calculate = self.metrics.clone();
let storage = self.storage.clone();
Box::pin(async move {
trace!("[realtime]: disconnect => {}", disconnect_user);
let was_removed = connect_state.handle_user_disconnect(&disconnect_user);
if was_removed.is_some() {
storage
.remove_connected_user(disconnect_user.uid, &disconnect_user.device_id)
.await;
metrics_calculate
.connected_users
.set(connect_state.number_of_connected_users() as i64);

View File

@ -1,74 +0,0 @@
use crate::error::RealtimeError;
use futures_util::StreamExt;
use redis::{pipe, AsyncCommands, AsyncIter};
#[derive(Clone)]
pub struct RealtimeSharedState {
redis_conn_manager: redis::aio::ConnectionManager,
}
impl RealtimeSharedState {
pub fn new(redis_conn_manager: redis::aio::ConnectionManager) -> Self {
Self { redis_conn_manager }
}
pub async fn add_connected_user(&self, uid: i64, device_id: &str) -> Result<(), RealtimeError> {
let mut conn = self.redis_conn_manager.clone();
let key = realtime_shared_state_cache_key(&uid, device_id);
conn
.set_ex(key, "1", 60 * 60 * 3)
.await
.map_err(|err| RealtimeError::Internal(err.into()))?;
Ok(())
}
pub async fn remove_connected_user(
&self,
uid: i64,
device_id: &str,
) -> Result<(), RealtimeError> {
let mut conn = self.redis_conn_manager.clone();
let key = realtime_shared_state_cache_key(&uid, device_id);
conn
.del(key)
.await
.map_err(|err| RealtimeError::Internal(err.into()))?;
Ok(())
}
pub async fn is_user_connected(&self, uid: &i64, device_id: &str) -> Result<bool, RealtimeError> {
let mut conn = self.redis_conn_manager.clone();
let key = realtime_shared_state_cache_key(uid, device_id);
let result: Option<String> = conn
.get(key)
.await
.map_err(|err| RealtimeError::Internal(err.into()))?;
Ok(result.is_some())
}
pub async fn remove_all_connected_users(&self) -> Result<(), RealtimeError> {
let mut conn = self.redis_conn_manager.clone();
let iter: AsyncIter<String> = conn
.scan_match(format!("{}:*", REALTIME_SHARE_STATE_PREFIX))
.await
.map_err(|err| RealtimeError::Internal(err.into()))?;
let keys_to_delete: Vec<_> = iter.collect().await;
if !keys_to_delete.is_empty() {
let mut pipeline = pipe();
for key in keys_to_delete.iter() {
pipeline.del(key);
}
pipeline
.query_async(&mut conn)
.await
.map_err(|err| RealtimeError::Internal(err.into()))?;
}
Ok(())
}
}
pub(crate) const REALTIME_SHARE_STATE_PREFIX: &str = "realtime_shared_state_v0";
#[inline]
pub(crate) fn realtime_shared_state_cache_key(uid: &i64, device_id: &str) -> String {
format!("{}:{}:{}", REALTIME_SHARE_STATE_PREFIX, uid, device_id)
}

View File

@ -15,7 +15,6 @@ use crate::config::Config;
use crate::indexer::IndexerProvider;
use crate::metrics::CollabMetrics;
use crate::pg_listener::PgListeners;
use crate::shared_state::RealtimeSharedState;
use crate::CollabRealtimeMetrics;
pub type RedisConnectionManager = redis::aio::ConnectionManager;
@ -29,7 +28,6 @@ pub struct AppState {
pub access_control: AccessControl,
pub collab_access_control_storage: Arc<CollabAccessControlStorage>,
pub metrics: AppMetrics,
pub realtime_shared_state: RealtimeSharedState,
pub indexer_provider: Arc<IndexerProvider>,
}

View File

@ -1,63 +0,0 @@
use anyhow::Context;
use appflowy_collaborate::shared_state::RealtimeSharedState;
async fn redis_client() -> redis::Client {
let redis_uri = "redis://localhost:6379";
redis::Client::open(redis_uri)
.context("failed to connect to redis")
.unwrap()
}
#[tokio::test]
async fn connected_user_test() {
let redis_client = redis_client().await;
let shared_state = RealtimeSharedState::new(redis_client.get_connection_manager().await.unwrap());
let device_id = uuid::Uuid::new_v4().to_string();
let is_connected = shared_state
.is_user_connected(&1, &device_id)
.await
.unwrap();
assert!(!is_connected);
shared_state
.add_connected_user(1, &device_id)
.await
.unwrap();
let is_connected = shared_state
.is_user_connected(&1, &device_id)
.await
.unwrap();
assert!(is_connected);
shared_state
.remove_connected_user(1, &device_id)
.await
.unwrap();
let is_connected = shared_state
.is_user_connected(&1, &device_id)
.await
.unwrap();
assert!(!is_connected);
}
#[tokio::test]
async fn remove_all_connected_user_test() {
let redis_client = redis_client().await;
let shared_state = RealtimeSharedState::new(redis_client.get_connection_manager().await.unwrap());
let device_id = uuid::Uuid::new_v4().to_string();
shared_state
.add_connected_user(1, &device_id)
.await
.unwrap();
shared_state.remove_all_connected_users().await.unwrap();
let is_connected = shared_state
.is_user_connected(&1, &device_id)
.await
.unwrap();
assert!(!is_connected);
}

View File

@ -1533,15 +1533,6 @@ async fn post_realtime_message_stream_handler(
event!(tracing::Level::INFO, "message len: {}", bytes.len());
let device_id = device_id.to_string();
// Only send message to websocket server when the user is connected
if !state
.realtime_shared_state
.is_user_connected(&uid, &device_id)
.await
.unwrap_or(false)
{
return Ok(Json(AppResponse::Ok()));
}
let message = parser_realtime_msg(bytes.freeze(), req.clone()).await?;
let stream_message = ClientStreamMessage {

View File

@ -40,7 +40,6 @@ use appflowy_collaborate::actix_ws::server::RealtimeServerActor;
use appflowy_collaborate::collab::storage::CollabStorageImpl;
use appflowy_collaborate::command::{CLCommandReceiver, CLCommandSender};
use appflowy_collaborate::indexer::IndexerProvider;
use appflowy_collaborate::shared_state::RealtimeSharedState;
use appflowy_collaborate::snapshot::SnapshotControl;
use appflowy_collaborate::CollaborationServer;
use database::file::s3_client_impl::{AwsS3BucketClientImpl, S3BucketStorage};
@ -316,10 +315,6 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
let grpc_history_client = Arc::new(Mutex::new(HistoryClient::new(channel)));
let mailer = get_mailer(config).await?;
let realtime_shared_state = RealtimeSharedState::new(redis_conn_manager.clone());
if let Err(err) = realtime_shared_state.remove_all_connected_users().await {
warn!("Failed to remove all connected users: {:?}", err);
}
info!("Application state initialized");
Ok(AppState {
@ -343,7 +338,6 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
mailer,
ai_client: appflowy_ai_client,
grpc_history_client,
realtime_shared_state,
indexer_provider,
})
}

View File

@ -17,7 +17,6 @@ use appflowy_ai_client::client::AppFlowyAIClient;
use appflowy_collaborate::collab::storage::CollabAccessControlStorage;
use appflowy_collaborate::indexer::IndexerProvider;
use appflowy_collaborate::metrics::CollabMetrics;
use appflowy_collaborate::shared_state::RealtimeSharedState;
use appflowy_collaborate::CollabRealtimeMetrics;
use database::file::s3_client_impl::{AwsS3BucketClientImpl, S3BucketStorage};
use database::user::{select_all_uid_uuid, select_uid_from_uuid};
@ -54,7 +53,6 @@ pub struct AppState {
pub gotrue_admin: GoTrueAdmin,
pub mailer: AFCloudMailer,
pub ai_client: AppFlowyAIClient,
pub realtime_shared_state: RealtimeSharedState,
pub grpc_history_client: Arc<Mutex<HistoryClient<tonic::transport::Channel>>>,
pub indexer_provider: Arc<IndexerProvider>,
}