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

@ -12,7 +12,7 @@ use tokio::sync::Mutex;
use tracing::{debug, info};
use crate::blob::BlobStore;
use crate::storage::Storage;
use crate::storage::{Storage, StoragePool};
use crate::types::PostVisibility;
/// Connection budget: 5 content slots, 15 redirect slots, 1 per IP.
@ -104,7 +104,7 @@ impl HttpBudget {
/// Run the HTTP server on the given port. Blocks forever.
pub async fn run_http_server(
port: u16,
storage: Arc<Mutex<Storage>>,
storage: Arc<StoragePool>,
blob_store: Arc<BlobStore>,
downstream_addrs: Arc<Mutex<HashMap<[u8; 32], Vec<SocketAddr>>>>,
) -> anyhow::Result<()> {
@ -180,7 +180,7 @@ async fn handle_connection(
mut stream: TcpStream,
_ip: IpAddr,
slot: SlotKind,
storage: &Arc<Mutex<Storage>>,
storage: &Arc<StoragePool>,
blob_store: &Arc<BlobStore>,
downstream_addrs: &Arc<Mutex<HashMap<[u8; 32], Vec<SocketAddr>>>>,
) {
@ -281,12 +281,12 @@ fn validate_hex64(s: &str) -> Option<[u8; 32]> {
async fn serve_post(
stream: &mut TcpStream,
post_id: &[u8; 32],
storage: &Arc<Mutex<Storage>>,
storage: &Arc<StoragePool>,
blob_store: &Arc<BlobStore>,
) -> bool {
// Look up post + visibility
let result = {
let store = storage.lock().await;
let store = storage.get().await;
store.get_post_with_visibility(post_id)
};
@ -301,7 +301,7 @@ async fn serve_post(
// Look up author name
let author_name = {
let store = storage.lock().await;
let store = storage.get().await;
store
.get_profile(&post.author)
.ok()
@ -321,12 +321,12 @@ async fn serve_post(
async fn serve_blob(
stream: &mut TcpStream,
blob_id: &[u8; 32],
storage: &Arc<Mutex<Storage>>,
storage: &Arc<StoragePool>,
blob_store: &Arc<BlobStore>,
) -> bool {
// Verify this blob belongs to a public post
let (mime_type, _post_id) = {
let store = storage.lock().await;
let store = storage.get().await;
match find_public_blob_info(&store, blob_id) {
Some(info) => info,
None => return false, // not found or not public — hard close
@ -367,12 +367,12 @@ fn find_public_blob_info(store: &Storage, blob_id: &[u8; 32]) -> Option<(String,
async fn try_redirect(
stream: &mut TcpStream,
post_id: &[u8; 32],
storage: &Arc<Mutex<Storage>>,
storage: &Arc<StoragePool>,
_downstream_addrs: &Arc<Mutex<HashMap<[u8; 32], Vec<SocketAddr>>>>,
) -> bool {
// Get downstream peers for this post
let downstream_peers = {
let store = storage.lock().await;
let store = storage.get().await;
// Verify post exists and is public first
match store.get_post_with_visibility(post_id) {
Ok(Some((_, PostVisibility::Public))) => {}
@ -383,7 +383,7 @@ async fn try_redirect(
// Get addresses for downstream peers
let candidates: Vec<SocketAddr> = {
let store = storage.lock().await;
let store = storage.get().await;
let mut addrs = Vec::new();
for peer_id in &downstream_peers {
if let Ok(Some(peer)) = store.get_peer_record(peer_id) {