mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 13:16:38 +00:00
fix: pre-release shared instances fixes (#6856)
* fix: dedupe notifications * fix: notifs + members btn robustness * fix: temp fix for external file wrongly showing updated * fix: fmt
This commit is contained in:
@@ -25,6 +25,7 @@ pub async fn invite_shared_instance_users(
|
||||
user_ids: Vec<String>,
|
||||
) -> crate::Result<SharedInstanceUsers> {
|
||||
let state = State::get().await?;
|
||||
let _shared_instance_lock = state.lock_shared_instance(instance_id).await;
|
||||
let (metadata, attachment) =
|
||||
shared_instance_for_invites(instance_id, user_ids.len(), &state)
|
||||
.await?;
|
||||
@@ -64,6 +65,7 @@ pub async fn create_shared_instance_invite_link(
|
||||
replace_invite_id: Option<String>,
|
||||
) -> crate::Result<SharedInstanceInviteLink> {
|
||||
let state = State::get().await?;
|
||||
let _shared_instance_lock = state.lock_shared_instance(instance_id).await;
|
||||
let (metadata, attachment) =
|
||||
shared_instance_for_invites(instance_id, 0, &state).await?;
|
||||
ensure_owner(&attachment)?;
|
||||
@@ -129,6 +131,7 @@ pub async fn remove_shared_instance_users(
|
||||
has_pending_recipients: bool,
|
||||
) -> crate::Result<SharedInstanceUsers> {
|
||||
let state = State::get().await?;
|
||||
let _shared_instance_lock = state.lock_shared_instance(instance_id).await;
|
||||
let Some(attachment) = shared_attachment(instance_id, &state).await? else {
|
||||
return Ok(SharedInstanceUsers::empty());
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ pub async fn authenticate_finish_flow(
|
||||
.await?;
|
||||
|
||||
creds.upsert(&state.pool).await?;
|
||||
state.friends_socket.disconnect().await?;
|
||||
state
|
||||
.friends_socket
|
||||
.connect(&state.pool, &state.api_semaphore, &state.process_manager)
|
||||
@@ -45,8 +46,8 @@ pub async fn logout() -> crate::Result<()> {
|
||||
|
||||
if let Some(current) = current {
|
||||
ModrinthCredentials::remove(¤t.user_id, &state.pool).await?;
|
||||
state.friends_socket.disconnect().await?;
|
||||
}
|
||||
state.friends_socket.disconnect().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -57,6 +58,9 @@ pub async fn get_credentials() -> crate::Result<Option<ModrinthCredentials>> {
|
||||
let current =
|
||||
ModrinthCredentials::get_and_refresh(&state.pool, &state.api_semaphore)
|
||||
.await?;
|
||||
if current.is_none() {
|
||||
state.friends_socket.disconnect().await?;
|
||||
}
|
||||
|
||||
Ok(current)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use serde_json::Value;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::net::tcp::OwnedReadHalf;
|
||||
@@ -38,6 +39,8 @@ pub(super) type TunnelSockets = Arc<DashMap<Uuid, Arc<InternalTunnelSocket>>>;
|
||||
|
||||
pub struct FriendsSocket {
|
||||
write: WriteSocket,
|
||||
connection_lock: Mutex<()>,
|
||||
connection_generation: Arc<AtomicU64>,
|
||||
user_statuses: Arc<DashMap<UserId, UserStatus>>,
|
||||
tunnel_sockets: TunnelSockets,
|
||||
}
|
||||
@@ -60,6 +63,8 @@ impl FriendsSocket {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
write: Arc::new(RwLock::new(None)),
|
||||
connection_lock: Mutex::new(()),
|
||||
connection_generation: Arc::new(AtomicU64::new(0)),
|
||||
user_statuses: Arc::new(DashMap::new()),
|
||||
tunnel_sockets: Arc::new(DashMap::new()),
|
||||
}
|
||||
@@ -72,6 +77,11 @@ impl FriendsSocket {
|
||||
semaphore: &FetchSemaphore,
|
||||
process_manager: &ProcessManager,
|
||||
) -> crate::Result<()> {
|
||||
let _connection_guard = self.connection_lock.lock().await;
|
||||
if self.is_connected().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let credentials =
|
||||
ModrinthCredentials::get_and_refresh(exec, semaphore).await?;
|
||||
|
||||
@@ -94,6 +104,10 @@ impl FriendsSocket {
|
||||
Ok((socket, _)) => {
|
||||
tracing::info!("Connected to friends socket");
|
||||
let (write, read) = socket.split();
|
||||
let connection_generation = self
|
||||
.connection_generation
|
||||
.fetch_add(1, Ordering::AcqRel)
|
||||
+ 1;
|
||||
|
||||
{
|
||||
let mut write_lock = self.write.write().await;
|
||||
@@ -107,12 +121,21 @@ impl FriendsSocket {
|
||||
}
|
||||
|
||||
let write_handle = self.write.clone();
|
||||
let connection_generation_handle =
|
||||
self.connection_generation.clone();
|
||||
let statuses = self.user_statuses.clone();
|
||||
let sockets = self.tunnel_sockets.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut read_stream = read;
|
||||
while let Some(msg_result) = read_stream.next().await {
|
||||
if connection_generation_handle
|
||||
.load(Ordering::Acquire)
|
||||
!= connection_generation
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
match msg_result {
|
||||
Ok(msg) => {
|
||||
let server_message = match msg {
|
||||
@@ -222,8 +245,17 @@ impl FriendsSocket {
|
||||
}
|
||||
}
|
||||
|
||||
let mut w = write_handle.write().await;
|
||||
*w = None;
|
||||
if connection_generation_handle.load(Ordering::Acquire)
|
||||
== connection_generation
|
||||
{
|
||||
let mut write = write_handle.write().await;
|
||||
if connection_generation_handle
|
||||
.load(Ordering::Acquire)
|
||||
== connection_generation
|
||||
{
|
||||
*write = None;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -302,11 +334,20 @@ impl FriendsSocket {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn disconnect(&self) -> crate::Result<()> {
|
||||
let _connection_guard = self.connection_lock.lock().await;
|
||||
self.connection_generation.fetch_add(1, Ordering::AcqRel);
|
||||
|
||||
let mut write_lock = self.write.write().await;
|
||||
if let Some(ref mut write_half) = *write_lock {
|
||||
SinkExt::close(write_half).await?;
|
||||
*write_lock = None;
|
||||
let write_half = write_lock.take();
|
||||
drop(write_lock);
|
||||
|
||||
self.user_statuses.clear();
|
||||
self.tunnel_sockets.clear();
|
||||
|
||||
if let Some(mut write_half) = write_half {
|
||||
SinkExt::close(&mut write_half).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ pub(crate) async fn remove_instance(
|
||||
})?;
|
||||
let _content_lock = state.lock_instance_content(instance_id).await;
|
||||
|
||||
delete_instance_row_and_content_lock(&instance.id, state).await?;
|
||||
delete_instance_row_and_locks(&instance.id, state).await?;
|
||||
|
||||
let path = state.directories.instances_dir().join(&instance.path);
|
||||
if path.exists() {
|
||||
@@ -23,13 +23,13 @@ pub(crate) async fn remove_instance(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_instance_row_and_content_lock(
|
||||
async fn delete_instance_row_and_locks(
|
||||
instance_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<()> {
|
||||
// Keep these together so deleted instances cannot leave stale entries in the per-instance lock map.
|
||||
// Keep these together so deleted instances cannot leave stale entries in the per-instance lock maps.
|
||||
instance_rows::delete_instance_by_id(instance_id, &state.pool).await?;
|
||||
state.remove_instance_content_lock(instance_id);
|
||||
state.remove_instance_locks(instance_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ pub struct State {
|
||||
pub(crate) install_db_semaphore: Semaphore,
|
||||
/// Serializes filesystem reconciliation and content mutations per instance.
|
||||
instance_content_locks: DashMap<String, Arc<Mutex<()>>>,
|
||||
/// Serializes shared instance attachment and recipient mutations per instance.
|
||||
shared_instance_locks: DashMap<String, Arc<Mutex<()>>>,
|
||||
|
||||
/// Discord RPC
|
||||
pub discord_rpc: DiscordGuard,
|
||||
@@ -111,8 +113,22 @@ impl State {
|
||||
lock.lock_owned().await
|
||||
}
|
||||
|
||||
pub(crate) fn remove_instance_content_lock(&self, instance_id: &str) {
|
||||
pub(crate) async fn lock_shared_instance(
|
||||
&self,
|
||||
instance_id: &str,
|
||||
) -> OwnedMutexGuard<()> {
|
||||
let lock = self
|
||||
.shared_instance_locks
|
||||
.entry(instance_id.to_string())
|
||||
.or_insert_with(|| Arc::new(Mutex::new(())))
|
||||
.clone();
|
||||
|
||||
lock.lock_owned().await
|
||||
}
|
||||
|
||||
pub(crate) fn remove_instance_locks(&self, instance_id: &str) {
|
||||
let _ = self.instance_content_locks.remove(instance_id);
|
||||
let _ = self.shared_instance_locks.remove(instance_id);
|
||||
}
|
||||
|
||||
pub async fn init(app_identifier: String) -> crate::Result<()> {
|
||||
@@ -231,6 +247,7 @@ impl State {
|
||||
install_job_semaphore: Semaphore::new(MAX_CONCURRENT_INSTALL_JOBS),
|
||||
install_db_semaphore: Semaphore::new(1),
|
||||
instance_content_locks: DashMap::new(),
|
||||
shared_instance_locks: DashMap::new(),
|
||||
discord_rpc,
|
||||
process_manager,
|
||||
friends_socket,
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
:requested-label="requestedButtonLabel"
|
||||
:requested-tooltip="requestedTooltip(friend.username)"
|
||||
:user-profile-link="userProfileLink"
|
||||
:disabled="!canInvite"
|
||||
@invite="inviteFriend"
|
||||
@cancel="cancelInvite"
|
||||
/>
|
||||
@@ -348,6 +349,7 @@ const {
|
||||
})
|
||||
|
||||
function inviteFriend(friend: InvitePlayersUser) {
|
||||
if (!props.canInvite) return
|
||||
emit('invite', {
|
||||
user: friend,
|
||||
source: 'friend',
|
||||
@@ -355,6 +357,7 @@ function inviteFriend(friend: InvitePlayersUser) {
|
||||
}
|
||||
|
||||
function cancelInvite(friend: InvitePlayersUser) {
|
||||
if (!props.canInvite) return
|
||||
emit('cancel', friend)
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -37,7 +37,7 @@
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else-if="status === 'pending'" type="outlined">
|
||||
<button @click="$emit('cancel', user)">
|
||||
<button :disabled="disabled" @click="$emit('cancel', user)">
|
||||
{{ cancelLabel }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -49,7 +49,7 @@
|
||||
</ButtonStyled>
|
||||
</span>
|
||||
<ButtonStyled v-else color-fill="none">
|
||||
<button @click="$emit('invite', user)">
|
||||
<button :disabled="disabled" @click="$emit('invite', user)">
|
||||
{{ inviteLabel }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
@@ -74,9 +74,11 @@ const props = withDefaults(
|
||||
inviteLabel: string
|
||||
requestedLabel: string
|
||||
requestedTooltip: string
|
||||
disabled?: boolean
|
||||
userProfileLink?: (username: string) => InvitePlayersUserProfileLink
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
userProfileLink: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user