Feat: Async - #51
Conversation
There was a problem hiding this comment.
Pull request overview
Adds asynchronous gRPC call support to pg_grpc by introducing a background worker + queue/results tables, along with public SQL APIs and accompanying documentation/website updates. This addresses #50 by allowing callers to enqueue work and fetch results later instead of blocking on the network.
Changes:
- Introduces async execution infrastructure (bg worker, shared memory signaling, queue/result persistence, async GUCs).
- Adds new public SQL functions:
grpc_call_async(),grpc_call_result(),grpc_wait_until_running(). - Updates docs/site (new “Async calls” guide, reference updates, sidebar/hero updates) and bumps website dependencies.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/lib.rs |
Registers the background worker; adds SQL APIs for async enqueue/result/wait (and a restart helper). |
src/worker.rs |
Implements the bg worker loop: latch wake, dequeue, concurrent execution, persist results, TTL cleanup. |
src/queue.rs |
Implements dequeue/lookup/result persistence and TTL cleanup logic. |
src/shmem.rs |
Shared-memory state for worker status, wake flag, and latch pointer storage. |
src/guc.rs |
Adds async-related GUCs (database_name, username, batch_size, ttl). |
src/call.rs |
Adds call_async_row() to execute a queued call with timeout and return a structured outcome. |
src/async_schema.rs |
Creates grpc.call_queue / grpc._call_result tables (and related types) on extension bootstrap. |
src/tests/* |
Adds tests for worker readiness, GUC defaults, queue operations, and async call end-to-end flows. |
website/docs/guides/async-calls.md |
New guide explaining async calls, worker behavior, and configuration. |
website/docs/reference.md |
Documents new SQL functions and async GUCs. |
website/static/llms*.txt |
Updates the LLM index/docs bundle with async call content and guide link. |
website/src/components/Hero/Hero.tsx |
Adds an “async” tab/pane to the homepage terminal demo. |
website/sidebars.ts |
Adds the async guide to the Guides sidebar. |
website/package.json / website/package-lock.json |
Updates Docusaurus-related dependency versions/ranges. |
website/docusaurus.config.ts |
Updates OG/Twitter alt text formatting. |
Comments suppressed due to low confidence (1)
website/package.json:33
- The
@docusaurusdependencies were bumped to 3.10.1, but the related@docusaurusdevDependencies (@docusaurus/module-type-aliases,@docusaurus/tsconfig,@docusaurus/types) are still pinned to 3.10.0. Docusaurus expects its packages to stay on the same version line, and this mismatch can cause type/build inconsistencies. Update the devDependencies to the same version range/version as the runtime@docusauruspackages (or intentionally pin everything to a single exact version).
"@docusaurus/core": "^3.10.1",
"@docusaurus/faster": "^3.10.1",
"@docusaurus/preset-classic": "^3.10.1",
"@docusaurus/theme-mermaid": "^3.10.1",
"@easyops-cn/docusaurus-search-local": "^0.55.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.10.0",
"@docusaurus/tsconfig": "3.10.0",
"@docusaurus/types": "3.10.0",
"@types/react": "^19.0.0",
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 20 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.
Files not reviewed (1)
- website/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub(crate) async fn call_async_row(row: crate::queue::QueueRow) -> crate::queue::CallResult { | ||
| let opts = match &row.options { | ||
| None => crate::options::OptionsConfig::default(), | ||
| Some(v) => crate::options::OptionsConfig::parse(v).unwrap_or_default(), |
| for row in rows { | ||
| set.spawn(call::call_async_row(row)); | ||
| } | ||
| let mut out = Vec::new(); | ||
| while let Some(res) = set.join_next().await { | ||
| if let Ok(r) = res { | ||
| out.push(r); |
| .map(|j| serde_json::from_str(&j.0.to_string()).unwrap()), | ||
| options: row["options"] | ||
| .value::<pgrx::JsonB>() | ||
| .unwrap() | ||
| .map(|j| serde_json::from_str(&j.0.to_string()).unwrap()), |
| v.map(|j| serde_json::from_str(&j.0.to_string()).unwrap()) | ||
| .unwrap_or(JsonValue::Null) |
| @@ -1,25 +1,26 @@ | |||
| import React, {useEffect, useRef, useState} from 'react'; | |||
| import React, { JSX, useEffect, useRef, useState } from 'react'; | |||
| // Register commit-time wake so the worker is signaled only when the | ||
| // enqueuing transaction actually commits (rolled-back calls never wake it). | ||
| // The flag prevents registering more than once per transaction even when | ||
| // grpc_call_async() is called many times within the same transaction. | ||
| if !WAKE_CB_ACTIVE.swap(true, Ordering::Relaxed) { | ||
| unsafe { | ||
| pg_sys::RegisterXactCallback(Some(wake_worker_on_commit), std::ptr::null_mut()); | ||
| } | ||
| } |
|
I'll analyze this and get back to you. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
Claude finished @CSenshi's task in 2m 54s —— View job PR Review: Feat/Async
This PR adds a solid async gRPC call infrastructure. The architecture is clean: UNLOGGED queue tables, a background worker, shared-memory latch signaling, and a single xact callback registered at Bugs1. When a spawned task panics or is cancelled, while let Some(res) = set.join_next().await {
if let Ok(r) = res {
out.push(r);
}
}The queue row was already Fix: match on the 2. Some(v) => crate::options::OptionsConfig::parse(v).unwrap_or_default(),If the Tests3. VALUES (20001, 'grpcb.in:9000', 'grpcbin.GRPCBin/DummyUnary', ...)This test directly inserts a row with a hardcoded endpoint, bypassing the Design / Operational observations4. Both 5. pub fn set_latch() {
let ptr = *WORKER_LATCH.share(); // lock acquired, ptr copied, lock released
if ptr != 0 {
unsafe { pg_sys::SetLatch(ptr as *mut pg_sys::Latch) }; // lock already gone
}
}The 6. The query 7. The synchronous wait mode polls every 50ms with Nits
Overall this is a well-structured feature with good test coverage. The three fixes above (JoinError handling, options parse errors, hardcoded endpoint) are the most important before merging. |
|
Claude finished @CSenshi's task in 2m 32s —— View job PR Review: Feat/Async
All three bugs flagged in the previous Claude review have been fixed, along with every actionable Copilot comment. This is looking merge-ready. A few remaining observations below. Fixed since last review
Remaining observations1.
2.
3.
4.
5. UNLOGGED tables — crash semantics not prominently documented Both Overall this is a clean, well-structured implementation with solid test coverage. The three critical bugs from the previous review are all resolved. The remaining items are low-severity (nit, operational note, safety hardening, docs) and can be addressed as follow-ups. |

Summary
Adds
grpc_call_async()supportCloses #50