Skip to content

fix: dedupe concurrent getNodeAjaxOptions requests (#10155)#10159

Open
hiteshjambhale wants to merge 3 commits into
pgadmin-org:masterfrom
hiteshjambhale:fix/node-ajax-inflight-dedup
Open

fix: dedupe concurrent getNodeAjaxOptions requests (#10155)#10159
hiteshjambhale wants to merge 3 commits into
pgadmin-org:masterfrom
hiteshjambhale:fix/node-ajax-inflight-dedup

Conversation

@hiteshjambhale

@hiteshjambhale hiteshjambhale commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #10155.

getNodeAjaxOptions() in web/pgadmin/browser/static/js/node_ajax.js — the shared helper behind every AJAX-backed dropdown — cached responses only after they resolved. So when many components requested the same URL before the first response landed (e.g. every column row's Data Type dropdown mounting at once on a wide table's Columns tab), each one saw an empty cache and fired its own identical GET. A 150-column table produced ~150 concurrent duplicate get_types requests.

Fix

Added in-flight request sharing:

  • A module-level Map tracks requests currently in progress, keyed by the resolved full URL + query params.
  • On a cache miss, a caller reuses an existing in-flight request if one matches; otherwise it starts one and stores it.
  • The first request to resolve populates the cache exactly as before.
  • The map entry is removed on settle (.finally()), success or failure, so it doesn't leak.

Result: one shared get_types request for all concurrent callers instead of one per row. Cached behavior and response-shape handling are unchanged.

Notes

Could not run the JS linter locally — the repo's .eslintrc.js (legacy format) is incompatible with the installed ESLint v9 (flat-config only), a pre-existing toolchain issue unrelated to this change. The file was verified to parse as valid ES module syntax.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prevented duplicate concurrent GET requests when multiple parts of the application request the same resource with identical parameters.
    • Shared a single in-progress request among concurrent callers and ensured it is properly cleared after the request completes or fails, without changing existing cache-hit behavior.

getNodeAjaxOptions cached responses only after they resolved, so concurrent
callers for the same URL (e.g. every column row's Data Type dropdown mounting
at once) all saw an empty cache and each fired its own identical HTTP GET.

Track in-flight requests in a module-level Map keyed by the resolved URL and
query params. Concurrent callers now await the same underlying request; the
first to resolve populates the cache as before, and the entry is cleaned up on
settle so the map does not leak.

Fixes pgadmin-org#10155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e41e05d4-04dc-4c40-a9df-fae27b76988a

📥 Commits

Reviewing files that changed from the base of the PR and between 10512a4 and 73bfc89.

📒 Files selected for processing (1)
  • web/pgadmin/static/js/api_instance.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/static/js/api_instance.js

Walkthrough

Concurrent GET requests from getNodeAjaxOptions are deduplicated by URL and stable request parameters. Shared promises are removed after settlement, while each caller retains response transformation and optional caching.

Changes

AJAX request deduplication

Layer / File(s) Summary
In-flight request registry
web/pgadmin/static/js/api_instance.js
Adds deterministic serialization and an exported getInflight helper that shares matching pending GET promises and removes them after completion.
Node AJAX integration
web/pgadmin/browser/static/js/node_ajax.js
Routes cache misses through getInflight, then applies per-caller response normalization, caching, and transformation.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant getNodeAjaxOptions
  participant getInflight
  participant api.get
  getNodeAjaxOptions->>getInflight: Request fullUrl with params
  alt No matching pending request
    getInflight->>api.get: Start GET request
    api.get-->>getInflight: Return response
    getInflight-->>getNodeAjaxOptions: Shared response promise
  else Matching pending request
    getInflight-->>getNodeAjaxOptions: Reuse shared promise
  end
  getNodeAjaxOptions-->>getNodeAjaxOptions: Cache and transform response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: deduplicating concurrent getNodeAjaxOptions requests for issue #10155.
Linked Issues check ✅ Passed The PR implements shared in-flight GET deduplication for getNodeAjaxOptions, with cache preservation and cleanup after completion as requested.
Out of Scope Changes check ✅ Passed The added request-key hardening and shared in-flight helper are directly related to the deduplication fix and not unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/pgadmin/browser/static/js/node_ajax.js`:
- Around line 137-145: Move the cacheNode.cache invocation out of the shared
api.get promise callback and into the per-caller request.then block before
transform(resData). Use each caller’s own otherParams, cacheNode, nodeObj, url,
treeNodeInfo, and cacheLevel values so concurrent requests apply independent
cache logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 27d542f2-1ebc-4cbb-881c-b8a2f93d5bc4

📥 Commits

Reviewing files that changed from the base of the PR and between b15c745 and eae3f97.

📒 Files selected for processing (1)
  • web/pgadmin/browser/static/js/node_ajax.js

Comment thread web/pgadmin/browser/static/js/node_ajax.js Outdated
// Share a single in-flight request among all concurrent callers asking
// for the same URL + params, so we don't fire duplicate GETs before the
// first response lands and populates the cache.
let inflightKey = fullUrl + '#' + JSON.stringify(otherParams.urlParams || {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some observations -

  1. Not the best way to create a unique key
{
   a:1,
   b:2
}

vs

{
   b:2,
   a:1
}

And a nested object will make it worse.

  1. A better place to put such logic is inside the Axios wrapper, so that it will be used by the entire app

@kundansable

Copy link
Copy Markdown
Contributor

@hiteshjambhale fix is working, we only get one API call for column details instead of N(number of columns).
just fix the changes asked by @mzabuawala

Address review feedback on the getNodeAjaxOptions dedup:

- Move the in-flight request sharing out of node_ajax.js and into
  api_instance.js as a reusable getInflight() helper, so any part of
  the app can dedupe identical concurrent GETs, not just node options.
- Build the in-flight key with a stable, key-order-independent
  stringify (sorted keys, recursive) so {a:1,b:2} and {b:2,a:1} and
  nested params resolve to the same key.
- Run caching and transform per-caller in each caller's own .then on
  the shared response, so concurrent callers with differing params
  each cache correctly instead of inheriting the first caller's.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/pgadmin/static/js/api_instance.js`:
- Line 31: Update the in-flight request key generated by getInflight in
api_instance.js to include the effective Axios/API request context, not only the
URL and params. Ensure requests using different caller-specific headers,
authorization/CSRF values, or runtime options cannot share cached results, while
preserving deduplication for identical request contexts.
- Around line 56-59: Update getInflight() so each shared Axios GET request uses
a finite timeout or cancellation mechanism before being stored in
_inflightGetRequests. Ensure the map entry is removed when the request aborts,
rejects, or resolves, preventing stalled requests from retaining the key
indefinitely.
- Around line 35-44: Update stableStringify, used by getInflight, to produce
collision-free keys for supported parameter values and reject unsupported
config.params inputs, including undefined-containing arrays, Date, Map, and
cyclic structures. Preserve deterministic object-key ordering while ensuring
distinct JSON-like types cannot serialize to the same key; add coverage for
these collision and rejection cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 28f67f76-5bdb-4492-8df2-e84e32878bcb

📥 Commits

Reviewing files that changed from the base of the PR and between eae3f97 and 10512a4.

📒 Files selected for processing (2)
  • web/pgadmin/browser/static/js/node_ajax.js
  • web/pgadmin/static/js/api_instance.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/browser/static/js/node_ajax.js

Comment thread web/pgadmin/static/js/api_instance.js
Comment thread web/pgadmin/static/js/api_instance.js
Comment on lines +56 to +59
request = api.get(url, config).finally(() => {
_inflightGetRequests.delete(key);
});
_inflightGetRequests.set(key, request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n -C3 '\b(timeout|AbortController|signal)\b' \
  web/pgadmin/static/js/api_instance.js \
  web/pgadmin/browser/static/js/node_ajax.js

Repository: pgadmin-org/pgadmin4

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -u

echo "== locate relevant files =="
git ls-files | fgrep -E '(^|/)api_instance\.js$|node_ajax\.js$|axios|api' | sed -n '1,200p'

echo
echo "== api_instance.js outline/size/contents =="
wc -l web/pgadmin/static/js/api_instance.js
sed -n '1,220p' web/pgadmin/static/js/api_instance.js

echo
echo "== node_ajax.js relevant symbols/size =="
wc -l web/pgadmin/browser/static/js/node_ajax.js
sed -n '1,220p' web/pgadmin/browser/static/js/node_ajax.js

echo
echo "== all getApiInstance calls =="
rg -n -C2 'getApiInstance|_inflightGetRequests|apiInstance|axios\.get' web/pgadmin/static/js web/pgadmin -g '*.js'

Repository: pgadmin-org/pgadmin4

Length of output: 50376


🌐 Web query:

Axios defaults request timeout default 0 infinite

💡 Result:

The default value for the Axios request timeout is 0 [1][2]. In Axios, a timeout value of 0 means there is no timeout, effectively causing the request to wait indefinitely [1][3][4]. It is highly recommended to set an explicit timeout value in production environments [1]. Without a defined timeout, a stalled or hanging request can remain open indefinitely, which may lead to resource exhaustion or unexpected application behavior [1][3]. When a request exceeds the specified timeout duration, Axios aborts the request and throws an error with the code 'ECONNABORTED' [1][4]. Note that the standard timeout property primarily governs response timeouts; it may not always account for connection-level issues, for which alternative approaches like AbortSignal are recommended [5].

Citations:


Enforce a timeout before registering shared GET requests.

getInflight() stores the Axios promise before it settles, and the axios instance has no timeout/abort configuration, so a stalled request can retain the key forever and make later callers await the same pending request. Add a finite timeout (and/or cancellation handling) inside the request wrapper and clean the map on both abort and resolution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/static/js/api_instance.js` around lines 56 - 59, Update
getInflight() so each shared Axios GET request uses a finite timeout or
cancellation mechanism before being stored in _inflightGetRequests. Ensure the
map entry is removed when the request aborts, rejects, or resolves, preventing
stalled requests from retaining the key indefinitely.

…sions

Address CodeRabbit review on pgadmin-org#10159. getInflight is exported, so key
building should be safe regardless of caller, even though the sole caller
today (getNodeAjaxOptions) can't trigger these cases:

- Include config.headers in the key so callers differing only in headers
  (e.g. a different Authorization) never share an in-flight response.
- Type-tag primitives in stableStringify so values with the same JSON form
  but different types no longer collide (number 1 vs string "1"), and handle
  undefined so [undefined] no longer collapses to [].

Insertion-order independence and the undefined-params default are unchanged.
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.

Duplicate concurrent get_types (and other dropdown) requests when many rows mount at once

3 participants