mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 15:24:48 +01:00
feat: stop worker when dropping controller, unwraps
This commit is contained in:
parent
853d754d8b
commit
bd6132dc1e
8 changed files with 115 additions and 18 deletions
|
@ -2,6 +2,7 @@ use operational_transform::OperationSeq;
|
||||||
use tokio::sync::{watch, mpsc, broadcast, Mutex};
|
use tokio::sync::{watch, mpsc, broadcast, Mutex};
|
||||||
use tonic::async_trait;
|
use tonic::async_trait;
|
||||||
|
|
||||||
|
use crate::errors::IgnorableError;
|
||||||
use crate::{Controller, Error};
|
use crate::{Controller, Error};
|
||||||
use crate::buffer::factory::{leading_noop, tailing_noop, OperationFactory};
|
use crate::buffer::factory::{leading_noop, tailing_noop, OperationFactory};
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ pub struct BufferController {
|
||||||
content: watch::Receiver<String>,
|
content: watch::Receiver<String>,
|
||||||
operations: mpsc::Sender<OperationSeq>,
|
operations: mpsc::Sender<OperationSeq>,
|
||||||
stream: Mutex<broadcast::Receiver<OperationSeq>>,
|
stream: Mutex<broadcast::Receiver<OperationSeq>>,
|
||||||
|
stop: mpsc::UnboundedSender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferController {
|
impl BufferController {
|
||||||
|
@ -18,8 +20,15 @@ impl BufferController {
|
||||||
content: watch::Receiver<String>,
|
content: watch::Receiver<String>,
|
||||||
operations: mpsc::Sender<OperationSeq>,
|
operations: mpsc::Sender<OperationSeq>,
|
||||||
stream: Mutex<broadcast::Receiver<OperationSeq>>,
|
stream: Mutex<broadcast::Receiver<OperationSeq>>,
|
||||||
|
stop: mpsc::UnboundedSender<()>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
BufferController { content, operations, stream }
|
BufferController { content, operations, stream, stop }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for BufferController {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop.send(()).unwrap_or_warn("could not send stop message to worker");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod controller;
|
||||||
pub mod factory;
|
pub mod factory;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct TextChange {
|
pub struct TextChange {
|
||||||
pub span: Range<usize>,
|
pub span: Range<usize>,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use tokio::sync::{watch, mpsc, broadcast, Mutex};
|
||||||
use tonic::transport::Channel;
|
use tonic::transport::Channel;
|
||||||
use tonic::{async_trait, Streaming};
|
use tonic::{async_trait, Streaming};
|
||||||
|
|
||||||
|
use crate::errors::IgnorableError;
|
||||||
use crate::proto::{OperationRequest, RawOp};
|
use crate::proto::{OperationRequest, RawOp};
|
||||||
use crate::proto::buffer_client::BufferClient;
|
use crate::proto::buffer_client::BufferClient;
|
||||||
use crate::ControllerWorker;
|
use crate::ControllerWorker;
|
||||||
|
@ -23,6 +24,8 @@ pub(crate) struct BufferControllerWorker {
|
||||||
sender: mpsc::Sender<OperationSeq>,
|
sender: mpsc::Sender<OperationSeq>,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
path: String,
|
path: String,
|
||||||
|
stop: mpsc::UnboundedReceiver<()>,
|
||||||
|
stop_control: mpsc::UnboundedSender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferControllerWorker {
|
impl BufferControllerWorker {
|
||||||
|
@ -30,6 +33,7 @@ impl BufferControllerWorker {
|
||||||
let (txt_tx, txt_rx) = watch::channel(buffer.to_string());
|
let (txt_tx, txt_rx) = watch::channel(buffer.to_string());
|
||||||
let (op_tx, op_rx) = mpsc::channel(64);
|
let (op_tx, op_rx) = mpsc::channel(64);
|
||||||
let (s_tx, _s_rx) = broadcast::channel(64);
|
let (s_tx, _s_rx) = broadcast::channel(64);
|
||||||
|
let (end_tx, end_rx) = mpsc::unbounded_channel();
|
||||||
BufferControllerWorker {
|
BufferControllerWorker {
|
||||||
uid,
|
uid,
|
||||||
content: txt_tx,
|
content: txt_tx,
|
||||||
|
@ -40,6 +44,8 @@ impl BufferControllerWorker {
|
||||||
queue: VecDeque::new(),
|
queue: VecDeque::new(),
|
||||||
buffer: buffer.to_string(),
|
buffer: buffer.to_string(),
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
|
stop: end_rx,
|
||||||
|
stop_control: end_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +61,7 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
self.receiver.clone(),
|
self.receiver.clone(),
|
||||||
self.sender.clone(),
|
self.sender.clone(),
|
||||||
Mutex::new(self.stream.subscribe()),
|
Mutex::new(self.stream.subscribe()),
|
||||||
|
self.stop_control.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,19 +71,31 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
Some(operation) = recv_opseq(&mut rx) => {
|
Some(operation) = recv_opseq(&mut rx) => {
|
||||||
let mut out = operation;
|
let mut out = operation;
|
||||||
for op in self.queue.iter_mut() {
|
for op in self.queue.iter_mut() {
|
||||||
(*op, out) = op.transform(&out).unwrap();
|
(*op, out) = match op.transform(&out) {
|
||||||
|
Ok((x, y)) => (x, y),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("could not transform enqueued operation: {}", e);
|
||||||
|
break
|
||||||
|
},
|
||||||
}
|
}
|
||||||
self.stream.send(out.clone()).unwrap();
|
}
|
||||||
|
self.stream.send(out.clone()).unwrap_or_warn("could not send operation to server");
|
||||||
out
|
out
|
||||||
},
|
},
|
||||||
Some(op) = self.operations.recv() => {
|
Some(op) = self.operations.recv() => {
|
||||||
self.queue.push_back(op.clone());
|
self.queue.push_back(op.clone());
|
||||||
op
|
op
|
||||||
},
|
},
|
||||||
|
Some(()) = self.stop.recv() => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
else => break
|
else => break
|
||||||
};
|
};
|
||||||
self.buffer = op.apply(&self.buffer).unwrap();
|
self.buffer = op.apply(&self.buffer).unwrap_or_else(|e| {
|
||||||
self.content.send(self.buffer.clone()).unwrap();
|
tracing::error!("could not update buffer string: {}", e);
|
||||||
|
self.buffer
|
||||||
|
});
|
||||||
|
self.content.send(self.buffer.clone()).unwrap_or_warn("error showing updated buffer");
|
||||||
|
|
||||||
while let Some(op) = self.queue.get(0) {
|
while let Some(op) = self.queue.get(0) {
|
||||||
if !send_opseq(&mut tx, self.uid.clone(), self.path.clone(), op.clone()).await { break }
|
if !send_opseq(&mut tx, self.uid.clone(), self.path.clone(), op.clone()).await { break }
|
||||||
|
@ -87,11 +106,17 @@ impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_opseq(tx: &mut BufferClient<Channel>, uid: String, path: String, op: OperationSeq) -> bool {
|
async fn send_opseq(tx: &mut BufferClient<Channel>, uid: String, path: String, op: OperationSeq) -> bool {
|
||||||
|
let opseq = match serde_json::to_string(&op) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("could not serialize opseq: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
let req = OperationRequest {
|
let req = OperationRequest {
|
||||||
hash: "".into(),
|
hash: "".into(),
|
||||||
opseq: serde_json::to_string(&op).unwrap(),
|
|
||||||
path,
|
|
||||||
user: uid,
|
user: uid,
|
||||||
|
opseq, path,
|
||||||
};
|
};
|
||||||
match tx.edit(req).await {
|
match tx.edit(req).await {
|
||||||
Ok(_) => true,
|
Ok(_) => true,
|
||||||
|
@ -104,7 +129,13 @@ async fn send_opseq(tx: &mut BufferClient<Channel>, uid: String, path: String, o
|
||||||
|
|
||||||
async fn recv_opseq(rx: &mut Streaming<RawOp>) -> Option<OperationSeq> {
|
async fn recv_opseq(rx: &mut Streaming<RawOp>) -> Option<OperationSeq> {
|
||||||
match rx.message().await {
|
match rx.message().await {
|
||||||
Ok(Some(op)) => Some(serde_json::from_str(&op.opseq).unwrap()),
|
Ok(Some(op)) => match serde_json::from_str(&op.opseq) {
|
||||||
|
Ok(x) => Some(x),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("could not deserialize opseq: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
Ok(None) => None,
|
Ok(None) => None,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("could not receive edit from server: {}", e);
|
tracing::error!("could not receive edit from server: {}", e);
|
||||||
|
|
|
@ -41,6 +41,17 @@ impl Client {
|
||||||
Some(self.workspace.as_ref()?.cursor.clone())
|
Some(self.workspace.as_ref()?.cursor.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn leave_workspace(&mut self) {
|
||||||
|
self.workspace = None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnect_buffer(&mut self, path: &str) -> bool {
|
||||||
|
match &mut self.workspace {
|
||||||
|
Some(w) => w.buffers.remove(path).is_some(),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_buffer(&self, path: &str) -> Option<Arc<BufferController>> {
|
pub fn get_buffer(&self, path: &str) -> Option<Arc<BufferController>> {
|
||||||
self.workspace.as_ref()?.buffers.get(path).cloned()
|
self.workspace.as_ref()?.buffers.get(path).cloned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
use tokio::sync::{mpsc, broadcast::{self, error::RecvError}, Mutex};
|
use tokio::sync::{mpsc, broadcast::{self, error::RecvError}, Mutex};
|
||||||
use tonic::async_trait;
|
use tonic::async_trait;
|
||||||
|
|
||||||
use crate::{proto::{CursorPosition, CursorEvent}, Error, Controller};
|
use crate::{proto::{CursorPosition, CursorEvent}, Error, Controller, errors::IgnorableError};
|
||||||
|
|
||||||
pub struct CursorController {
|
pub struct CursorController {
|
||||||
uid: String,
|
uid: String,
|
||||||
op: mpsc::Sender<CursorEvent>,
|
op: mpsc::Sender<CursorEvent>,
|
||||||
stream: Mutex<broadcast::Receiver<CursorEvent>>,
|
stream: Mutex<broadcast::Receiver<CursorEvent>>,
|
||||||
|
stop: mpsc::UnboundedSender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CursorController {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop.send(()).unwrap_or_warn("could not stop cursor actor")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorController {
|
impl CursorController {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
uid: String,
|
uid: String,
|
||||||
op: mpsc::Sender<CursorEvent>,
|
op: mpsc::Sender<CursorEvent>,
|
||||||
stream: Mutex<broadcast::Receiver<CursorEvent>>
|
stream: Mutex<broadcast::Receiver<CursorEvent>>,
|
||||||
|
stop: mpsc::UnboundedSender<()>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
CursorController { uid, op, stream }
|
CursorController { uid, op, stream, stop }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,17 +12,22 @@ pub(crate) struct CursorControllerWorker {
|
||||||
producer: mpsc::Sender<CursorEvent>,
|
producer: mpsc::Sender<CursorEvent>,
|
||||||
op: mpsc::Receiver<CursorEvent>,
|
op: mpsc::Receiver<CursorEvent>,
|
||||||
channel: Arc<broadcast::Sender<CursorEvent>>,
|
channel: Arc<broadcast::Sender<CursorEvent>>,
|
||||||
|
stop: mpsc::UnboundedReceiver<()>,
|
||||||
|
stop_control: mpsc::UnboundedSender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorControllerWorker {
|
impl CursorControllerWorker {
|
||||||
pub(crate) fn new(uid: String) -> Self {
|
pub(crate) fn new(uid: String) -> Self {
|
||||||
let (op_tx, op_rx) = mpsc::channel(64);
|
let (op_tx, op_rx) = mpsc::channel(64);
|
||||||
let (cur_tx, _cur_rx) = broadcast::channel(64);
|
let (cur_tx, _cur_rx) = broadcast::channel(64);
|
||||||
|
let (end_tx, end_rx) = mpsc::unbounded_channel();
|
||||||
Self {
|
Self {
|
||||||
uid,
|
uid,
|
||||||
producer: op_tx,
|
producer: op_tx,
|
||||||
op: op_rx,
|
op: op_rx,
|
||||||
channel: Arc::new(cur_tx),
|
channel: Arc::new(cur_tx),
|
||||||
|
stop: end_rx,
|
||||||
|
stop_control: end_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +42,8 @@ impl ControllerWorker<CursorEvent> for CursorControllerWorker {
|
||||||
CursorController::new(
|
CursorController::new(
|
||||||
self.uid.clone(),
|
self.uid.clone(),
|
||||||
self.producer.clone(),
|
self.producer.clone(),
|
||||||
Mutex::new(self.channel.subscribe())
|
Mutex::new(self.channel.subscribe()),
|
||||||
|
self.stop_control.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +52,7 @@ impl ControllerWorker<CursorEvent> for CursorControllerWorker {
|
||||||
tokio::select!{
|
tokio::select!{
|
||||||
Ok(Some(cur)) = rx.message() => self.channel.send(cur).unwrap_or_warn("could not broadcast event"),
|
Ok(Some(cur)) = rx.message() => self.channel.send(cur).unwrap_or_warn("could not broadcast event"),
|
||||||
Some(op) = self.op.recv() => { tx.moved(op).await.unwrap_or_warn("could not update cursor"); },
|
Some(op) = self.op.recv() => { tx.moved(op).await.unwrap_or_warn("could not update cursor"); },
|
||||||
|
Some(()) = self.stop.recv() => { break; },
|
||||||
else => break,
|
else => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
buffer::controller::BufferController,
|
buffer::controller::BufferController,
|
||||||
errors::Error, client::Client, cursor::controller::CursorController, Controller,
|
errors::Error, client::Client, cursor::controller::CursorController,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,4 +82,25 @@ impl Instance {
|
||||||
.get_buffer(path)
|
.get_buffer(path)
|
||||||
.ok_or(Error::InvalidState { msg: "join a workspace or create requested buffer first".into() })
|
.ok_or(Error::InvalidState { msg: "join a workspace or create requested buffer first".into() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn leave_workspace(&self) -> Result<(), Error> {
|
||||||
|
self.client
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(Error::InvalidState { msg: "connect first".into() })?
|
||||||
|
.leave_workspace();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn disconnect_buffer(&self, path: &str) -> Result<bool, Error> {
|
||||||
|
Ok(
|
||||||
|
self.client
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_mut()
|
||||||
|
.ok_or(Error::InvalidState { msg: "connect first".into() })?
|
||||||
|
.disconnect_buffer(path)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -41,13 +41,22 @@ pub trait Controller<T> : Sized + Send + Sync {
|
||||||
async fn send(&self, x: Self::Input) -> Result<(), Error>;
|
async fn send(&self, x: Self::Input) -> Result<(), Error>;
|
||||||
async fn recv(&self) -> Result<T, Error>;
|
async fn recv(&self) -> Result<T, Error>;
|
||||||
|
|
||||||
fn callback<F>(self: Arc<Self>, rt: &tokio::runtime::Runtime, mut cb: F)
|
fn callback<F>(
|
||||||
where Self : 'static, F : FnMut(T) + Sync + Send + 'static
|
self: Arc<Self>,
|
||||||
|
rt: &tokio::runtime::Runtime,
|
||||||
|
mut stop: tokio::sync::mpsc::UnboundedReceiver<()>,
|
||||||
|
mut cb: F
|
||||||
|
) where
|
||||||
|
Self : 'static,
|
||||||
|
F : FnMut(T) + Sync + Send + 'static
|
||||||
{
|
{
|
||||||
let x = Arc::new(self);
|
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
while let Ok(data) = x.recv().await {
|
loop {
|
||||||
cb(data)
|
tokio::select! {
|
||||||
|
Ok(data) = self.recv() => cb(data),
|
||||||
|
Some(()) = stop.recv() => break,
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue