mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 14:47:13 -04:00
push rev if client doc is outdated
This commit is contained in:
parent
d70072ae9f
commit
15d628c750
6 changed files with 184 additions and 58 deletions
|
@ -35,6 +35,12 @@ pub enum EditMsg {
|
||||||
DocumentJson {
|
DocumentJson {
|
||||||
ret: oneshot::Sender<DocResult<String>>,
|
ret: oneshot::Sender<DocResult<String>>,
|
||||||
},
|
},
|
||||||
|
NewDocUser {
|
||||||
|
user: Arc<WsUser>,
|
||||||
|
socket: Socket,
|
||||||
|
rev_id: i64,
|
||||||
|
ret: oneshot::Sender<DocResult<()>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditDocActor {
|
pub struct EditDocActor {
|
||||||
|
@ -78,7 +84,6 @@ impl EditDocActor {
|
||||||
revision,
|
revision,
|
||||||
ret,
|
ret,
|
||||||
} => {
|
} => {
|
||||||
// ret.send(self.handle_client_data(client_data, pool).await);
|
|
||||||
let user = EditUser {
|
let user = EditUser {
|
||||||
user: user.clone(),
|
user: user.clone(),
|
||||||
socket: socket.clone(),
|
socket: socket.clone(),
|
||||||
|
@ -92,6 +97,18 @@ impl EditDocActor {
|
||||||
.map_err(internal_error);
|
.map_err(internal_error);
|
||||||
let _ = ret.send(json);
|
let _ = ret.send(json);
|
||||||
},
|
},
|
||||||
|
EditMsg::NewDocUser {
|
||||||
|
user,
|
||||||
|
socket,
|
||||||
|
rev_id,
|
||||||
|
ret,
|
||||||
|
} => {
|
||||||
|
let user = EditUser {
|
||||||
|
user: user.clone(),
|
||||||
|
socket: socket.clone(),
|
||||||
|
};
|
||||||
|
let _ = ret.send(self.edit_doc.new_connection(user, rev_id, self.pg_pool.clone()).await);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::service::{
|
||||||
};
|
};
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
|
|
||||||
|
use crate::service::doc::edit::interval::Interval;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use flowy_document::{
|
use flowy_document::{
|
||||||
|
@ -22,6 +23,7 @@ use parking_lot::RwLock;
|
||||||
use protobuf::Message;
|
use protobuf::Message;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::{
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicI64, Ordering::SeqCst},
|
atomic::{AtomicI64, Ordering::SeqCst},
|
||||||
|
@ -52,6 +54,28 @@ impl ServerEditDoc {
|
||||||
|
|
||||||
pub fn document_json(&self) -> String { self.document.read().to_json() }
|
pub fn document_json(&self) -> String { self.document.read().to_json() }
|
||||||
|
|
||||||
|
pub async fn new_connection(&self, user: EditUser, rev_id: i64, _pg_pool: Data<PgPool>) -> Result<(), ServerError> {
|
||||||
|
self.users.insert(user.id(), user.clone());
|
||||||
|
let cur_rev_id = self.rev_id.load(SeqCst);
|
||||||
|
if cur_rev_id > rev_id {
|
||||||
|
let doc_delta = self.document.read().delta().clone();
|
||||||
|
let cli_revision = self.mk_revision(rev_id, doc_delta);
|
||||||
|
let ws_cli_revision = mk_push_rev_ws_message(&self.doc_id, cli_revision);
|
||||||
|
user.socket.do_send(ws_cli_revision).map_err(internal_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(
|
||||||
|
level = "debug",
|
||||||
|
skip(self, user, pg_pool),
|
||||||
|
fields(
|
||||||
|
rev_id = %self.rev_id.load(SeqCst),
|
||||||
|
revision_rev_id = %revision.rev_id,
|
||||||
|
revision_base_rev_id = %revision.base_rev_id
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn apply_revision(
|
pub async fn apply_revision(
|
||||||
&self,
|
&self,
|
||||||
user: EditUser,
|
user: EditUser,
|
||||||
|
@ -60,49 +84,48 @@ impl ServerEditDoc {
|
||||||
) -> Result<(), ServerError> {
|
) -> Result<(), ServerError> {
|
||||||
// Opti: find out another way to keep the user socket available.
|
// Opti: find out another way to keep the user socket available.
|
||||||
self.users.insert(user.id(), user.clone());
|
self.users.insert(user.id(), user.clone());
|
||||||
log::debug!(
|
|
||||||
"cur_base_rev_id: {}, expect_base_rev_id: {} rev_id: {}",
|
|
||||||
self.rev_id.load(SeqCst),
|
|
||||||
revision.base_rev_id,
|
|
||||||
revision.rev_id
|
|
||||||
);
|
|
||||||
|
|
||||||
let cur_rev_id = self.rev_id.load(SeqCst);
|
let cur_rev_id = self.rev_id.load(SeqCst);
|
||||||
if cur_rev_id > revision.rev_id {
|
match cur_rev_id.cmp(&revision.rev_id) {
|
||||||
// The client document is outdated. Transform the client revision delta and then
|
Ordering::Less => {
|
||||||
// send the prime delta to the client. Client should compose the this prime
|
if cur_rev_id != revision.base_rev_id {
|
||||||
// delta.
|
// The server document is outdated, try to get the missing revision from the
|
||||||
|
// client.
|
||||||
let (cli_prime, server_prime) = self.transform(&revision.delta_data).map_err(internal_error)?;
|
user.socket
|
||||||
let _ = self.update_document_delta(server_prime)?;
|
.do_send(mk_pull_rev_ws_message(&self.doc_id, cur_rev_id, revision.rev_id))
|
||||||
|
.map_err(internal_error)?;
|
||||||
log::debug!("{} client delta: {}", self.doc_id, cli_prime.to_json());
|
} else {
|
||||||
let cli_revision = self.mk_revision(revision.rev_id, cli_prime);
|
let _ = self.compose_revision(&revision, pg_pool).await?;
|
||||||
let ws_cli_revision = mk_push_rev_ws_message(&self.doc_id, cli_revision);
|
user.socket
|
||||||
user.socket.do_send(ws_cli_revision).map_err(internal_error)?;
|
.do_send(mk_acked_ws_message(&revision))
|
||||||
Ok(())
|
.map_err(internal_error)?;
|
||||||
} else if cur_rev_id < revision.rev_id {
|
}
|
||||||
if cur_rev_id != revision.base_rev_id {
|
},
|
||||||
// The server document is outdated, try to get the missing revision from the
|
Ordering::Equal => {},
|
||||||
// client.
|
Ordering::Greater => {
|
||||||
user.socket
|
// The client document is outdated. Transform the client revision delta and then
|
||||||
.do_send(mk_pull_rev_ws_message(&self.doc_id, cur_rev_id, revision.rev_id))
|
// send the prime delta to the client. Client should compose the this prime
|
||||||
.map_err(internal_error)?;
|
// delta.
|
||||||
} else {
|
let cli_revision = self.transform_client_revision(&revision)?;
|
||||||
let delta = Delta::from_bytes(&revision.delta_data).map_err(internal_error)?;
|
let ws_cli_revision = mk_push_rev_ws_message(&self.doc_id, cli_revision);
|
||||||
let _ = self.update_document_delta(delta)?;
|
user.socket.do_send(ws_cli_revision).map_err(internal_error)?;
|
||||||
user.socket
|
},
|
||||||
.do_send(mk_acked_ws_message(&revision))
|
|
||||||
.map_err(internal_error)?;
|
|
||||||
self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id));
|
|
||||||
let _ = self.save_revision(&revision, pg_pool).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
log::error!("Client rev_id should not equal to server rev_id");
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compose_revision(&self, revision: &Revision, pg_pool: Data<PgPool>) -> Result<(), ServerError> {
|
||||||
|
let delta = Delta::from_bytes(&revision.delta_data).map_err(internal_error)?;
|
||||||
|
let _ = self.compose_delta(delta)?;
|
||||||
|
let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id));
|
||||||
|
let _ = self.save_revision(&revision, pg_pool).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_client_revision(&self, revision: &Revision) -> Result<Revision, ServerError> {
|
||||||
|
let (cli_prime, server_prime) = self.transform(&revision.delta_data).map_err(internal_error)?;
|
||||||
|
let _ = self.compose_delta(server_prime)?;
|
||||||
|
let cli_revision = self.mk_revision(revision.rev_id, cli_prime);
|
||||||
|
Ok(cli_revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_revision(&self, base_rev_id: i64, delta: Delta) -> Revision {
|
fn mk_revision(&self, base_rev_id: i64, delta: Delta) -> Revision {
|
||||||
|
@ -133,7 +156,7 @@ impl ServerEditDoc {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
fn update_document_delta(&self, delta: Delta) -> Result<(), ServerError> {
|
fn compose_delta(&self, delta: Delta) -> Result<(), ServerError> {
|
||||||
// Opti: push each revision into queue and process it one by one.
|
// Opti: push each revision into queue and process it one by one.
|
||||||
match self.document.try_write_for(Duration::from_millis(300)) {
|
match self.document.try_write_for(Duration::from_millis(300)) {
|
||||||
None => {
|
None => {
|
||||||
|
|
57
backend/src/service/doc/edit/interval.rs
Normal file
57
backend/src/service/doc/edit/interval.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Interval {
|
||||||
|
pub start: i64,
|
||||||
|
pub end: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interval {
|
||||||
|
/// Construct a new `Interval` representing the range [start..end).
|
||||||
|
/// It is an invariant that `start <= end`.
|
||||||
|
pub fn new(start: i64, end: i64) -> Interval {
|
||||||
|
debug_assert!(start <= end);
|
||||||
|
Interval { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&self) -> i64 { self.start }
|
||||||
|
|
||||||
|
pub fn end(&self) -> i64 { self.end }
|
||||||
|
|
||||||
|
pub fn is_before(&self, val: i64) -> bool { self.end <= val }
|
||||||
|
|
||||||
|
pub fn contains(&self, val: i64) -> bool { self.start <= val && val < self.end }
|
||||||
|
|
||||||
|
pub fn contains_range(&self, start: i64, end: i64) -> bool { !self.intersect(Interval::new(start, end)).is_empty() }
|
||||||
|
|
||||||
|
pub fn is_after(&self, val: i64) -> bool { self.start > val }
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool { self.end <= self.start }
|
||||||
|
|
||||||
|
pub fn intersect(&self, other: Interval) -> Interval {
|
||||||
|
let start = max(self.start, other.start);
|
||||||
|
let end = min(self.end, other.end);
|
||||||
|
Interval {
|
||||||
|
start,
|
||||||
|
end: max(start, end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the first half of self - other
|
||||||
|
pub fn prefix(&self, other: Interval) -> Interval {
|
||||||
|
Interval {
|
||||||
|
start: min(self.start, other.start),
|
||||||
|
end: min(self.end, other.start),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the second half of self - other
|
||||||
|
pub fn suffix(&self, other: Interval) -> Interval {
|
||||||
|
Interval {
|
||||||
|
start: max(self.start, other.end),
|
||||||
|
end: max(self.end, other.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> i64 { self.end - self.start }
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
mod edit_actor;
|
mod edit_actor;
|
||||||
mod edit_doc;
|
mod edit_doc;
|
||||||
|
mod interval;
|
||||||
mod open_handle;
|
mod open_handle;
|
||||||
|
|
||||||
|
pub use edit_actor::*;
|
||||||
pub use edit_doc::*;
|
pub use edit_doc::*;
|
||||||
pub use open_handle::*;
|
pub use open_handle::*;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::sync::Arc;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
pub struct DocHandle {
|
pub struct DocHandle {
|
||||||
sender: mpsc::Sender<EditMsg>,
|
pub sender: mpsc::Sender<EditMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocHandle {
|
impl DocHandle {
|
||||||
|
@ -22,6 +22,18 @@ impl DocHandle {
|
||||||
Ok(Self { sender })
|
Ok(Self { sender })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_new_user(&self, user: Arc<WsUser>, rev_id: i64, socket: Socket) -> Result<(), ServerError> {
|
||||||
|
let (ret, rx) = oneshot::channel();
|
||||||
|
let msg = EditMsg::NewDocUser {
|
||||||
|
user,
|
||||||
|
socket,
|
||||||
|
rev_id,
|
||||||
|
ret,
|
||||||
|
};
|
||||||
|
let _ = self.send(msg, rx).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self, user, socket, revision))]
|
#[tracing::instrument(level = "debug", skip(self, user, socket, revision))]
|
||||||
pub async fn apply_revision(
|
pub async fn apply_revision(
|
||||||
&self,
|
&self,
|
||||||
|
@ -46,7 +58,7 @@ impl DocHandle {
|
||||||
self.send(msg, rx).await?
|
self.send(msg, rx).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send<T>(&self, msg: EditMsg, rx: oneshot::Receiver<T>) -> DocResult<T> {
|
pub(crate) async fn send<T>(&self, msg: EditMsg, rx: oneshot::Receiver<T>) -> DocResult<T> {
|
||||||
let _ = self.sender.send(msg).await.map_err(internal_error)?;
|
let _ = self.sender.send(msg).await.map_err(internal_error)?;
|
||||||
let result = rx.await?;
|
let result = rx.await?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::service::{
|
use crate::service::{
|
||||||
doc::doc::DocManager,
|
doc::{doc::DocManager, edit::DocHandle},
|
||||||
util::{md5, parse_from_bytes},
|
util::{md5, parse_from_bytes},
|
||||||
ws::{entities::Socket, WsClientData, WsUser},
|
ws::{entities::Socket, WsClientData, WsUser},
|
||||||
};
|
};
|
||||||
|
@ -74,21 +74,29 @@ impl DocWsActor {
|
||||||
match document_data.ty {
|
match document_data.ty {
|
||||||
WsDataType::Acked => Ok(()),
|
WsDataType::Acked => Ok(()),
|
||||||
WsDataType::PushRev => self.handle_push_rev(user, socket, data, pool).await,
|
WsDataType::PushRev => self.handle_push_rev(user, socket, data, pool).await,
|
||||||
WsDataType::NewDocUser => self.handle_new_doc_user(socket, data).await,
|
WsDataType::NewDocUser => self.handle_new_doc_user(user, socket, data, pool).await,
|
||||||
WsDataType::PullRev => Ok(()),
|
WsDataType::PullRev => Ok(()),
|
||||||
WsDataType::Conflict => Ok(()),
|
WsDataType::Conflict => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_new_doc_user(&self, _socket: Socket, data: Vec<u8>) -> DocResult<()> {
|
async fn handle_new_doc_user(
|
||||||
let _user = spawn_blocking(move || {
|
&self,
|
||||||
|
user: Arc<WsUser>,
|
||||||
|
socket: Socket,
|
||||||
|
data: Vec<u8>,
|
||||||
|
pool: Data<PgPool>,
|
||||||
|
) -> DocResult<()> {
|
||||||
|
let doc_user = spawn_blocking(move || {
|
||||||
let user: NewDocUser = parse_from_bytes(&data)?;
|
let user: NewDocUser = parse_from_bytes(&data)?;
|
||||||
DocResult::Ok(user)
|
DocResult::Ok(user)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(internal_error)??;
|
.map_err(internal_error)??;
|
||||||
|
if let Some(handle) = self.doc_handle(&doc_user.doc_id, pool).await {
|
||||||
unimplemented!()
|
handle.handle_new_user(user, doc_user.rev_id, socket).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_push_rev(
|
async fn handle_push_rev(
|
||||||
|
@ -105,15 +113,22 @@ impl DocWsActor {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(internal_error)??;
|
.map_err(internal_error)??;
|
||||||
|
if let Some(handle) = self.doc_handle(&revision.doc_id, pool).await {
|
||||||
|
handle.apply_revision(user, socket, revision).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
match self.doc_manager.get(&revision.doc_id, pool).await? {
|
async fn doc_handle(&self, doc_id: &str, pool: Data<PgPool>) -> Option<Arc<DocHandle>> {
|
||||||
Some(edit_doc) => {
|
match self.doc_manager.get(doc_id, pool).await {
|
||||||
edit_doc.apply_revision(user, socket, revision).await?;
|
Ok(Some(edit_doc)) => Some(edit_doc),
|
||||||
Ok(())
|
Ok(None) => {
|
||||||
|
log::error!("Document with id: {} not exist", doc_id);
|
||||||
|
None
|
||||||
},
|
},
|
||||||
None => {
|
Err(e) => {
|
||||||
log::error!("Document with id: {} not exist", &revision.doc_id);
|
log::error!("Get doc handle failed: {:?}", e);
|
||||||
Ok(())
|
None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue