Introduce an epoch to ConnectionId and PeerId

This commit is contained in:
Antonio Scandurra 2022-12-14 15:55:56 +01:00
parent 9bd400cf16
commit 05e99eb67e
24 changed files with 714 additions and 382 deletions

View file

@ -44,7 +44,7 @@ CREATE TABLE "projects" (
"room_id" INTEGER REFERENCES rooms (id) NOT NULL,
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
"host_connection_id" INTEGER NOT NULL,
"host_connection_epoch" TEXT NOT NULL,
"host_connection_epoch" INTEGER NOT NULL,
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX "index_projects_on_host_connection_epoch" ON "projects" ("host_connection_epoch");
@ -103,7 +103,7 @@ CREATE TABLE "project_collaborators" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
"connection_id" INTEGER NOT NULL,
"connection_epoch" TEXT NOT NULL,
"connection_epoch" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"replica_id" INTEGER NOT NULL,
"is_host" BOOLEAN NOT NULL
@ -119,14 +119,14 @@ CREATE TABLE "room_participants" (
"room_id" INTEGER NOT NULL REFERENCES rooms (id),
"user_id" INTEGER NOT NULL REFERENCES users (id),
"answering_connection_id" INTEGER,
"answering_connection_epoch" TEXT,
"answering_connection_epoch" INTEGER,
"answering_connection_lost" BOOLEAN NOT NULL,
"location_kind" INTEGER,
"location_project_id" INTEGER,
"initial_project_id" INTEGER,
"calling_user_id" INTEGER NOT NULL REFERENCES users (id),
"calling_connection_id" INTEGER NOT NULL,
"calling_connection_epoch" TEXT NOT NULL
"calling_connection_epoch" INTEGER NOT NULL
);
CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");

View file

@ -0,0 +1,9 @@
ALTER TABLE "projects"
ALTER COLUMN "host_connection_epoch" TYPE INTEGER USING 0;
ALTER TABLE "project_collaborators"
ALTER COLUMN "connection_epoch" TYPE INTEGER USING 0;
ALTER TABLE "room_participants"
ALTER COLUMN "answering_connection_epoch" TYPE INTEGER USING 0,
ALTER COLUMN "calling_connection_epoch" TYPE INTEGER USING 0;

View file

@ -1076,7 +1076,7 @@ impl Database {
pub async fn create_room(
&self,
user_id: UserId,
connection_id: ConnectionId,
connection: ConnectionId,
live_kit_room: &str,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(|tx| async move {
@ -1091,12 +1091,12 @@ impl Database {
room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(user_id),
answering_connection_id: ActiveValue::set(Some(connection_id.0 as i32)),
answering_connection_epoch: ActiveValue::set(Some(self.epoch())),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_epoch: ActiveValue::set(Some(connection.epoch as i32)),
answering_connection_lost: ActiveValue::set(false),
calling_user_id: ActiveValue::set(user_id),
calling_connection_id: ActiveValue::set(connection_id.0 as i32),
calling_connection_epoch: ActiveValue::set(self.epoch()),
calling_connection_id: ActiveValue::set(connection.id as i32),
calling_connection_epoch: ActiveValue::set(connection.epoch as i32),
..Default::default()
}
.insert(&*tx)
@ -1112,7 +1112,7 @@ impl Database {
&self,
room_id: RoomId,
calling_user_id: UserId,
calling_connection_id: ConnectionId,
calling_connection: ConnectionId,
called_user_id: UserId,
initial_project_id: Option<ProjectId>,
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
@ -1122,8 +1122,8 @@ impl Database {
user_id: ActiveValue::set(called_user_id),
answering_connection_lost: ActiveValue::set(false),
calling_user_id: ActiveValue::set(calling_user_id),
calling_connection_id: ActiveValue::set(calling_connection_id.0 as i32),
calling_connection_epoch: ActiveValue::set(self.epoch()),
calling_connection_id: ActiveValue::set(calling_connection.id as i32),
calling_connection_epoch: ActiveValue::set(calling_connection.epoch as i32),
initial_project_id: ActiveValue::set(initial_project_id),
..Default::default()
}
@ -1192,19 +1192,23 @@ impl Database {
pub async fn cancel_call(
&self,
expected_room_id: Option<RoomId>,
calling_connection_id: ConnectionId,
calling_connection: ConnectionId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(
room_participant::Column::UserId
.eq(called_user_id)
.and(
Condition::all()
.add(room_participant::Column::UserId.eq(called_user_id))
.add(
room_participant::Column::CallingConnectionId
.eq(calling_connection_id.0 as i32),
.eq(calling_connection.id as i32),
)
.and(room_participant::Column::AnsweringConnectionId.is_null()),
.add(
room_participant::Column::CallingConnectionEpoch
.eq(calling_connection.epoch as i32),
)
.add(room_participant::Column::AnsweringConnectionId.is_null()),
)
.one(&*tx)
.await?
@ -1228,7 +1232,7 @@ impl Database {
&self,
room_id: RoomId,
user_id: UserId,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(|tx| async move {
let result = room_participant::Entity::update_many()
@ -1242,13 +1246,13 @@ impl Database {
.add(room_participant::Column::AnsweringConnectionLost.eq(true))
.add(
room_participant::Column::AnsweringConnectionEpoch
.ne(self.epoch()),
.ne(connection.epoch as i32),
),
),
)
.set(room_participant::ActiveModel {
answering_connection_id: ActiveValue::set(Some(connection_id.0 as i32)),
answering_connection_epoch: ActiveValue::set(Some(self.epoch())),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_epoch: ActiveValue::set(Some(connection.epoch as i32)),
answering_connection_lost: ActiveValue::set(false),
..Default::default()
})
@ -1264,10 +1268,20 @@ impl Database {
.await
}
pub async fn leave_room(&self, connection_id: ConnectionId) -> Result<RoomGuard<LeftRoom>> {
pub async fn leave_room(&self, connection: ConnectionId) -> Result<RoomGuard<LeftRoom>> {
self.room_transaction(|tx| async move {
let leaving_participant = room_participant::Entity::find()
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionEpoch
.eq(connection.epoch as i32),
),
)
.one(&*tx)
.await?;
@ -1281,9 +1295,16 @@ impl Database {
// Cancel pending calls initiated by the leaving user.
let called_participants = room_participant::Entity::find()
.filter(
room_participant::Column::CallingConnectionId
.eq(connection_id.0)
.and(room_participant::Column::AnsweringConnectionId.is_null()),
Condition::all()
.add(
room_participant::Column::CallingConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::CallingConnectionEpoch
.eq(connection.epoch as i32),
)
.add(room_participant::Column::AnsweringConnectionId.is_null()),
)
.all(&*tx)
.await?;
@ -1310,7 +1331,16 @@ impl Database {
project_collaborator::Column::ProjectId,
QueryProjectIds::ProjectId,
)
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(
project_collaborator::Column::ConnectionId.eq(connection.id as i32),
)
.add(
project_collaborator::Column::ConnectionEpoch
.eq(connection.epoch as i32),
),
)
.into_values::<_, QueryProjectIds>()
.all(&*tx)
.await?;
@ -1331,32 +1361,43 @@ impl Database {
host_connection_id: Default::default(),
});
let collaborator_connection_id =
ConnectionId(collaborator.connection_id as u32);
if collaborator_connection_id != connection_id {
let collaborator_connection_id = ConnectionId {
epoch: collaborator.connection_epoch as u32,
id: collaborator.connection_id as u32,
};
if collaborator_connection_id != connection {
left_project.connection_ids.push(collaborator_connection_id);
}
if collaborator.is_host {
left_project.host_user_id = collaborator.user_id;
left_project.host_connection_id =
ConnectionId(collaborator.connection_id as u32);
left_project.host_connection_id = collaborator_connection_id;
}
}
drop(collaborators);
// Leave projects.
project_collaborator::Entity::delete_many()
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(
project_collaborator::Column::ConnectionId.eq(connection.id as i32),
)
.add(
project_collaborator::Column::ConnectionEpoch
.eq(connection.epoch as i32),
),
)
.exec(&*tx)
.await?;
// Unshare projects.
project::Entity::delete_many()
.filter(
project::Column::RoomId
.eq(room_id)
.and(project::Column::HostConnectionId.eq(connection_id.0 as i32)),
Condition::all()
.add(project::Column::RoomId.eq(room_id))
.add(project::Column::HostConnectionId.eq(connection.id as i32))
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
)
.exec(&*tx)
.await?;
@ -1387,7 +1428,7 @@ impl Database {
pub async fn update_room_participant_location(
&self,
room_id: RoomId,
connection_id: ConnectionId,
connection: ConnectionId,
location: proto::ParticipantLocation,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(|tx| async {
@ -1414,9 +1455,18 @@ impl Database {
}
let result = room_participant::Entity::update_many()
.filter(room_participant::Column::RoomId.eq(room_id).and(
room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32),
))
.filter(
Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionEpoch
.eq(connection.epoch as i32),
),
)
.set(room_participant::ActiveModel {
location_kind: ActiveValue::set(Some(location_kind)),
location_project_id: ActiveValue::set(location_project_id),
@ -1437,11 +1487,21 @@ impl Database {
pub async fn connection_lost(
&self,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<LeftProject>>> {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionEpoch
.eq(connection.epoch as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("not a participant in any room"))?;
@ -1456,11 +1516,25 @@ impl Database {
let collaborator_on_projects = project_collaborator::Entity::find()
.find_also_related(project::Entity)
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_collaborator::Column::ConnectionEpoch
.eq(connection.epoch as i32),
),
)
.all(&*tx)
.await?;
project_collaborator::Entity::delete_many()
.filter(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_collaborator::Column::ConnectionEpoch
.eq(connection.epoch as i32),
),
)
.exec(&*tx)
.await?;
@ -1473,20 +1547,30 @@ impl Database {
.await?;
let connection_ids = collaborators
.into_iter()
.map(|collaborator| ConnectionId(collaborator.connection_id as u32))
.map(|collaborator| ConnectionId {
id: collaborator.connection_id as u32,
epoch: collaborator.connection_epoch as u32,
})
.collect();
left_projects.push(LeftProject {
id: project.id,
host_user_id: project.host_user_id,
host_connection_id: ConnectionId(project.host_connection_id as u32),
host_connection_id: ConnectionId {
id: project.host_connection_id as u32,
epoch: project.host_connection_epoch as u32,
},
connection_ids,
});
}
}
project::Entity::delete_many()
.filter(project::Column::HostConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
)
.exec(&*tx)
.await?;
@ -1537,7 +1621,10 @@ impl Database {
let mut pending_participants = Vec::new();
while let Some(db_participant) = db_participants.next().await {
let db_participant = db_participant?;
if let Some(answering_connection_id) = db_participant.answering_connection_id {
if let Some((answering_connection_id, answering_connection_epoch)) = db_participant
.answering_connection_id
.zip(db_participant.answering_connection_epoch)
{
let location = match (
db_participant.location_kind,
db_participant.location_project_id,
@ -1560,7 +1647,10 @@ impl Database {
answering_connection_id,
proto::Participant {
user_id: db_participant.user_id.to_proto(),
peer_id: answering_connection_id as u32,
peer_id: Some(proto::PeerId {
epoch: answering_connection_epoch as u32,
id: answering_connection_id as u32,
}),
projects: Default::default(),
location: Some(proto::ParticipantLocation { variant: location }),
},
@ -1637,12 +1727,22 @@ impl Database {
pub async fn share_project(
&self,
room_id: RoomId,
connection_id: ConnectionId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionEpoch
.eq(connection.epoch as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("could not find participant"))?;
@ -1653,8 +1753,8 @@ impl Database {
let project = project::ActiveModel {
room_id: ActiveValue::set(participant.room_id),
host_user_id: ActiveValue::set(participant.user_id),
host_connection_id: ActiveValue::set(connection_id.0 as i32),
host_connection_epoch: ActiveValue::set(self.epoch()),
host_connection_id: ActiveValue::set(connection.id as i32),
host_connection_epoch: ActiveValue::set(connection.epoch as i32),
..Default::default()
}
.insert(&*tx)
@ -1678,8 +1778,8 @@ impl Database {
project_collaborator::ActiveModel {
project_id: ActiveValue::set(project.id),
connection_id: ActiveValue::set(connection_id.0 as i32),
connection_epoch: ActiveValue::set(self.epoch()),
connection_id: ActiveValue::set(connection.id as i32),
connection_epoch: ActiveValue::set(connection.epoch as i32),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(ReplicaId(0)),
is_host: ActiveValue::set(true),
@ -1697,7 +1797,7 @@ impl Database {
pub async fn unshare_project(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
self.room_transaction(|tx| async move {
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
@ -1706,7 +1806,11 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project not found"))?;
if project.host_connection_id == connection_id.0 as i32 {
let host_connection = ConnectionId {
epoch: project.host_connection_epoch as u32,
id: project.host_connection_id as u32,
};
if host_connection == connection {
let room_id = project.room_id;
project::Entity::delete(project.into_active_model())
.exec(&*tx)
@ -1723,12 +1827,16 @@ impl Database {
pub async fn update_project(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
self.room_transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.filter(project::Column::HostConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
@ -1774,7 +1882,7 @@ impl Database {
pub async fn update_worktree(
&self,
update: &proto::UpdateWorktree,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
self.room_transaction(|tx| async move {
let project_id = ProjectId::from_proto(update.project_id);
@ -1782,7 +1890,11 @@ impl Database {
// Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id)
.filter(project::Column::HostConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
.add(project::Column::HostConnectionEpoch.eq(connection.epoch as i32)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
@ -1862,7 +1974,7 @@ impl Database {
pub async fn update_diagnostic_summary(
&self,
update: &proto::UpdateDiagnosticSummary,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
self.room_transaction(|tx| async move {
let project_id = ProjectId::from_proto(update.project_id);
@ -1877,7 +1989,11 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.host_connection_id != connection_id.0 as i32 {
let host_connection = ConnectionId {
epoch: project.host_connection_epoch as u32,
id: project.host_connection_id as u32,
};
if host_connection != connection {
return Err(anyhow!("can't update a project hosted by someone else"))?;
}
@ -1916,7 +2032,7 @@ impl Database {
pub async fn start_language_server(
&self,
update: &proto::StartLanguageServer,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
self.room_transaction(|tx| async move {
let project_id = ProjectId::from_proto(update.project_id);
@ -1930,7 +2046,11 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.host_connection_id != connection_id.0 as i32 {
let host_connection = ConnectionId {
epoch: project.host_connection_epoch as u32,
id: project.host_connection_id as u32,
};
if host_connection != connection {
return Err(anyhow!("can't update a project hosted by someone else"))?;
}
@ -1961,11 +2081,21 @@ impl Database {
pub async fn join_project(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<(Project, ReplicaId)>> {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.0 as i32))
.filter(
Condition::all()
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionEpoch
.eq(connection.epoch as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("must join a room first"))?;
@ -1992,8 +2122,8 @@ impl Database {
}
let new_collaborator = project_collaborator::ActiveModel {
project_id: ActiveValue::set(project_id),
connection_id: ActiveValue::set(connection_id.0 as i32),
connection_epoch: ActiveValue::set(self.epoch()),
connection_id: ActiveValue::set(connection.id as i32),
connection_epoch: ActiveValue::set(connection.epoch as i32),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(replica_id),
is_host: ActiveValue::set(false),
@ -2095,14 +2225,18 @@ impl Database {
pub async fn leave_project(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<LeftProject>> {
self.room_transaction(|tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
project_collaborator::Column::ProjectId
.eq(project_id)
.and(project_collaborator::Column::ConnectionId.eq(connection_id.0 as i32)),
Condition::all()
.add(project_collaborator::Column::ProjectId.eq(project_id))
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_collaborator::Column::ConnectionEpoch
.eq(connection.epoch as i32),
),
)
.exec(&*tx)
.await?;
@ -2120,13 +2254,19 @@ impl Database {
.await?;
let connection_ids = collaborators
.into_iter()
.map(|collaborator| ConnectionId(collaborator.connection_id as u32))
.map(|collaborator| ConnectionId {
epoch: collaborator.connection_epoch as u32,
id: collaborator.connection_id as u32,
})
.collect();
let left_project = LeftProject {
id: project_id,
host_user_id: project.host_user_id,
host_connection_id: ConnectionId(project.host_connection_id as u32),
host_connection_id: ConnectionId {
epoch: project.host_connection_epoch as u32,
id: project.host_connection_id as u32,
},
connection_ids,
};
Ok((project.room_id, left_project))
@ -2137,7 +2277,7 @@ impl Database {
pub async fn project_collaborators(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<project_collaborator::Model>>> {
self.room_transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
@ -2149,10 +2289,13 @@ impl Database {
.all(&*tx)
.await?;
if collaborators
.iter()
.any(|collaborator| collaborator.connection_id == connection_id.0 as i32)
{
if collaborators.iter().any(|collaborator| {
let collaborator_connection = ConnectionId {
epoch: collaborator.connection_epoch as u32,
id: collaborator.connection_id as u32,
};
collaborator_connection == connection
}) {
Ok((project.room_id, collaborators))
} else {
Err(anyhow!("no such project"))?
@ -2166,30 +2309,42 @@ impl Database {
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
self.room_transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
ConnectionId,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
Epoch,
Id,
}
#[derive(Debug, FromQueryResult)]
struct ProjectConnection {
epoch: i32,
id: i32,
}
self.room_transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let mut db_connection_ids = project_collaborator::Entity::find()
let mut db_connections = project_collaborator::Entity::find()
.select_only()
.column_as(project_collaborator::Column::ConnectionId, QueryAs::Id)
.column_as(
project_collaborator::Column::ConnectionId,
QueryAs::ConnectionId,
project_collaborator::Column::ConnectionEpoch,
QueryAs::Epoch,
)
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.into_values::<i32, QueryAs>()
.into_model::<ProjectConnection>()
.stream(&*tx)
.await?;
let mut connection_ids = HashSet::default();
while let Some(connection_id) = db_connection_ids.next().await {
connection_ids.insert(ConnectionId(connection_id? as u32));
while let Some(connection) = db_connections.next().await {
let connection = connection?;
connection_ids.insert(ConnectionId {
epoch: connection.epoch as u32,
id: connection.id as u32,
});
}
if connection_ids.contains(&connection_id) {
@ -2208,27 +2363,39 @@ impl Database {
) -> Result<Vec<ConnectionId>> {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
ConnectionId,
Epoch,
Id,
}
let mut db_guest_connection_ids = project_collaborator::Entity::find()
#[derive(Debug, FromQueryResult)]
struct ProjectConnection {
epoch: i32,
id: i32,
}
let mut db_guest_connections = project_collaborator::Entity::find()
.select_only()
.column_as(project_collaborator::Column::ConnectionId, QueryAs::Id)
.column_as(
project_collaborator::Column::ConnectionId,
QueryAs::ConnectionId,
project_collaborator::Column::ConnectionEpoch,
QueryAs::Epoch,
)
.filter(
project_collaborator::Column::ProjectId
.eq(project_id)
.and(project_collaborator::Column::IsHost.eq(false)),
)
.into_values::<i32, QueryAs>()
.into_model::<ProjectConnection>()
.stream(tx)
.await?;
let mut guest_connection_ids = Vec::new();
while let Some(connection_id) = db_guest_connection_ids.next().await {
guest_connection_ids.push(ConnectionId(connection_id? as u32));
while let Some(connection) = db_guest_connections.next().await {
let connection = connection?;
guest_connection_ids.push(ConnectionId {
epoch: connection.epoch as u32,
id: connection.id as u32,
});
}
Ok(guest_connection_ids)
}

View file

@ -9,7 +9,7 @@ pub struct Model {
pub room_id: RoomId,
pub host_user_id: UserId,
pub host_connection_id: i32,
pub host_connection_epoch: Uuid,
pub host_connection_epoch: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View file

@ -8,7 +8,7 @@ pub struct Model {
pub id: ProjectCollaboratorId,
pub project_id: ProjectId,
pub connection_id: i32,
pub connection_epoch: Uuid,
pub connection_epoch: i32,
pub user_id: UserId,
pub replica_id: ReplicaId,
pub is_host: bool,

View file

@ -9,14 +9,14 @@ pub struct Model {
pub room_id: RoomId,
pub user_id: UserId,
pub answering_connection_id: Option<i32>,
pub answering_connection_epoch: Option<Uuid>,
pub answering_connection_epoch: Option<i32>,
pub answering_connection_lost: bool,
pub location_kind: Option<i32>,
pub location_project_id: Option<ProjectId>,
pub initial_project_id: Option<ProjectId>,
pub calling_user_id: UserId,
pub calling_connection_id: i32,
pub calling_connection_epoch: Uuid,
pub calling_connection_epoch: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View file

@ -436,36 +436,44 @@ test_both_dbs!(
.unwrap();
let room_id = RoomId::from_proto(
db.create_room(user1.user_id, ConnectionId(0), "")
db.create_room(user1.user_id, ConnectionId { epoch: 0, id: 0 }, "")
.await
.unwrap()
.id,
);
db.call(room_id, user1.user_id, ConnectionId(0), user2.user_id, None)
.await
.unwrap();
db.join_room(room_id, user2.user_id, ConnectionId(1))
db.call(
room_id,
user1.user_id,
ConnectionId { epoch: 0, id: 0 },
user2.user_id,
None,
)
.await
.unwrap();
db.join_room(room_id, user2.user_id, ConnectionId { epoch: 0, id: 1 })
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
db.share_project(room_id, ConnectionId(1), &[])
db.share_project(room_id, ConnectionId { epoch: 0, id: 1 }, &[])
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
db.share_project(room_id, ConnectionId(1), &[])
db.share_project(room_id, ConnectionId { epoch: 0, id: 1 }, &[])
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
// Projects shared by admins aren't counted.
db.share_project(room_id, ConnectionId(0), &[])
db.share_project(room_id, ConnectionId { epoch: 0, id: 0 }, &[])
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
db.leave_room(ConnectionId(1)).await.unwrap();
db.leave_room(ConnectionId { epoch: 0, id: 1 })
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
}
);

View file

@ -7,8 +7,8 @@ use crate::{
use anyhow::anyhow;
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{
self, test::FakeHttpClient, Client, Connection, Credentials, EstablishConnectionError, PeerId,
User, UserStore, RECEIVE_TIMEOUT,
self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials,
EstablishConnectionError, User, UserStore, RECEIVE_TIMEOUT,
};
use collections::{BTreeMap, HashMap, HashSet};
use editor::{
@ -6066,7 +6066,7 @@ async fn test_random_collaboration(
.user_connection_ids(removed_guest_id)
.collect::<Vec<_>>();
assert_eq!(user_connection_ids.len(), 1);
let removed_peer_id = PeerId(user_connection_ids[0].0);
let removed_peer_id = user_connection_ids[0].into();
let guest = clients.remove(guest_ix);
op_start_signals.remove(guest_ix);
server.forbid_connections();
@ -6115,7 +6115,7 @@ async fn test_random_collaboration(
.user_connection_ids(user_id)
.collect::<Vec<_>>();
assert_eq!(user_connection_ids.len(), 1);
let peer_id = PeerId(user_connection_ids[0].0);
let peer_id = user_connection_ids[0].into();
server.disconnect_client(peer_id);
deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
operations += 1;
@ -6429,7 +6429,7 @@ impl TestServer {
let connection_id = connection_id_rx.await.unwrap();
connection_killers
.lock()
.insert(PeerId(connection_id.0), killed);
.insert(connection_id.into(), killed);
Ok(client_conn)
}
})

View file

@ -170,7 +170,7 @@ where
impl Server {
pub fn new(app_state: Arc<AppState>, executor: Executor) -> Arc<Self> {
let mut server = Self {
peer: Peer::new(),
peer: Peer::new(0),
app_state,
executor,
connection_pool: Default::default(),
@ -457,9 +457,9 @@ impl Server {
move |duration| executor.sleep(duration)
});
tracing::info!(%user_id, %login, %connection_id, %address, "connection opened");
this.peer.send(connection_id, proto::Hello { peer_id: connection_id.0 })?;
tracing::info!(%user_id, %login, %connection_id, %address, "sent hello message");
tracing::info!(%user_id, %login, ?connection_id, %address, "connection opened");
this.peer.send(connection_id, proto::Hello { peer_id: Some(connection_id.into()) })?;
tracing::info!(%user_id, %login, ?connection_id, %address, "sent hello message");
if let Some(send_connection_id) = send_connection_id.take() {
let _ = send_connection_id.send(connection_id);
@ -521,7 +521,7 @@ impl Server {
_ = teardown.changed().fuse() => return Ok(()),
result = handle_io => {
if let Err(error) = result {
tracing::error!(?error, %user_id, %login, %connection_id, %address, "error handling I/O");
tracing::error!(?error, %user_id, %login, ?connection_id, %address, "error handling I/O");
}
break;
}
@ -529,7 +529,7 @@ impl Server {
message = next_message => {
if let Some(message) = message {
let type_name = message.payload_type_name();
let span = tracing::info_span!("receive message", %user_id, %login, %connection_id, %address, type_name);
let span = tracing::info_span!("receive message", %user_id, %login, ?connection_id, %address, type_name);
let span_enter = span.enter();
if let Some(handler) = this.handlers.get(&message.payload_type_id()) {
let is_background = message.is_background();
@ -543,10 +543,10 @@ impl Server {
foreground_message_handlers.push(handle_message);
}
} else {
tracing::error!(%user_id, %login, %connection_id, %address, "no message handler");
tracing::error!(%user_id, %login, ?connection_id, %address, "no message handler");
}
} else {
tracing::info!(%user_id, %login, %connection_id, %address, "connection closed");
tracing::info!(%user_id, %login, ?connection_id, %address, "connection closed");
break;
}
}
@ -554,9 +554,9 @@ impl Server {
}
drop(foreground_message_handlers);
tracing::info!(%user_id, %login, %connection_id, %address, "signing out");
tracing::info!(%user_id, %login, ?connection_id, %address, "signing out");
if let Err(error) = sign_out(session, teardown, executor).await {
tracing::error!(%user_id, %login, %connection_id, %address, ?error, "error signing out");
tracing::error!(%user_id, %login, ?connection_id, %address, ?error, "error signing out");
}
Ok(())
@ -1128,12 +1128,18 @@ async fn join_project(
let collaborators = project
.collaborators
.iter()
.filter(|collaborator| collaborator.connection_id != session.connection_id.0 as i32)
.map(|collaborator| proto::Collaborator {
peer_id: collaborator.connection_id as u32,
replica_id: collaborator.replica_id.0 as u32,
user_id: collaborator.user_id.to_proto(),
.map(|collaborator| {
let peer_id = proto::PeerId {
epoch: collaborator.connection_epoch as u32,
id: collaborator.connection_id as u32,
};
proto::Collaborator {
peer_id: Some(peer_id),
replica_id: collaborator.replica_id.0 as u32,
user_id: collaborator.user_id.to_proto(),
}
})
.filter(|collaborator| collaborator.peer_id != Some(session.connection_id.into()))
.collect::<Vec<_>>();
let worktrees = project
.worktrees
@ -1150,11 +1156,11 @@ async fn join_project(
session
.peer
.send(
ConnectionId(collaborator.peer_id),
collaborator.peer_id.unwrap().into(),
proto::AddProjectCollaborator {
project_id: project_id.to_proto(),
collaborator: Some(proto::Collaborator {
peer_id: session.connection_id.0,
peer_id: Some(session.connection_id.into()),
replica_id: replica_id.0 as u32,
user_id: guest_user_id.to_proto(),
}),
@ -1375,13 +1381,14 @@ where
.await
.project_collaborators(project_id, session.connection_id)
.await?;
ConnectionId(
collaborators
.iter()
.find(|collaborator| collaborator.is_host)
.ok_or_else(|| anyhow!("host not found"))?
.connection_id as u32,
)
let host = collaborators
.iter()
.find(|collaborator| collaborator.is_host)
.ok_or_else(|| anyhow!("host not found"))?;
ConnectionId {
epoch: host.connection_epoch as u32,
id: host.connection_id as u32,
}
};
let payload = session
@ -1409,7 +1416,10 @@ async fn save_buffer(
.iter()
.find(|collaborator| collaborator.is_host)
.ok_or_else(|| anyhow!("host not found"))?;
ConnectionId(host.connection_id as u32)
ConnectionId {
epoch: host.connection_epoch as u32,
id: host.connection_id as u32,
}
};
let response_payload = session
.peer
@ -1421,11 +1431,17 @@ async fn save_buffer(
.await
.project_collaborators(project_id, session.connection_id)
.await?;
collaborators
.retain(|collaborator| collaborator.connection_id != session.connection_id.0 as i32);
let project_connection_ids = collaborators
.iter()
.map(|collaborator| ConnectionId(collaborator.connection_id as u32));
collaborators.retain(|collaborator| {
let collaborator_connection = ConnectionId {
epoch: collaborator.connection_epoch as u32,
id: collaborator.connection_id as u32,
};
collaborator_connection != session.connection_id
});
let project_connection_ids = collaborators.iter().map(|collaborator| ConnectionId {
epoch: collaborator.connection_epoch as u32,
id: collaborator.connection_id as u32,
});
broadcast(host_connection_id, project_connection_ids, |conn_id| {
session
.peer
@ -1439,11 +1455,10 @@ async fn create_buffer_for_peer(
request: proto::CreateBufferForPeer,
session: Session,
) -> Result<()> {
session.peer.forward_send(
session.connection_id,
ConnectionId(request.peer_id),
request,
)?;
let peer_id = request.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?;
session
.peer
.forward_send(session.connection_id, peer_id.into(), request)?;
Ok(())
}
@ -1536,7 +1551,10 @@ async fn follow(
session: Session,
) -> Result<()> {
let project_id = ProjectId::from_proto(request.project_id);
let leader_id = ConnectionId(request.leader_id);
let leader_id = request
.leader_id
.ok_or_else(|| anyhow!("invalid leader id"))?
.into();
let follower_id = session.connection_id;
{
let project_connection_ids = session
@ -1556,14 +1574,17 @@ async fn follow(
.await?;
response_payload
.views
.retain(|view| view.leader_id != Some(follower_id.0));
.retain(|view| view.leader_id != Some(follower_id.into()));
response.send(response_payload)?;
Ok(())
}
async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> {
let project_id = ProjectId::from_proto(request.project_id);
let leader_id = ConnectionId(request.leader_id);
let leader_id = request
.leader_id
.ok_or_else(|| anyhow!("invalid leader id"))?
.into();
let project_connection_ids = session
.db()
.await
@ -1592,12 +1613,16 @@ async fn update_followers(request: proto::UpdateFollowers, session: Session) ->
proto::update_followers::Variant::UpdateView(payload) => payload.leader_id,
proto::update_followers::Variant::UpdateActiveView(payload) => payload.leader_id,
});
for follower_id in &request.follower_ids {
let follower_id = ConnectionId(*follower_id);
if project_connection_ids.contains(&follower_id) && Some(follower_id.0) != leader_id {
session
.peer
.forward_send(session.connection_id, follower_id, request.clone())?;
for follower_peer_id in request.follower_ids.iter().copied() {
let follower_connection_id = follower_peer_id.into();
if project_connection_ids.contains(&follower_connection_id)
&& Some(follower_peer_id) != leader_id
{
session.peer.forward_send(
session.connection_id,
follower_connection_id,
request.clone(),
)?;
}
}
Ok(())
@ -1912,13 +1937,19 @@ fn contact_for_user(
fn room_updated(room: &proto::Room, peer: &Peer) {
for participant in &room.participants {
peer.send(
ConnectionId(participant.peer_id),
proto::RoomUpdated {
room: Some(room.clone()),
},
)
.trace_err();
if let Some(peer_id) = participant
.peer_id
.ok_or_else(|| anyhow!("invalid participant peer id"))
.trace_err()
{
peer.send(
peer_id.into(),
proto::RoomUpdated {
room: Some(room.clone()),
},
)
.trace_err();
}
}
}
@ -2033,7 +2064,7 @@ fn project_left(project: &db::LeftProject, session: &Session) {
*connection_id,
proto::RemoveProjectCollaborator {
project_id: project.id.to_proto(),
peer_id: session.connection_id.0,
peer_id: Some(session.connection_id.into()),
},
)
.trace_err();