Skip to content

feat(app): wire AppRuntime single-task dispatcher + P0 fixes - #2

Merged
sandy-sachin7 merged 9 commits into
mainfrom
feat/app-runtime-wiring
Aug 31, 2026
Merged

feat(app): wire AppRuntime single-task dispatcher + P0 fixes#2
sandy-sachin7 merged 9 commits into
mainfrom
feat/app-runtime-wiring

Conversation

@sandy-sachin7

Copy link
Copy Markdown
Owner

See branch feat/app-runtime-wiring — pushed with fmt/check/clippy/test passing. Body in commit message.

- Add binary crates/app/src/main.rs (run_native entrypoint, tracing init)
- Add crates/app/src/runtime.rs: single Tokio task draining AppCommand via spawn_blocking recv, registries for LiveSession/QueryEntry, handlers for Connect/Disconnect/Execute(limit 100)/Cancel/RefreshSchema/HistorySearch, streaming via query_raw+prepare + Box::pin(RowStream) -> spawn_drive -> SharedStore
- Fix LiveSession.client to Arc<Client> (tokio-postgres 0.7.18 !Clone) and cancel_query(NoTls)
- Add webpki-roots, connect_timeout+keepalives, fix bytea OID 17 handling, editing escaping, history UTF-8 truncation, prepare_session default_transaction_read_only
- Fix app/lib.rs: Arc<RwLock<AppState>>, spawn runtime on Handle::try_current with exclusive cmd_rx, off-UI-thread editor persist, CompletionEngine ptr-eq cache

Checks: cargo fmt, cargo check --workspace, cargo clippy --workspace --all-targets --all-features (0 errors), cargo test -p pgnative-app/db-connection/results-* 5/4/3/11 pass
With channel_cap=2 the producer would block on 3rd send if awaited
before draining rx (Meta + 1st Batch fills cap, 2nd Batch awaits
forever -> >60s hang). Run drive_iter concurrently via tokio::spawn
and await handle after draining, asserting 5 rows / 3 batches.
…tization, stream budgets)

- db/connection: cancel uses MakeRustlsConnect per SslMode (Fix CRITICAL NoTls leak)
- db/execution: gate SQL logging to sql_len only (§35)
- storage/history: sanitize_for_history redacts secrets, FTS5 escaping, char-boundary trunc
- app/runtime: remove silent LIMIT 100 rewrite (§16), try_send everywhere (no crossbeam block)
- app/runtime+db/connection: queries leak fixed via Arc<Mutex>+QGuard Drop, LiveSession abort_driver on disconnect
- db/introspection: prepare_session session-scoped read-only removed (only statement_timeout)
- results/stream: byte trunc finds UTF-8 boundary, preserves type on trunc
- viewport: f64 precision for scroll/height; schema/build: u32::try_from for Id casts
- StreamConfig: batch 64 / channel 16 (32GiB theoretical -> ~256MiB)
- runtime: replace per-command spawn_blocking churn with single
  crossbeam->tokio mpsc bridge thread (1 blocking thread, 256 cap)
- runtime: Cancel marks SessionHealth::Poisoned on cancel_query failure
  and aborts bridge thread on shutdown
- app: PgnativeApp::new loads ui_state off render thread via
  spawn_blocking + PreferencesRestored event (no SQLite on UI thread)
- app: editor persist debounced 350ms (prevents per-keystroke thread
  explosion), runtime handle stored and aborted on Drop
- schema/model: last().unwrap() -> expect("just pushed") (clippy)
C1 crates/db/connection/src/lib.rs:676 cancel_query_via_tls now returns
Err(String) on build_rustls_config failure instead of falling back to
NoTls; plaintext fallback would leak pid/secret on hostssl servers.
C1 crates/db/cancellation/src/lib.rs:54 TokenCanceller stores SslMode +
ssl_root_cert and uses build_rustls_config; Disable uses NoTls only,
TLS errors map to CancelError::Failed (no plaintext fallback).
C2 crates/db/connection/src/lib.rs:555 document VerifyFull/VerifyCa/
Require/Prefer trust-anchor behavior; all non-Disable modes load
webpki roots and never silently accept insecure.
C3 crates/storage/history/src/lib.rs:56 expand sanitize_for_history to
redact api_key, apikey, private_key, aws_secret, client_secret.
C4 crates/storage/editor_state/src/lib.rs:86 upsert returns early when
persisted_buffers OFF (avoids persisting arbitrary SQL secrets).
H1 crates/db/connection/src/lib.rs:164 sanitize_url fallback now loops
per key to redact all occurrences via while let Some(idx) pattern.
H4 crates/results/export/src/lib.rs:39 export_json uses
serde_json::to_string for cell escaping (handles ", \n, \ etc.).
H5 crates/db/connection/src/lib.rs:525 replace hand-rolled base64 with
base64 crate (Engine::decode) and duplicate in cancellation crate.

Verification: cargo fmt, cargo check --workspace (0 errors),
cargo clippy --workspace --all-targets (0 errors), cargo test --workspace --lib (all ok).
BUG #2 crates/app/src/runtime.rs:160 — query handle race: insert
placeholder QueryEntry before tokio::spawn so Cancel finds entry
immediately; swap placeholder with real handle after spawn, abort new
handle if entry was already removed (no leak on fast prepare error via
QGuard Drop removing entry).

BUG #10 crates/app/src/runtime.rs:38 — bridge thread deadlock:
replace blocking_send with try_send+retry loop with 1ms backoff and
is_closed check; channel cap 256 prevents flood, abort closes channel.

LOW 2.1 crates/results/stream/src/lib.rs:107 — truncation end.max(1)
reintroduced invalid UTF-8 at cap=1: return Bytes::new() when end==0
instead of max(1); also walk to char boundary safely.

LOW 2.1 variant preservation crates/results/stream/src/lib.rs:107 —
add TODO that truncated jsonb/bytea currently coerced to Text.

LOW 2.3 crates/ui/results/src/lib.rs:115 — fix &b[..2048] not char
boundary: floor to previous char boundary via continuation-byte walk
before from_utf8_lossy.

LOW 3.2 crates/results/stream/src/lib.rs:13 — reduce PER_CELL_CAP
from 256KiB to 64KiB with budget comment (64MiB store, channel_cap 16).

LOW 7.1 crates/results/viewport/src/lib.rs:39 — document overscan
double (2×) intentional, clamp via fetch_range, clarify visible_range
docs.

LOW 8.1 crates/results/store/src/lib.rs:129 — add comment that
parking_lot RwLock not held across await; write locks are short
critical sections in push_batch/complete/cancel.

Co-authored-by: opencode
…migrations

Fix AppController::send_command dummy-channel drain bug by dropping
retry logic that tried to recv from swapped dummy_rx; now just warns
and drops command per bounded 256 contract. Annotate AppState.tx as
derived from ConnectionState. Add missing FTS AFTER DELETE/UPDATE
triggers (history_ad/history_au) for history_fts external content
sync. Wrap each migrate version block in BEGIN/COMMIT with ROLLBACK
on error to make migrations transactional.

Co-Authored-By: internal-model
… extraction

- edit: reject half-bound UPDATE when PK values missing/empty or mismatched;
  update_sql now errors without PK (no empty-string defaults), update_sql_with_pk
  validates pk_values len and per-column presence/emptiness
- introspection: return Err on empty schemas instead of phantom Id(0) fallback;
  hydrate now returns Result and introspect propagates error
- introspection: fix FK foreign_keys_in direction — swap referencing/referenced
  and referenced_relation to child (src_id) for inbound side
- completion: implement FROM/JOIN alias extraction (FROM/JOIN regex hand-rolled,
  AS handling, reserved-word filtering) with extract_alias_map and
  extract_aliases_with_model
- schema/cache, app: document AppState.schema duplicates SchemaCache (hot epoch)

Co-authored-by: internal
…eholder

Wire explorer tree from SchemaModel with search filter and columns,
history panel with scrollable selectable labels, connection form grid,
keyboard shortcuts (Ctrl+Enter/F5/Esc) and history typed event with
back-compat, top-bar Tx badge, and export buttons. Small appends to
runtime to emit HistoryResults.
@sandy-sachin7
sandy-sachin7 merged commit 5cf7ec0 into main Aug 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant