v0.4.3: Lock contention overhaul, StoragePool, mobile bottom nav, text scaling

Eliminate all conn_mgr lock holds during network I/O across 14 actor commands
and bi-stream handlers. PostFetch, TcpPunch, PullFromPeer, FetchEngagement,
ResolveAddress, AnchorProbe use brief locks for data gathering only. WormLookup,
ContentSearch, WormQuery use connection snapshots for lock-free cascade fan-out.
RelayIntroduce extracts forwarding data under brief lock, does I/O outside.
BlobRequest, PostFetchRequest, ManifestRefresh use Arc clones instead of conn_mgr
lock. ConnectionActor hoists shared Arcs (storage, blob_store, endpoint) for
lock-free access. ResolveAddress adds 5s per-query timeout (was unbounded).

Initial exchange failure now aborts mesh upgrade (was silently continuing with
broken connection). connect_to_peer/connect_to_anchor use consistent 15s timeout.
Rebalance connects outside the lock via pending_connects pattern.

StoragePool: 8 concurrent SQLite connections in WAL mode replace single
Mutex<Storage>. Reads run fully parallel; writes serialize at SQLite level only.
PRAGMA busy_timeout=5000 for graceful write contention.

Mobile bottom nav bar (<=768px) with icon tabs. Text sizes: XS/S/M/L/XL
(75%/100%/125%/150%/200%), default M. localStorage persistence for instant
restore. Toast repositioned above mobile nav.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-03-22 21:35:38 -04:00
parent f17535d61d
commit 43adbbdf7d
15 changed files with 1546 additions and 618 deletions

View file

@ -126,7 +126,7 @@ async fn serve_post(stream: &mut TcpStream, path: &str, node: &Arc<Node>, browse
// Single lock: gather holders, local post, AND author name if local
let (holders, local_post, local_author_name) = {
let store = node.storage.lock().await;
let store = node.storage.get().await;
let mut holders = Vec::new();
if let Some(author) = author_id {
@ -190,7 +190,7 @@ async fn serve_post(stream: &mut TcpStream, path: &str, node: &Arc<Node>, browse
Ok(Ok(Some(sync_post))) => {
// Single lock: store post AND get author name
let author_name = {
let store = node.storage.lock().await;
let store = node.storage.get().await;
let _ = store.store_post_with_visibility(
&sync_post.id, &sync_post.post, &sync_post.visibility,
);
@ -230,7 +230,7 @@ async fn try_redirect(
use crate::types::NatMapping;
let post_hex = hex::encode(post_id);
let store = node.storage.lock().await;
let store = node.storage.get().await;
// Classify holders into tiers
let mut direct_candidates: Vec<(NodeId, String)> = Vec::new(); // http_addr known
@ -354,7 +354,7 @@ async fn serve_blob(stream: &mut TcpStream, path: &str, node: &Arc<Node>) {
// Check blobs table first, then scan post attachments (for posts stored via PostFetch
// which don't populate the blobs table).
let (mime_type, author_id) = {
let store = node.storage.lock().await;
let store = node.storage.get().await;
// Try blobs table first
if let Some(mime) = find_public_blob_mime(&store, &blob_id) {
let author = store.get_blob_post_id(&blob_id).ok().flatten().and_then(|pid| {