Start work on rejoining channel buffers
This commit is contained in:
parent
2bf417fa45
commit
d370c72fbf
9 changed files with 526 additions and 163 deletions
|
@ -17,6 +17,7 @@ pub struct ChannelBuffer {
|
||||||
connected: bool,
|
connected: bool,
|
||||||
collaborators: Vec<proto::Collaborator>,
|
collaborators: Vec<proto::Collaborator>,
|
||||||
buffer: ModelHandle<language::Buffer>,
|
buffer: ModelHandle<language::Buffer>,
|
||||||
|
buffer_epoch: u64,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
subscription: Option<client::Subscription>,
|
subscription: Option<client::Subscription>,
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,7 @@ impl ChannelBuffer {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
buffer,
|
buffer,
|
||||||
|
buffer_epoch: response.epoch,
|
||||||
client,
|
client,
|
||||||
connected: true,
|
connected: true,
|
||||||
collaborators,
|
collaborators,
|
||||||
|
@ -82,6 +84,26 @@ impl ChannelBuffer {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn replace_collaborators(
|
||||||
|
&mut self,
|
||||||
|
collaborators: Vec<proto::Collaborator>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
for old_collaborator in &self.collaborators {
|
||||||
|
if collaborators
|
||||||
|
.iter()
|
||||||
|
.any(|c| c.replica_id == old_collaborator.replica_id)
|
||||||
|
{
|
||||||
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.remove_peer(old_collaborator.replica_id as u16, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.collaborators = collaborators;
|
||||||
|
cx.emit(Event::CollaboratorsChanged);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_update_channel_buffer(
|
async fn handle_update_channel_buffer(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
||||||
|
@ -166,6 +188,10 @@ impl ChannelBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn epoch(&self) -> u64 {
|
||||||
|
self.buffer_epoch
|
||||||
|
}
|
||||||
|
|
||||||
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
|
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
|
||||||
self.buffer.clone()
|
self.buffer.clone()
|
||||||
}
|
}
|
||||||
|
@ -179,6 +205,7 @@ impl ChannelBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
|
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
|
log::info!("channel buffer {} disconnected", self.channel.id);
|
||||||
if self.connected {
|
if self.connected {
|
||||||
self.connected = false;
|
self.connected = false;
|
||||||
self.subscription.take();
|
self.subscription.take();
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use crate::channel_buffer::ChannelBuffer;
|
use crate::channel_buffer::ChannelBuffer;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use client::{Client, Status, Subscription, User, UserId, UserStore};
|
use client::{Client, Subscription, User, UserId, UserStore};
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||||
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
|
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
|
||||||
use rpc::{proto, TypedEnvelope};
|
use rpc::{proto, TypedEnvelope};
|
||||||
use std::sync::Arc;
|
use std::{mem, sync::Arc, time::Duration};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
|
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
pub type ChannelId = u64;
|
pub type ChannelId = u64;
|
||||||
|
|
||||||
pub struct ChannelStore {
|
pub struct ChannelStore {
|
||||||
|
@ -22,7 +24,8 @@ pub struct ChannelStore {
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
_rpc_subscription: Subscription,
|
_rpc_subscription: Subscription,
|
||||||
_watch_connection_status: Task<()>,
|
_watch_connection_status: Task<Option<()>>,
|
||||||
|
disconnect_channel_buffers_task: Option<Task<()>>,
|
||||||
_update_channels: Task<()>,
|
_update_channels: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,24 +70,20 @@ impl ChannelStore {
|
||||||
let rpc_subscription =
|
let rpc_subscription =
|
||||||
client.add_message_handler(cx.handle(), Self::handle_update_channels);
|
client.add_message_handler(cx.handle(), Self::handle_update_channels);
|
||||||
|
|
||||||
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
|
||||||
let mut connection_status = client.status();
|
let mut connection_status = client.status();
|
||||||
|
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
||||||
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
|
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
|
||||||
while let Some(status) = connection_status.next().await {
|
while let Some(status) = connection_status.next().await {
|
||||||
if !status.is_connected() {
|
let this = this.upgrade(&cx)?;
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if status.is_connected() {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| this.handle_connect(cx))
|
||||||
if matches!(status, Status::ConnectionLost | Status::SignedOut) {
|
.await
|
||||||
this.handle_disconnect(cx);
|
.log_err()?;
|
||||||
} else {
|
} else {
|
||||||
this.disconnect_buffers(cx);
|
this.update(&mut cx, |this, cx| this.handle_disconnect(cx));
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(())
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -100,6 +99,7 @@ impl ChannelStore {
|
||||||
user_store,
|
user_store,
|
||||||
_rpc_subscription: rpc_subscription,
|
_rpc_subscription: rpc_subscription,
|
||||||
_watch_connection_status: watch_connection_status,
|
_watch_connection_status: watch_connection_status,
|
||||||
|
disconnect_channel_buffers_task: None,
|
||||||
_update_channels: cx.spawn_weak(|this, mut cx| async move {
|
_update_channels: cx.spawn_weak(|this, mut cx| async move {
|
||||||
while let Some(update_channels) = update_channels_rx.next().await {
|
while let Some(update_channels) = update_channels_rx.next().await {
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
|
@ -482,8 +482,102 @@ impl ChannelStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_disconnect(&mut self, cx: &mut ModelContext<'_, ChannelStore>) {
|
fn handle_connect(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
self.disconnect_buffers(cx);
|
self.disconnect_channel_buffers_task.take();
|
||||||
|
|
||||||
|
let mut buffer_versions = Vec::new();
|
||||||
|
for buffer in self.opened_buffers.values() {
|
||||||
|
if let OpenedChannelBuffer::Open(buffer) = buffer {
|
||||||
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
|
let channel_buffer = buffer.read(cx);
|
||||||
|
let buffer = channel_buffer.buffer().read(cx);
|
||||||
|
buffer_versions.push(proto::ChannelBufferVersion {
|
||||||
|
channel_id: channel_buffer.channel().id,
|
||||||
|
epoch: channel_buffer.epoch(),
|
||||||
|
version: language::proto::serialize_version(&buffer.version()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = self.client.request(proto::RejoinChannelBuffers {
|
||||||
|
buffers: buffer_versions,
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let mut response = response.await?;
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.opened_buffers.retain(|_, buffer| match buffer {
|
||||||
|
OpenedChannelBuffer::Open(channel_buffer) => {
|
||||||
|
let Some(channel_buffer) = channel_buffer.upgrade(cx) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
channel_buffer.update(cx, |channel_buffer, cx| {
|
||||||
|
let channel_id = channel_buffer.channel().id;
|
||||||
|
if let Some(remote_buffer) = response
|
||||||
|
.buffers
|
||||||
|
.iter_mut()
|
||||||
|
.find(|buffer| buffer.channel_id == channel_id)
|
||||||
|
{
|
||||||
|
let channel_id = channel_buffer.channel().id;
|
||||||
|
let remote_version =
|
||||||
|
language::proto::deserialize_version(&remote_buffer.version);
|
||||||
|
|
||||||
|
channel_buffer.replace_collaborators(
|
||||||
|
mem::take(&mut remote_buffer.collaborators),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
let operations = channel_buffer
|
||||||
|
.buffer()
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
let outgoing_operations =
|
||||||
|
buffer.serialize_ops(Some(remote_version), cx);
|
||||||
|
let incoming_operations =
|
||||||
|
mem::take(&mut remote_buffer.operations)
|
||||||
|
.into_iter()
|
||||||
|
.map(language::proto::deserialize_operation)
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
buffer.apply_ops(incoming_operations, cx)?;
|
||||||
|
anyhow::Ok(outgoing_operations)
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
if let Some(operations) = operations {
|
||||||
|
let client = this.client.clone();
|
||||||
|
cx.background()
|
||||||
|
.spawn(async move {
|
||||||
|
let operations = operations.await;
|
||||||
|
for chunk in
|
||||||
|
language::proto::split_operations(operations)
|
||||||
|
{
|
||||||
|
client
|
||||||
|
.send(proto::UpdateChannelBuffer {
|
||||||
|
channel_id,
|
||||||
|
operations: chunk,
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_buffer.disconnect(cx);
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
OpenedChannelBuffer::Loading(_) => true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_disconnect(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
self.channels_by_id.clear();
|
self.channels_by_id.clear();
|
||||||
self.channel_invitations.clear();
|
self.channel_invitations.clear();
|
||||||
self.channel_participants.clear();
|
self.channel_participants.clear();
|
||||||
|
@ -491,16 +585,23 @@ impl ChannelStore {
|
||||||
self.channel_paths.clear();
|
self.channel_paths.clear();
|
||||||
self.outgoing_invites.clear();
|
self.outgoing_invites.clear();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
|
||||||
|
|
||||||
fn disconnect_buffers(&mut self, cx: &mut ModelContext<ChannelStore>) {
|
self.disconnect_channel_buffers_task.get_or_insert_with(|| {
|
||||||
for (_, buffer) in self.opened_buffers.drain() {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
if let OpenedChannelBuffer::Open(buffer) = buffer {
|
cx.background().timer(RECONNECT_TIMEOUT).await;
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
|
this.update(&mut cx, |this, cx| {
|
||||||
|
for (_, buffer) in this.opened_buffers.drain() {
|
||||||
|
if let OpenedChannelBuffer::Open(buffer) = buffer {
|
||||||
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
|
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_channels(
|
pub(crate) fn update_channels(
|
||||||
|
|
|
@ -10,8 +10,6 @@ impl Database {
|
||||||
connection: ConnectionId,
|
connection: ConnectionId,
|
||||||
) -> Result<proto::JoinChannelBufferResponse> {
|
) -> Result<proto::JoinChannelBufferResponse> {
|
||||||
self.transaction(|tx| async move {
|
self.transaction(|tx| async move {
|
||||||
let tx = tx;
|
|
||||||
|
|
||||||
self.check_user_is_channel_member(channel_id, user_id, &tx)
|
self.check_user_is_channel_member(channel_id, user_id, &tx)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -70,7 +68,6 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
collaborators.push(collaborator);
|
collaborators.push(collaborator);
|
||||||
|
|
||||||
// Assemble the buffer state
|
|
||||||
let (base_text, operations) = self.get_buffer_state(&buffer, &tx).await?;
|
let (base_text, operations) = self.get_buffer_state(&buffer, &tx).await?;
|
||||||
|
|
||||||
Ok(proto::JoinChannelBufferResponse {
|
Ok(proto::JoinChannelBufferResponse {
|
||||||
|
@ -78,6 +75,7 @@ impl Database {
|
||||||
replica_id: replica_id.to_proto() as u32,
|
replica_id: replica_id.to_proto() as u32,
|
||||||
base_text,
|
base_text,
|
||||||
operations,
|
operations,
|
||||||
|
epoch: buffer.epoch as u64,
|
||||||
collaborators: collaborators
|
collaborators: collaborators
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|collaborator| proto::Collaborator {
|
.map(|collaborator| proto::Collaborator {
|
||||||
|
@ -91,6 +89,113 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn rejoin_channel_buffers(
|
||||||
|
&self,
|
||||||
|
buffers: &[proto::ChannelBufferVersion],
|
||||||
|
user_id: UserId,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
) -> Result<proto::RejoinChannelBuffersResponse> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
let mut response = proto::RejoinChannelBuffersResponse::default();
|
||||||
|
for client_buffer in buffers {
|
||||||
|
let channel_id = ChannelId::from_proto(client_buffer.channel_id);
|
||||||
|
if self
|
||||||
|
.check_user_is_channel_member(channel_id, user_id, &*tx)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
log::info!("user is not a member of channel");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = self.get_channel_buffer(channel_id, &*tx).await?;
|
||||||
|
let mut collaborators = channel_buffer_collaborator::Entity::find()
|
||||||
|
.filter(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// If the buffer epoch hasn't changed since the client lost
|
||||||
|
// connection, then the client's buffer can be syncronized with
|
||||||
|
// the server's buffer.
|
||||||
|
if buffer.epoch as u64 != client_buffer.epoch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is still a disconnected collaborator for the user,
|
||||||
|
// update the connection associated with that collaborator, and reuse
|
||||||
|
// that replica id.
|
||||||
|
if let Some(ix) = collaborators
|
||||||
|
.iter()
|
||||||
|
.position(|c| c.user_id == user_id && c.connection_lost)
|
||||||
|
{
|
||||||
|
let self_collaborator = &mut collaborators[ix];
|
||||||
|
*self_collaborator = channel_buffer_collaborator::ActiveModel {
|
||||||
|
id: ActiveValue::Unchanged(self_collaborator.id),
|
||||||
|
connection_id: ActiveValue::Set(connection_id.id as i32),
|
||||||
|
connection_server_id: ActiveValue::Set(ServerId(
|
||||||
|
connection_id.owner_id as i32,
|
||||||
|
)),
|
||||||
|
connection_lost: ActiveValue::Set(false),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.update(&*tx)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client_version = version_from_wire(&client_buffer.version);
|
||||||
|
let serialization_version = self
|
||||||
|
.get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut rows = buffer_operation::Entity::find()
|
||||||
|
.filter(
|
||||||
|
buffer_operation::Column::BufferId
|
||||||
|
.eq(buffer.id)
|
||||||
|
.and(buffer_operation::Column::Epoch.eq(buffer.epoch)),
|
||||||
|
)
|
||||||
|
.stream(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Find the server's version vector and any operations
|
||||||
|
// that the client has not seen.
|
||||||
|
let mut server_version = clock::Global::new();
|
||||||
|
let mut operations = Vec::new();
|
||||||
|
while let Some(row) = rows.next().await {
|
||||||
|
let row = row?;
|
||||||
|
let timestamp = clock::Lamport {
|
||||||
|
replica_id: row.replica_id as u16,
|
||||||
|
value: row.lamport_timestamp as u32,
|
||||||
|
};
|
||||||
|
server_version.observe(timestamp);
|
||||||
|
if !client_version.observed(timestamp) {
|
||||||
|
operations.push(proto::Operation {
|
||||||
|
variant: Some(operation_from_storage(row, serialization_version)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.buffers.push(proto::RejoinedChannelBuffer {
|
||||||
|
channel_id: client_buffer.channel_id,
|
||||||
|
version: version_to_wire(&server_version),
|
||||||
|
operations,
|
||||||
|
collaborators: collaborators
|
||||||
|
.into_iter()
|
||||||
|
.map(|collaborator| proto::Collaborator {
|
||||||
|
peer_id: Some(collaborator.connection().into()),
|
||||||
|
user_id: collaborator.user_id.to_proto(),
|
||||||
|
replica_id: collaborator.replica_id.0 as u32,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn leave_channel_buffer(
|
pub async fn leave_channel_buffer(
|
||||||
&self,
|
&self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
@ -103,6 +208,39 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn leave_channel_buffers(
|
||||||
|
&self,
|
||||||
|
connection: ConnectionId,
|
||||||
|
) -> Result<Vec<(ChannelId, Vec<ConnectionId>)>> {
|
||||||
|
self.transaction(|tx| async move {
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
||||||
|
enum QueryChannelIds {
|
||||||
|
ChannelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel_ids: Vec<ChannelId> = channel_buffer_collaborator::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column(channel_buffer_collaborator::Column::ChannelId)
|
||||||
|
.filter(Condition::all().add(
|
||||||
|
channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32),
|
||||||
|
))
|
||||||
|
.into_values::<_, QueryChannelIds>()
|
||||||
|
.all(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for channel_id in channel_ids {
|
||||||
|
let collaborators = self
|
||||||
|
.leave_channel_buffer_internal(channel_id, connection, &*tx)
|
||||||
|
.await?;
|
||||||
|
result.push((channel_id, collaborators));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn leave_channel_buffer_internal(
|
pub async fn leave_channel_buffer_internal(
|
||||||
&self,
|
&self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
@ -143,45 +281,12 @@ impl Database {
|
||||||
drop(rows);
|
drop(rows);
|
||||||
|
|
||||||
if connections.is_empty() {
|
if connections.is_empty() {
|
||||||
self.snapshot_buffer(channel_id, &tx).await?;
|
self.snapshot_channel_buffer(channel_id, &tx).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(connections)
|
Ok(connections)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn leave_channel_buffers(
|
|
||||||
&self,
|
|
||||||
connection: ConnectionId,
|
|
||||||
) -> Result<Vec<(ChannelId, Vec<ConnectionId>)>> {
|
|
||||||
self.transaction(|tx| async move {
|
|
||||||
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
|
||||||
enum QueryChannelIds {
|
|
||||||
ChannelId,
|
|
||||||
}
|
|
||||||
|
|
||||||
let channel_ids: Vec<ChannelId> = channel_buffer_collaborator::Entity::find()
|
|
||||||
.select_only()
|
|
||||||
.column(channel_buffer_collaborator::Column::ChannelId)
|
|
||||||
.filter(Condition::all().add(
|
|
||||||
channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32),
|
|
||||||
))
|
|
||||||
.into_values::<_, QueryChannelIds>()
|
|
||||||
.all(&*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut result = Vec::new();
|
|
||||||
for channel_id in channel_ids {
|
|
||||||
let collaborators = self
|
|
||||||
.leave_channel_buffer_internal(channel_id, connection, &*tx)
|
|
||||||
.await?;
|
|
||||||
result.push((channel_id, collaborators));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_channel_buffer_collaborators(
|
pub async fn get_channel_buffer_collaborators(
|
||||||
&self,
|
&self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
@ -224,20 +329,9 @@ impl Database {
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| anyhow!("no such buffer"))?;
|
.ok_or_else(|| anyhow!("no such buffer"))?;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
let serialization_version = self
|
||||||
enum QueryVersion {
|
.get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx)
|
||||||
OperationSerializationVersion,
|
.await?;
|
||||||
}
|
|
||||||
|
|
||||||
let serialization_version: i32 = buffer
|
|
||||||
.find_related(buffer_snapshot::Entity)
|
|
||||||
.select_only()
|
|
||||||
.column(buffer_snapshot::Column::OperationSerializationVersion)
|
|
||||||
.filter(buffer_snapshot::Column::Epoch.eq(buffer.epoch))
|
|
||||||
.into_values::<_, QueryVersion>()
|
|
||||||
.one(&*tx)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| anyhow!("missing buffer snapshot"))?;
|
|
||||||
|
|
||||||
let operations = operations
|
let operations = operations
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -270,6 +364,38 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_buffer_operation_serialization_version(
|
||||||
|
&self,
|
||||||
|
buffer_id: BufferId,
|
||||||
|
epoch: i32,
|
||||||
|
tx: &DatabaseTransaction,
|
||||||
|
) -> Result<i32> {
|
||||||
|
Ok(buffer_snapshot::Entity::find()
|
||||||
|
.filter(buffer_snapshot::Column::BufferId.eq(buffer_id))
|
||||||
|
.filter(buffer_snapshot::Column::Epoch.eq(epoch))
|
||||||
|
.select_only()
|
||||||
|
.column(buffer_snapshot::Column::OperationSerializationVersion)
|
||||||
|
.into_values::<_, QueryOperationSerializationVersion>()
|
||||||
|
.one(&*tx)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| anyhow!("missing buffer snapshot"))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_channel_buffer(
|
||||||
|
&self,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
tx: &DatabaseTransaction,
|
||||||
|
) -> Result<buffer::Model> {
|
||||||
|
Ok(channel::Model {
|
||||||
|
id: channel_id,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.find_related(buffer::Entity)
|
||||||
|
.one(&*tx)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| anyhow!("no such buffer"))?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_buffer_state(
|
async fn get_buffer_state(
|
||||||
&self,
|
&self,
|
||||||
buffer: &buffer::Model,
|
buffer: &buffer::Model,
|
||||||
|
@ -303,27 +429,20 @@ impl Database {
|
||||||
.await?;
|
.await?;
|
||||||
let mut operations = Vec::new();
|
let mut operations = Vec::new();
|
||||||
while let Some(row) = rows.next().await {
|
while let Some(row) = rows.next().await {
|
||||||
let row = row?;
|
|
||||||
|
|
||||||
let operation = operation_from_storage(row, version)?;
|
|
||||||
operations.push(proto::Operation {
|
operations.push(proto::Operation {
|
||||||
variant: Some(operation),
|
variant: Some(operation_from_storage(row?, version)?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((base_text, operations))
|
Ok((base_text, operations))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn snapshot_buffer(&self, channel_id: ChannelId, tx: &DatabaseTransaction) -> Result<()> {
|
async fn snapshot_channel_buffer(
|
||||||
let buffer = channel::Model {
|
&self,
|
||||||
id: channel_id,
|
channel_id: ChannelId,
|
||||||
..Default::default()
|
tx: &DatabaseTransaction,
|
||||||
}
|
) -> Result<()> {
|
||||||
.find_related(buffer::Entity)
|
let buffer = self.get_channel_buffer(channel_id, tx).await?;
|
||||||
.one(&*tx)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| anyhow!("no such buffer"))?;
|
|
||||||
|
|
||||||
let (base_text, operations) = self.get_buffer_state(&buffer, tx).await?;
|
let (base_text, operations) = self.get_buffer_state(&buffer, tx).await?;
|
||||||
if operations.is_empty() {
|
if operations.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -527,6 +646,22 @@ fn version_from_wire(message: &[proto::VectorClockEntry]) -> clock::Global {
|
||||||
version
|
version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn version_to_wire(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
|
||||||
|
let mut message = Vec::new();
|
||||||
|
for entry in version.iter() {
|
||||||
|
message.push(proto::VectorClockEntry {
|
||||||
|
replica_id: entry.replica_id as u32,
|
||||||
|
timestamp: entry.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
message
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
||||||
|
enum QueryOperationSerializationVersion {
|
||||||
|
OperationSerializationVersion,
|
||||||
|
}
|
||||||
|
|
||||||
mod storage {
|
mod storage {
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
|
|
|
@ -251,6 +251,7 @@ impl Server {
|
||||||
.add_request_handler(join_channel_buffer)
|
.add_request_handler(join_channel_buffer)
|
||||||
.add_request_handler(leave_channel_buffer)
|
.add_request_handler(leave_channel_buffer)
|
||||||
.add_message_handler(update_channel_buffer)
|
.add_message_handler(update_channel_buffer)
|
||||||
|
.add_request_handler(rejoin_channel_buffers)
|
||||||
.add_request_handler(get_channel_members)
|
.add_request_handler(get_channel_members)
|
||||||
.add_request_handler(respond_to_channel_invite)
|
.add_request_handler(respond_to_channel_invite)
|
||||||
.add_request_handler(join_channel)
|
.add_request_handler(join_channel)
|
||||||
|
@ -854,13 +855,12 @@ async fn connection_lost(
|
||||||
.await
|
.await
|
||||||
.trace_err();
|
.trace_err();
|
||||||
|
|
||||||
leave_channel_buffers_for_session(&session)
|
|
||||||
.await
|
|
||||||
.trace_err();
|
|
||||||
|
|
||||||
futures::select_biased! {
|
futures::select_biased! {
|
||||||
_ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
|
_ = executor.sleep(RECONNECT_TIMEOUT).fuse() => {
|
||||||
leave_room_for_session(&session).await.trace_err();
|
leave_room_for_session(&session).await.trace_err();
|
||||||
|
leave_channel_buffers_for_session(&session)
|
||||||
|
.await
|
||||||
|
.trace_err();
|
||||||
|
|
||||||
if !session
|
if !session
|
||||||
.connection_pool()
|
.connection_pool()
|
||||||
|
@ -2547,6 +2547,23 @@ async fn update_channel_buffer(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn rejoin_channel_buffers(
|
||||||
|
request: proto::RejoinChannelBuffers,
|
||||||
|
response: Response<proto::RejoinChannelBuffers>,
|
||||||
|
session: Session,
|
||||||
|
) -> Result<()> {
|
||||||
|
let db = session.db().await;
|
||||||
|
let rejoin_response = db
|
||||||
|
.rejoin_channel_buffers(&request.buffers, session.user_id, session.connection_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// TODO: inform channel buffer collaborators that this user has rejoined.
|
||||||
|
|
||||||
|
response.send(rejoin_response)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn leave_channel_buffer(
|
async fn leave_channel_buffer(
|
||||||
request: proto::LeaveChannelBuffer,
|
request: proto::LeaveChannelBuffer,
|
||||||
response: Response<proto::LeaveChannelBuffer>,
|
response: Response<proto::LeaveChannelBuffer>,
|
||||||
|
|
|
@ -21,20 +21,19 @@ async fn test_core_channel_buffers(
|
||||||
let client_a = server.create_client(cx_a, "user_a").await;
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
let client_b = server.create_client(cx_b, "user_b").await;
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
|
||||||
let zed_id = server
|
let channel_id = server
|
||||||
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Client A joins the channel buffer
|
// Client A joins the channel buffer
|
||||||
let channel_buffer_a = client_a
|
let channel_buffer_a = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Client A edits the buffer
|
// Client A edits the buffer
|
||||||
let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
|
let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
|
||||||
|
|
||||||
buffer_a.update(cx_a, |buffer, cx| {
|
buffer_a.update(cx_a, |buffer, cx| {
|
||||||
buffer.edit([(0..0, "hello world")], None, cx)
|
buffer.edit([(0..0, "hello world")], None, cx)
|
||||||
});
|
});
|
||||||
|
@ -45,17 +44,15 @@ async fn test_core_channel_buffers(
|
||||||
buffer.edit([(0..5, "goodbye")], None, cx)
|
buffer.edit([(0..5, "goodbye")], None, cx)
|
||||||
});
|
});
|
||||||
buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
|
buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
|
||||||
deterministic.run_until_parked();
|
|
||||||
|
|
||||||
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
// Client B joins the channel buffer
|
// Client B joins the channel buffer
|
||||||
let channel_buffer_b = client_b
|
let channel_buffer_b = client_b
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_b, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
.update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
channel_buffer_b.read_with(cx_b, |buffer, _| {
|
||||||
assert_collaborators(
|
assert_collaborators(
|
||||||
buffer.collaborators(),
|
buffer.collaborators(),
|
||||||
|
@ -91,9 +88,7 @@ async fn test_core_channel_buffers(
|
||||||
// Client A rejoins the channel buffer
|
// Client A rejoins the channel buffer
|
||||||
let _channel_buffer_a = client_a
|
let _channel_buffer_a = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channels, cx| {
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
channels.open_channel_buffer(zed_id, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
|
@ -136,7 +131,7 @@ async fn test_channel_buffer_replica_ids(
|
||||||
|
|
||||||
let channel_id = server
|
let channel_id = server
|
||||||
.make_channel(
|
.make_channel(
|
||||||
"zed",
|
"the-channel",
|
||||||
(&client_a, cx_a),
|
(&client_a, cx_a),
|
||||||
&mut [(&client_b, cx_b), (&client_c, cx_c)],
|
&mut [(&client_b, cx_b), (&client_c, cx_c)],
|
||||||
)
|
)
|
||||||
|
@ -160,23 +155,17 @@ async fn test_channel_buffer_replica_ids(
|
||||||
// C first so that the replica IDs in the project and the channel buffer are different
|
// C first so that the replica IDs in the project and the channel buffer are different
|
||||||
let channel_buffer_c = client_c
|
let channel_buffer_c = client_c
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_c, |channel, cx| {
|
.update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
channel.open_channel_buffer(channel_id, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let channel_buffer_b = client_b
|
let channel_buffer_b = client_b
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_b, |channel, cx| {
|
.update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
channel.open_channel_buffer(channel_id, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let channel_buffer_a = client_a
|
let channel_buffer_a = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| {
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
channel.open_channel_buffer(channel_id, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -286,28 +275,30 @@ async fn test_reopen_channel_buffer(deterministic: Arc<Deterministic>, cx_a: &mu
|
||||||
let mut server = TestServer::start(&deterministic).await;
|
let mut server = TestServer::start(&deterministic).await;
|
||||||
let client_a = server.create_client(cx_a, "user_a").await;
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
|
||||||
let zed_id = server.make_channel("zed", (&client_a, cx_a), &mut []).await;
|
let channel_id = server
|
||||||
|
.make_channel("the-channel", (&client_a, cx_a), &mut [])
|
||||||
|
.await;
|
||||||
|
|
||||||
let channel_buffer_1 = client_a
|
let channel_buffer_1 = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx));
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
|
||||||
let channel_buffer_2 = client_a
|
let channel_buffer_2 = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx));
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
|
||||||
let channel_buffer_3 = client_a
|
let channel_buffer_3 = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx));
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
|
||||||
|
|
||||||
// All concurrent tasks for opening a channel buffer return the same model handle.
|
// All concurrent tasks for opening a channel buffer return the same model handle.
|
||||||
let (channel_buffer_1, channel_buffer_2, channel_buffer_3) =
|
let (channel_buffer, channel_buffer_2, channel_buffer_3) =
|
||||||
future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
|
future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let model_id = channel_buffer_1.id();
|
let channel_buffer_model_id = channel_buffer.id();
|
||||||
assert_eq!(channel_buffer_1, channel_buffer_2);
|
assert_eq!(channel_buffer, channel_buffer_2);
|
||||||
assert_eq!(channel_buffer_1, channel_buffer_3);
|
assert_eq!(channel_buffer, channel_buffer_3);
|
||||||
|
|
||||||
channel_buffer_1.update(cx_a, |buffer, cx| {
|
channel_buffer.update(cx_a, |buffer, cx| {
|
||||||
buffer.buffer().update(cx, |buffer, cx| {
|
buffer.buffer().update(cx, |buffer, cx| {
|
||||||
buffer.edit([(0..0, "hello")], None, cx);
|
buffer.edit([(0..0, "hello")], None, cx);
|
||||||
})
|
})
|
||||||
|
@ -315,7 +306,7 @@ async fn test_reopen_channel_buffer(deterministic: Arc<Deterministic>, cx_a: &mu
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
cx_a.update(|_| {
|
cx_a.update(|_| {
|
||||||
drop(channel_buffer_1);
|
drop(channel_buffer);
|
||||||
drop(channel_buffer_2);
|
drop(channel_buffer_2);
|
||||||
drop(channel_buffer_3);
|
drop(channel_buffer_3);
|
||||||
});
|
});
|
||||||
|
@ -324,10 +315,10 @@ async fn test_reopen_channel_buffer(deterministic: Arc<Deterministic>, cx_a: &mu
|
||||||
// The channel buffer can be reopened after dropping it.
|
// The channel buffer can be reopened after dropping it.
|
||||||
let channel_buffer = client_a
|
let channel_buffer = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| channel.open_channel_buffer(zed_id, cx))
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_ne!(channel_buffer.id(), model_id);
|
assert_ne!(channel_buffer.id(), channel_buffer_model_id);
|
||||||
channel_buffer.update(cx_a, |buffer, cx| {
|
channel_buffer.update(cx_a, |buffer, cx| {
|
||||||
buffer.buffer().update(cx, |buffer, _| {
|
buffer.buffer().update(cx, |buffer, _| {
|
||||||
assert_eq!(buffer.text(), "hello");
|
assert_eq!(buffer.text(), "hello");
|
||||||
|
@ -347,22 +338,17 @@ async fn test_channel_buffer_disconnect(
|
||||||
let client_b = server.create_client(cx_b, "user_b").await;
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
|
||||||
let channel_id = server
|
let channel_id = server
|
||||||
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
.make_channel("the-channel", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let channel_buffer_a = client_a
|
let channel_buffer_a = client_a
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_a, |channel, cx| {
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
channel.open_channel_buffer(channel_id, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let channel_buffer_b = client_b
|
let channel_buffer_b = client_b
|
||||||
.channel_store()
|
.channel_store()
|
||||||
.update(cx_b, |channel, cx| {
|
.update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
channel.open_channel_buffer(channel_id, cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -375,7 +361,7 @@ async fn test_channel_buffer_disconnect(
|
||||||
buffer.channel().as_ref(),
|
buffer.channel().as_ref(),
|
||||||
&Channel {
|
&Channel {
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "zed".to_string()
|
name: "the-channel".to_string()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert!(!buffer.is_connected());
|
assert!(!buffer.is_connected());
|
||||||
|
@ -403,13 +389,81 @@ async fn test_channel_buffer_disconnect(
|
||||||
buffer.channel().as_ref(),
|
buffer.channel().as_ref(),
|
||||||
&Channel {
|
&Channel {
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "zed".to_string()
|
name: "the-channel".to_string()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert!(!buffer.is_connected());
|
assert!(!buffer.is_connected());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_rejoin_channel_buffer(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx_a: &mut TestAppContext,
|
||||||
|
cx_b: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
let mut server = TestServer::start(&deterministic).await;
|
||||||
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
|
||||||
|
let channel_id = server
|
||||||
|
.make_channel("the-channel", (&client_a, cx_a), &mut [(&client_b, cx_b)])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let channel_buffer_a = client_a
|
||||||
|
.channel_store()
|
||||||
|
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let channel_buffer_b = client_b
|
||||||
|
.channel_store()
|
||||||
|
.update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||||
|
buffer.buffer().update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(0..0, "1")], None, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
|
// Client A disconnects.
|
||||||
|
server.forbid_connections();
|
||||||
|
server.disconnect_client(client_a.peer_id().unwrap());
|
||||||
|
// deterministic.advance_clock(RECEIVE_TIMEOUT);
|
||||||
|
|
||||||
|
// Both clients make an edit. Both clients see their own edit.
|
||||||
|
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||||
|
buffer.buffer().update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(1..1, "2")], None, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
channel_buffer_b.update(cx_b, |buffer, cx| {
|
||||||
|
buffer.buffer().update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(0..0, "0")], None, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
channel_buffer_a.read_with(cx_a, |buffer, cx| {
|
||||||
|
assert_eq!(buffer.buffer().read(cx).text(), "12");
|
||||||
|
});
|
||||||
|
channel_buffer_b.read_with(cx_b, |buffer, cx| {
|
||||||
|
assert_eq!(buffer.buffer().read(cx).text(), "01");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client A reconnects.
|
||||||
|
server.allow_connections();
|
||||||
|
deterministic.advance_clock(RECEIVE_TIMEOUT);
|
||||||
|
channel_buffer_a.read_with(cx_a, |buffer, cx| {
|
||||||
|
assert_eq!(buffer.buffer().read(cx).text(), "012");
|
||||||
|
});
|
||||||
|
channel_buffer_b.read_with(cx_b, |buffer, cx| {
|
||||||
|
assert_eq!(buffer.buffer().read(cx).text(), "012");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_collaborators(collaborators: &[proto::Collaborator], ids: &[Option<UserId>]) {
|
fn assert_collaborators(collaborators: &[proto::Collaborator], ids: &[Option<UserId>]) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -127,6 +127,31 @@ pub fn serialize_undo_map_entry(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn split_operations(
|
||||||
|
mut operations: Vec<proto::Operation>,
|
||||||
|
) -> impl Iterator<Item = Vec<proto::Operation>> {
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
const CHUNK_SIZE: usize = 5;
|
||||||
|
|
||||||
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
|
const CHUNK_SIZE: usize = 100;
|
||||||
|
|
||||||
|
let mut done = false;
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
if done {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let operations = operations
|
||||||
|
.drain(..std::cmp::min(CHUNK_SIZE, operations.len()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if operations.is_empty() {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
Some(operations)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
|
pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
|
||||||
selections.iter().map(serialize_selection).collect()
|
selections.iter().map(serialize_selection).collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ use language::{
|
||||||
point_to_lsp,
|
point_to_lsp,
|
||||||
proto::{
|
proto::{
|
||||||
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
|
||||||
serialize_anchor, serialize_version,
|
serialize_anchor, serialize_version, split_operations,
|
||||||
},
|
},
|
||||||
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
|
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
|
||||||
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
|
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
|
||||||
|
@ -8200,31 +8200,6 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_operations(
|
|
||||||
mut operations: Vec<proto::Operation>,
|
|
||||||
) -> impl Iterator<Item = Vec<proto::Operation>> {
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
const CHUNK_SIZE: usize = 5;
|
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
|
||||||
const CHUNK_SIZE: usize = 100;
|
|
||||||
|
|
||||||
let mut done = false;
|
|
||||||
std::iter::from_fn(move || {
|
|
||||||
if done {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let operations = operations
|
|
||||||
.drain(..cmp::min(CHUNK_SIZE, operations.len()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if operations.is_empty() {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
Some(operations)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
|
fn serialize_symbol(symbol: &Symbol) -> proto::Symbol {
|
||||||
proto::Symbol {
|
proto::Symbol {
|
||||||
language_server_name: symbol.language_server_name.0.to_string(),
|
language_server_name: symbol.language_server_name.0.to_string(),
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
package zed.messages;
|
package zed.messages;
|
||||||
|
|
||||||
|
// Looking for a number? Search "// Current max"
|
||||||
|
|
||||||
message PeerId {
|
message PeerId {
|
||||||
uint32 owner_id = 1;
|
uint32 owner_id = 1;
|
||||||
uint32 id = 2;
|
uint32 id = 2;
|
||||||
|
@ -151,6 +153,8 @@ message Envelope {
|
||||||
LeaveChannelBuffer leave_channel_buffer = 134;
|
LeaveChannelBuffer leave_channel_buffer = 134;
|
||||||
AddChannelBufferCollaborator add_channel_buffer_collaborator = 135;
|
AddChannelBufferCollaborator add_channel_buffer_collaborator = 135;
|
||||||
RemoveChannelBufferCollaborator remove_channel_buffer_collaborator = 136;
|
RemoveChannelBufferCollaborator remove_channel_buffer_collaborator = 136;
|
||||||
|
RejoinChannelBuffers rejoin_channel_buffers = 139;
|
||||||
|
RejoinChannelBuffersResponse rejoin_channel_buffers_response = 140; // Current max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,6 +620,12 @@ message BufferVersion {
|
||||||
repeated VectorClockEntry version = 2;
|
repeated VectorClockEntry version = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ChannelBufferVersion {
|
||||||
|
uint64 channel_id = 1;
|
||||||
|
repeated VectorClockEntry version = 2;
|
||||||
|
uint64 epoch = 3;
|
||||||
|
}
|
||||||
|
|
||||||
enum FormatTrigger {
|
enum FormatTrigger {
|
||||||
Save = 0;
|
Save = 0;
|
||||||
Manual = 1;
|
Manual = 1;
|
||||||
|
@ -1008,12 +1018,28 @@ message JoinChannelBuffer {
|
||||||
uint64 channel_id = 1;
|
uint64 channel_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RejoinChannelBuffers {
|
||||||
|
repeated ChannelBufferVersion buffers = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RejoinChannelBuffersResponse {
|
||||||
|
repeated RejoinedChannelBuffer buffers = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message JoinChannelBufferResponse {
|
message JoinChannelBufferResponse {
|
||||||
uint64 buffer_id = 1;
|
uint64 buffer_id = 1;
|
||||||
uint32 replica_id = 2;
|
uint32 replica_id = 2;
|
||||||
string base_text = 3;
|
string base_text = 3;
|
||||||
repeated Operation operations = 4;
|
repeated Operation operations = 4;
|
||||||
repeated Collaborator collaborators = 5;
|
repeated Collaborator collaborators = 5;
|
||||||
|
uint64 epoch = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RejoinedChannelBuffer {
|
||||||
|
uint64 channel_id = 1;
|
||||||
|
repeated VectorClockEntry version = 2;
|
||||||
|
repeated Operation operations = 3;
|
||||||
|
repeated Collaborator collaborators = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LeaveChannelBuffer {
|
message LeaveChannelBuffer {
|
||||||
|
|
|
@ -229,6 +229,8 @@ messages!(
|
||||||
(StartLanguageServer, Foreground),
|
(StartLanguageServer, Foreground),
|
||||||
(SynchronizeBuffers, Foreground),
|
(SynchronizeBuffers, Foreground),
|
||||||
(SynchronizeBuffersResponse, Foreground),
|
(SynchronizeBuffersResponse, Foreground),
|
||||||
|
(RejoinChannelBuffers, Foreground),
|
||||||
|
(RejoinChannelBuffersResponse, Foreground),
|
||||||
(Test, Foreground),
|
(Test, Foreground),
|
||||||
(Unfollow, Foreground),
|
(Unfollow, Foreground),
|
||||||
(UnshareProject, Foreground),
|
(UnshareProject, Foreground),
|
||||||
|
@ -319,6 +321,7 @@ request_messages!(
|
||||||
(SearchProject, SearchProjectResponse),
|
(SearchProject, SearchProjectResponse),
|
||||||
(ShareProject, ShareProjectResponse),
|
(ShareProject, ShareProjectResponse),
|
||||||
(SynchronizeBuffers, SynchronizeBuffersResponse),
|
(SynchronizeBuffers, SynchronizeBuffersResponse),
|
||||||
|
(RejoinChannelBuffers, RejoinChannelBuffersResponse),
|
||||||
(Test, Test),
|
(Test, Test),
|
||||||
(UpdateBuffer, Ack),
|
(UpdateBuffer, Ack),
|
||||||
(UpdateParticipantLocation, Ack),
|
(UpdateParticipantLocation, Ack),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue