community: every ledger takes its write lock, so the inbox check can no longer land on top of a friend accept

This commit is contained in:
KeelrLeek committed 2026-09-03 11:51:35 -05:00
1 parent 07227a90a7
commit 4d03cf849b
6 files changed
+89 -29

No files matched your search

+23 -8
View File
@@ -30,6 +30,7 @@ use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager};
use crate::inbox::{self, Cargo};
use crate::ledger;
use crate::persona;
use crate::slsk_link;
use crate::state::AppState;
@@ -114,8 +115,11 @@ fn path(app: &tauri::AppHandle) -> Result<PathBuf, String> {
.app_data_dir()
.map_err(|e| e.to_string())?
.join("freenet")
.join("friends.json"))
.join(LEDGER_FILE))
}
/// Both the file the ledger lives in and the key its write lock is taken on.
const LEDGER_FILE: &str = "friends.json";
fn load(app: &tauri::AppHandle) -> Store {
path(app)
.ok()
@@ -294,6 +298,7 @@ async fn start_request(
updated_at: now,
};
deliver(app, &mut rel, Cargo::FriendRequest(req.clone()), encode(REQ_PREFIX, &req)?).await?;
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
store.relationships.insert(id.clone(), rel);
save(app, &store)?;
@@ -322,6 +327,7 @@ pub async fn request_by_key(app: &tauri::AppHandle, persona_key: &str) -> Result
pub async fn accept(app: &tauri::AppHandle, id: &str) -> Result<RelationshipView, String> {
let me = my_key(app).await?;
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
let rel = store.relationships.get_mut(id).ok_or("no such request")?;
if rel.request_from_me {
@@ -340,6 +346,7 @@ pub async fn accept(app: &tauri::AppHandle, id: &str) -> Result<RelationshipView
}
pub async fn decline(app: &tauri::AppHandle, id: &str) -> Result<RelationshipView, String> {
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
let rel = store.relationships.get_mut(id).ok_or("no such request")?;
let now = now_secs();
@@ -355,6 +362,7 @@ pub async fn decline(app: &tauri::AppHandle, id: &str) -> Result<RelationshipVie
/// "revoked" — the design's honesty about what a removal can and cannot do.
pub async fn remove(app: &tauri::AppHandle, id: &str) -> Result<RelationshipView, String> {
let me = my_key(app).await?;
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
let rel = store.relationships.get_mut(id).ok_or("no such relationship")?;
let their = key_of(&rel.their_key)?;
@@ -368,7 +376,8 @@ pub async fn remove(app: &tauri::AppHandle, id: &str) -> Result<RelationshipView
Ok(v)
}
pub fn forget(app: &tauri::AppHandle, id: &str) -> Result<(), String> {
pub async fn forget(app: &tauri::AppHandle, id: &str) -> Result<(), String> {
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
store.relationships.remove(id);
save(app, &store)
@@ -444,6 +453,7 @@ async fn store_request(app: &tauri::AppHandle, from: From, req: fr::FriendReques
}
};
let id = b58(&req.relationship_id);
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
if store.relationships.contains_key(&id) {
return;
@@ -479,6 +489,7 @@ async fn apply_assertion(app: &tauri::AppHandle, from: From, a: fr::FriendAssert
return;
}
let id = b58(&a.relationship_id);
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
let Some(rel) = store.relationships.get_mut(&id) else {
log::debug!("friends: assertion for an unknown relationship");
@@ -533,7 +544,8 @@ async fn apply_assertion(app: &tauri::AppHandle, from: From, a: fr::FriendAssert
}
}
fn apply_decline(app: &tauri::AppHandle, from: From, id: &str) {
async fn apply_decline(app: &tauri::AppHandle, from: From, id: &str) {
let _ledger = ledger::hold(LEDGER_FILE).await;
let mut store = load(app);
let Some(rel) = store.relationships.get_mut(id) else { return };
let sender_ok = match &from {
@@ -555,8 +567,8 @@ pub(crate) async fn on_inbox_request(app: &tauri::AppHandle, sender: [u8; 32], r
pub(crate) async fn on_inbox_assertion(app: &tauri::AppHandle, sender: [u8; 32], a: fr::FriendAssertion) {
apply_assertion(app, From::Inbox(sender), a).await;
}
pub(crate) fn on_inbox_decline(app: &tauri::AppHandle, sender: [u8; 32], id: [u8; 16]) {
apply_decline(app, From::Inbox(sender), &b58(&id));
pub(crate) async fn on_inbox_decline(app: &tauri::AppHandle, sender: [u8; 32], id: [u8; 16]) {
apply_decline(app, From::Inbox(sender), &b58(&id)).await;
}
/// The event pump hands every private message here after the link proof.
@@ -579,7 +591,10 @@ pub fn intercept_pm(app: &tauri::AppHandle, from: &str, message: &str) -> bool {
return true;
}
if let Some(rest) = msg.strip_prefix(DECLINE_PREFIX) {
apply_decline(app, From::Pm(from.to_string()), rest.trim());
// Its own task like the two above: this now takes the ledger, and an
// inline await here deadlocks the event pump.
let (app, from, id) = (app.clone(), from.to_string(), rest.trim().to_string());
tauri::async_runtime::spawn(async move { apply_decline(&app, From::Pm(from), &id).await });
return true;
}
false
@@ -612,6 +627,6 @@ pub async fn friend_remove(app: tauri::AppHandle, id: String) -> Result<Relation
remove(&app, &id).await
}
#[tauri::command]
pub fn friend_forget(app: tauri::AppHandle, id: String) -> Result<(), String> {
forget(&app, &id)
pub async fn friend_forget(app: tauri::AppHandle, id: String) -> Result<(), String> {
forget(&app, &id).await
}