mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 14:47:13 -04:00
retry when ws connection lost
This commit is contained in:
parent
32d9331b35
commit
155a526d04
13 changed files with 116 additions and 45 deletions
|
@ -2,7 +2,7 @@ use std::{net::TcpListener, time::Duration};
|
||||||
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||||
use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope};
|
use actix_web::{dev::Server, web, web::Data, App, HttpServer, Scope};
|
||||||
use sqlx::{postgres::PgPoolOptions, PgPool};
|
use sqlx::{postgres::PgPoolOptions, PgPool};
|
||||||
use tokio::time::interval;
|
use tokio::time::interval;
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ pub fn run(listener: TcpListener, app_ctx: AppContext) -> Result<Server, std::io
|
||||||
|
|
||||||
let server = HttpServer::new(move || {
|
let server = HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Logger::default())
|
// .wrap(middleware::Logger::default())
|
||||||
.wrap(identify_service(&domain, &secret))
|
.wrap(identify_service(&domain, &secret))
|
||||||
.wrap(crate::middleware::default_cors())
|
.wrap(crate::middleware::default_cors())
|
||||||
.wrap(crate::middleware::AuthenticationService)
|
.wrap(crate::middleware::AuthenticationService)
|
||||||
|
|
|
@ -82,7 +82,7 @@ impl ServerEditDoc {
|
||||||
|
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
level = "debug",
|
level = "debug",
|
||||||
skip(self, user, pg_pool),
|
skip(self, user, pg_pool, revision),
|
||||||
fields(
|
fields(
|
||||||
rev_id = %self.rev_id.load(SeqCst),
|
rev_id = %self.rev_id.load(SeqCst),
|
||||||
revision_rev_id = %revision.rev_id,
|
revision_rev_id = %revision.rev_id,
|
||||||
|
@ -186,7 +186,7 @@ impl ServerEditDoc {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self, pg_pool), err)]
|
#[tracing::instrument(level = "debug", skip(self, revision, pg_pool), err)]
|
||||||
async fn save_revision(&self, revision: &Revision, pg_pool: Data<PgPool>) -> Result<(), ServerError> {
|
async fn save_revision(&self, revision: &Revision, pg_pool: Data<PgPool>) -> Result<(), ServerError> {
|
||||||
// Opti: save with multiple revisions
|
// Opti: save with multiple revisions
|
||||||
let mut params = UpdateDocParams::new();
|
let mut params = UpdateDocParams::new();
|
||||||
|
|
|
@ -33,7 +33,6 @@ impl DocHandle {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self, user, socket, revision))]
|
|
||||||
pub async fn apply_revision(
|
pub async fn apply_revision(
|
||||||
&self,
|
&self,
|
||||||
user: Arc<WsUser>,
|
user: Arc<WsUser>,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
||||||
server::Server,
|
server::Server,
|
||||||
ws::WsDocumentManager,
|
ws::WsDocumentManager,
|
||||||
},
|
},
|
||||||
sql_tables::doc::{DocTable, DocTableSql},
|
sql_tables::doc::DocTableSql,
|
||||||
};
|
};
|
||||||
use flowy_database::{ConnectionPool, SqliteConnection};
|
use flowy_database::{ConnectionPool, SqliteConnection};
|
||||||
use flowy_infra::future::{wrap_future, FnFuture, ResultFuture};
|
use flowy_infra::future::{wrap_future, FnFuture, ResultFuture};
|
||||||
|
@ -51,7 +51,7 @@ impl DocController {
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, conn), err)]
|
#[tracing::instrument(skip(self, conn), err)]
|
||||||
pub(crate) fn create(&self, params: CreateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
|
pub(crate) fn create(&self, params: CreateDocParams, conn: &SqliteConnection) -> Result<(), DocError> {
|
||||||
let doc = Doc {
|
let _doc = Doc {
|
||||||
id: params.id,
|
id: params.id,
|
||||||
data: params.data,
|
data: params.data,
|
||||||
rev_id: 0,
|
rev_id: 0,
|
||||||
|
|
|
@ -117,7 +117,7 @@ impl DocumentActor {
|
||||||
let data = self.document.read().await.to_json();
|
let data = self.document.read().await.to_json();
|
||||||
let _ = ret.send(Ok(data));
|
let _ = ret.send(Ok(data));
|
||||||
},
|
},
|
||||||
DocumentMsg::SaveDocument { rev_id, ret } => {
|
DocumentMsg::SaveDocument { rev_id: _, ret } => {
|
||||||
// let result = self.save_to_disk(rev_id).await;
|
// let result = self.save_to_disk(rev_id).await;
|
||||||
let _ = ret.send(Ok(()));
|
let _ = ret.send(Ok(()));
|
||||||
},
|
},
|
||||||
|
|
|
@ -289,11 +289,11 @@ impl WsDocumentHandler for EditDocWsHandler {
|
||||||
fn state_changed(&self, state: &WsState) {
|
fn state_changed(&self, state: &WsState) {
|
||||||
match state {
|
match state {
|
||||||
WsState::Init => {},
|
WsState::Init => {},
|
||||||
WsState::Connected(_) => {
|
WsState::Connected(_) => self.0.notify_open_doc(),
|
||||||
log::debug!("ws state changed: {}", state);
|
WsState::Disconnected(e) => {
|
||||||
self.0.notify_open_doc()
|
log::error!("websocket error: {:?}", e);
|
||||||
|
//
|
||||||
},
|
},
|
||||||
WsState::Disconnected(_) => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,10 +65,7 @@ fn listen_ws_state_changed(ws: Arc<dyn DocumentWebSocket>, handlers: Arc<DashMap
|
||||||
handle.value().state_changed(&state);
|
handle.value().state_changed(&state);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(_) => break,
|
||||||
log::error!("Websocket state notify error: {:?}", e);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod deps_resolve;
|
||||||
// mod flowy_server;
|
// mod flowy_server;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
|
||||||
use crate::deps_resolve::{DocumentDepsResolver, WorkspaceDepsResolver};
|
use crate::deps_resolve::WorkspaceDepsResolver;
|
||||||
use flowy_dispatch::prelude::*;
|
use flowy_dispatch::prelude::*;
|
||||||
use flowy_document::prelude::FlowyDocument;
|
use flowy_document::prelude::FlowyDocument;
|
||||||
use flowy_net::config::ServerConfig;
|
use flowy_net::config::ServerConfig;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::deps_resolve::{DocumentDepsResolver, WorkspaceDepsResolver};
|
use crate::deps_resolve::DocumentDepsResolver;
|
||||||
use flowy_dispatch::prelude::Module;
|
use flowy_dispatch::prelude::Module;
|
||||||
use flowy_document::module::FlowyDocument;
|
use flowy_document::module::FlowyDocument;
|
||||||
use flowy_net::config::ServerConfig;
|
use flowy_net::config::ServerConfig;
|
||||||
use flowy_user::services::user::UserSession;
|
use flowy_user::services::user::UserSession;
|
||||||
use flowy_workspace::{module::mk_workspace, prelude::WorkspaceController};
|
use flowy_workspace::prelude::WorkspaceController;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn mk_modules(workspace_controller: Arc<WorkspaceController>, user_session: Arc<UserSession>) -> Vec<Module> {
|
pub fn mk_modules(workspace_controller: Arc<WorkspaceController>, user_session: Arc<UserSession>) -> Vec<Module> {
|
||||||
|
|
|
@ -19,7 +19,7 @@ use flowy_database::{
|
||||||
use flowy_infra::kv::KV;
|
use flowy_infra::kv::KV;
|
||||||
use flowy_net::config::ServerConfig;
|
use flowy_net::config::ServerConfig;
|
||||||
use flowy_sqlite::ConnectionPool;
|
use flowy_sqlite::ConnectionPool;
|
||||||
use flowy_ws::{WsController, WsMessageHandler};
|
use flowy_ws::{WsController, WsMessageHandler, WsState};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -278,12 +278,41 @@ impl UserSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self, token))]
|
||||||
pub async fn start_ws_connection(&self, token: &str) -> Result<(), UserError> {
|
pub async fn start_ws_connection(&self, token: &str) -> Result<(), UserError> {
|
||||||
log::debug!("start_ws_connection");
|
|
||||||
let addr = format!("{}/{}", self.server.ws_addr(), token);
|
let addr = format!("{}/{}", self.server.ws_addr(), token);
|
||||||
let _ = self.ws_controller.connect(addr).await?;
|
self.listen_on_websocket();
|
||||||
|
|
||||||
|
let _ = self.ws_controller.start_connect(addr).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
fn listen_on_websocket(&self) {
|
||||||
|
let mut notify = self.ws_controller.state_subscribe();
|
||||||
|
let ws_controller = self.ws_controller.clone();
|
||||||
|
let _ = tokio::spawn(async move {
|
||||||
|
log::debug!("listen ws state");
|
||||||
|
loop {
|
||||||
|
match notify.recv().await {
|
||||||
|
Ok(state) => {
|
||||||
|
log::info!("Websocket state changed: {}", state);
|
||||||
|
match state {
|
||||||
|
WsState::Init => {},
|
||||||
|
WsState::Connected(_) => {},
|
||||||
|
WsState::Disconnected(_) => {
|
||||||
|
ws_controller.retry().await;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Websocket state notify error: {:?}", e);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_user(
|
pub async fn update_user(
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use crate::{errors::WsError, MsgReceiver, MsgSender};
|
use crate::{
|
||||||
|
errors::{internal_error, WsError},
|
||||||
|
MsgReceiver,
|
||||||
|
MsgSender,
|
||||||
|
};
|
||||||
use futures_core::{future::BoxFuture, ready};
|
use futures_core::{future::BoxFuture, ready};
|
||||||
use futures_util::{FutureExt, StreamExt};
|
use futures_util::{FutureExt, StreamExt};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
@ -88,14 +92,32 @@ impl WsStream {
|
||||||
msg_tx: msg_tx.clone(),
|
msg_tx: msg_tx.clone(),
|
||||||
inner: Some((
|
inner: Some((
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
|
let (tx, mut rx) = tokio::sync::mpsc::channel(10);
|
||||||
let _ = ws_read
|
let _ = ws_read
|
||||||
.for_each(|message| async { post_message(msg_tx.clone(), message) })
|
.for_each(|message| async {
|
||||||
|
match tx.send(post_message(msg_tx.clone(), message)).await {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => log::error!("WsStream tx closed unexpectedly: {} ", e),
|
||||||
|
}
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
Ok(())
|
|
||||||
|
loop {
|
||||||
|
match rx.recv().await {
|
||||||
|
None => {
|
||||||
|
return Err(WsError::internal().context("WsStream rx closed unexpectedly"));
|
||||||
|
},
|
||||||
|
Some(result) => {
|
||||||
|
if result.is_err() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let _ = ws_rx.map(Ok).forward(ws_write).await?;
|
let result = ws_rx.map(Ok).forward(ws_write).await.map_err(internal_error);
|
||||||
Ok(())
|
result
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
@ -127,16 +149,11 @@ impl Future for WsStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_message(tx: MsgSender, message: Result<Message, Error>) {
|
fn post_message(tx: MsgSender, message: Result<Message, Error>) -> Result<(), WsError> {
|
||||||
match message {
|
match message {
|
||||||
Ok(Message::Binary(bytes)) => match tx.unbounded_send(Message::Binary(bytes)) {
|
Ok(Message::Binary(bytes)) => tx.unbounded_send(Message::Binary(bytes)).map_err(internal_error),
|
||||||
Ok(_) => {},
|
Ok(_) => Ok(()),
|
||||||
Err(e) => log::error!("tx send error: {:?}", e),
|
Err(e) => Err(WsError::internal().context(e)),
|
||||||
},
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("ws read error: {:?}", e)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
|
@ -45,6 +45,13 @@ impl WsError {
|
||||||
static_user_error!(unauthorized, ErrorCode::Unauthorized);
|
static_user_error!(unauthorized, ErrorCode::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn internal_error<T>(e: T) -> WsError
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
WsError::internal().context(e)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, ProtoBuf_Enum, Display, PartialEq, Eq)]
|
#[derive(Debug, Clone, ProtoBuf_Enum, Display, PartialEq, Eq)]
|
||||||
pub enum ErrorCode {
|
pub enum ErrorCode {
|
||||||
InternalError = 0,
|
InternalError = 0,
|
||||||
|
|
|
@ -6,9 +6,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use flowy_infra::retry::{Action, ExponentialBackoff, Retry};
|
use flowy_infra::retry::{Action, ExponentialBackoff, FixedInterval, Retry};
|
||||||
use flowy_net::errors::ServerError;
|
use flowy_net::errors::ServerError;
|
||||||
|
|
||||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -20,6 +19,7 @@ use std::{
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::sync::{broadcast, oneshot};
|
use tokio::sync::{broadcast, oneshot};
|
||||||
use tokio_tungstenite::tungstenite::{
|
use tokio_tungstenite::tungstenite::{
|
||||||
|
@ -61,6 +61,7 @@ pub struct WsController {
|
||||||
handlers: Handlers,
|
handlers: Handlers,
|
||||||
state_notify: Arc<broadcast::Sender<WsState>>,
|
state_notify: Arc<broadcast::Sender<WsState>>,
|
||||||
sender: Arc<RwLock<Option<Arc<WsSender>>>>,
|
sender: Arc<RwLock<Option<Arc<WsSender>>>>,
|
||||||
|
addr: Arc<RwLock<Option<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WsController {
|
impl WsController {
|
||||||
|
@ -70,6 +71,7 @@ impl WsController {
|
||||||
handlers: DashMap::new(),
|
handlers: DashMap::new(),
|
||||||
sender: Arc::new(RwLock::new(None)),
|
sender: Arc::new(RwLock::new(None)),
|
||||||
state_notify: Arc::new(state_notify),
|
state_notify: Arc::new(state_notify),
|
||||||
|
addr: Arc::new(RwLock::new(None)),
|
||||||
};
|
};
|
||||||
controller
|
controller
|
||||||
}
|
}
|
||||||
|
@ -83,14 +85,26 @@ impl WsController {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect(&self, addr: String) -> Result<(), ServerError> {
|
pub async fn start_connect(&self, addr: String) -> Result<(), ServerError> {
|
||||||
|
*self.addr.write() = Some(addr.clone());
|
||||||
|
|
||||||
|
let strategy = ExponentialBackoff::from_millis(100).take(5);
|
||||||
|
self.connect(addr, strategy).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect<T, I>(&self, addr: String, strategy: T) -> Result<(), ServerError>
|
||||||
|
where
|
||||||
|
T: IntoIterator<IntoIter = I, Item = Duration>,
|
||||||
|
I: Iterator<Item = Duration> + Send + 'static,
|
||||||
|
{
|
||||||
let (ret, rx) = oneshot::channel::<Result<(), ServerError>>();
|
let (ret, rx) = oneshot::channel::<Result<(), ServerError>>();
|
||||||
|
*self.addr.write() = Some(addr.clone());
|
||||||
|
|
||||||
let action = WsConnectAction {
|
let action = WsConnectAction {
|
||||||
addr,
|
addr,
|
||||||
handlers: self.handlers.clone(),
|
handlers: self.handlers.clone(),
|
||||||
};
|
};
|
||||||
let strategy = ExponentialBackoff::from_millis(100).take(3);
|
|
||||||
let retry = Retry::spawn(strategy, action);
|
let retry = Retry::spawn(strategy, action);
|
||||||
let sender_holder = self.sender.clone();
|
let sender_holder = self.sender.clone();
|
||||||
let state_notify = self.state_notify.clone();
|
let state_notify = self.state_notify.clone();
|
||||||
|
@ -121,7 +135,17 @@ impl WsController {
|
||||||
rx.await?
|
rx.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub async fn retry(&self) -> Result<(), ServerError> {
|
||||||
|
let addr = self
|
||||||
|
.addr
|
||||||
|
.read()
|
||||||
|
.as_ref()
|
||||||
|
.expect("must call start_connect first")
|
||||||
|
.clone();
|
||||||
|
let strategy = FixedInterval::from_millis(5000);
|
||||||
|
self.connect(addr, strategy).await
|
||||||
|
}
|
||||||
|
|
||||||
pub fn state_subscribe(&self) -> broadcast::Receiver<WsState> { self.state_notify.subscribe() }
|
pub fn state_subscribe(&self) -> broadcast::Receiver<WsState> { self.state_notify.subscribe() }
|
||||||
|
|
||||||
pub fn sender(&self) -> Result<Arc<WsSender>, WsError> {
|
pub fn sender(&self) -> Result<Arc<WsSender>, WsError> {
|
||||||
|
@ -142,9 +166,8 @@ async fn spawn_stream_and_handlers(
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {},
|
Ok(_) => {},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// TODO: retry?
|
log::error!("websocket error: {:?}", e);
|
||||||
log::error!("ws stream error {:?}", e);
|
let _ = state_notify.send(WsState::Disconnected(e)).unwrap();
|
||||||
let _ = state_notify.send(WsState::Disconnected(e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -275,7 +298,6 @@ impl WsConnectActionFut {
|
||||||
// └─────────┼──│ws_write │◀─┼────│ ws_rx │◀──┼──│ ws_tx │ │
|
// └─────────┼──│ws_write │◀─┼────│ ws_rx │◀──┼──│ ws_tx │ │
|
||||||
// │ └─────────┘ │ └────────┘ │ └────────┘ │
|
// │ └─────────┘ │ └────────┘ │ └────────┘ │
|
||||||
// └───────────────┘ └──────────────┘
|
// └───────────────┘ └──────────────┘
|
||||||
log::debug!("🐴 ws start connect: {}", &addr);
|
|
||||||
let (msg_tx, msg_rx) = futures_channel::mpsc::unbounded();
|
let (msg_tx, msg_rx) = futures_channel::mpsc::unbounded();
|
||||||
let (ws_tx, ws_rx) = futures_channel::mpsc::unbounded();
|
let (ws_tx, ws_rx) = futures_channel::mpsc::unbounded();
|
||||||
let sender = WsSender { ws_tx };
|
let sender = WsSender { ws_tx };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue