codeant-ai-for-open-source[bot] commented on code in PR #42033:
URL: https://github.com/apache/superset/pull/42033#discussion_r3600768719


##########
superset-frontend/src/middleware/asyncEvent.ts:
##########
@@ -151,26 +191,22 @@ const setLastId = (asyncEvent: AsyncEvent) => {
 export const processEvents = async (events: AsyncEvent[]) => {
   events.forEach((asyncEvent: AsyncEvent) => {
     const jobId = asyncEvent.job_id;
-    const listener = listenersByJobId.get(jobId);
-    // `jobId` originates from server/WebSocket payloads, so the listener is
-    // resolved exclusively through a Map (never plain-object property access,
-    // which would expose the prototype chain), and we confirm the retrieved
-    // value is a registered function before dispatching the event to it.
-    if (typeof listener === 'function') {
+    const listener = listenersByJobId[jobId];
+    if (listener) {
       listener(asyncEvent);
-      retriesByJobId.delete(jobId);
+      delete retriesByJobId[jobId];
     } else {
       // handle race condition where event is received
       // before listener is registered
-      const retries = (retriesByJobId.get(jobId) ?? 0) + 1;
-      retriesByJobId.set(jobId, retries);
+      if (!retriesByJobId[jobId]) retriesByJobId[jobId] = 0;
+      retriesByJobId[jobId] += 1;
 
-      if (retries <= MAX_RETRIES) {
+      if (retriesByJobId[jobId] <= MAX_RETRIES) {
         setTimeout(() => {
           processEvents([asyncEvent]);
-        }, RETRY_DELAY * retries);
+        }, RETRY_DELAY * retriesByJobId[jobId]);
       } else {
-        retriesByJobId.delete(jobId);
+        delete retriesByJobId[jobId];
         logging.warn('listener not found for job_id', asyncEvent.job_id);
       }

Review Comment:
   **Suggestion:** The retry path drops an event permanently after 
`MAX_RETRIES`, but there is no fallback to deliver that terminal event when the 
listener is registered later. This can leave the corresponding async chart 
request hanging forever (no resolve/reject) if the event arrives before 
listener registration and registration is delayed (for example in heavy UI work 
or background-tab throttling). Keep unmatched terminal events in a per-job 
buffer and flush them from `addListener`, or explicitly fail pending jobs when 
retries are exhausted. [race condition]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   ❌ Charts using async queries can hang indefinitely awaiting events.
   ❌ Native filter controls may never load default async values.
   ⚠️ AsyncEvent tests don't cover listener-missing race drop behavior.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start the Superset frontend with the GlobalAsyncQueries feature flag 
enabled so the
   async event middleware `init` runs 
(superset-frontend/src/middleware/asyncEvent.ts:34-60,
   with `init();` at line 62 starting polling via `loadEventsFromApi` or 
WebSocket via
   `wsConnect`).
   
   2. Trigger a chart-data request that is handled by `exploreJSON` in
   superset-frontend/src/components/Chart/chartAction.ts:734-942 (e.g., loading 
a chart or
   dashboard), which eventually calls `handleChartDataResponse` (lines 700-752) 
and, when the
   HTTP response status is 202, invokes `waitForAsyncData(...)` (lines 36-45 in 
the excerpt
   at 680-759) with the async event payload.
   
   3. In the async middleware, `waitForAsyncData`
   (superset-frontend/src/middleware/asyncEvent.ts:107-171) calls 
`addListener(jobId,
   listener)` (lines 78-80, 171) to register a listener in `listenersByJobId`. 
If an async
   event for that job (status DONE or ERROR) is delivered by 
`loadEventsFromApi` (lines
   226-246) before the listener is registered (due to network/UI delay or 
background tab
   throttling), `loadEventsFromApi` still calls `fetchEvents(eventArgs)` and 
then
   `processEvents(events)` (line 234).
   
   4. Inside `processEvents` 
(superset-frontend/src/middleware/asyncEvent.ts:191-215), when
   `listenersByJobId[jobId]` is undefined the else branch (lines 199-211) runs: 
it increments
   `retriesByJobId[jobId]` (lines 201-202), schedules 
`processEvents([asyncEvent])` via
   `setTimeout` (lines 204-207), and after more than `MAX_RETRIES` (line 59) 
deletes
   `retriesByJobId[jobId]` and logs a warning (lines 208-210). The event is 
dropped
   permanently while `setLastId(asyncEvent)` (line 213) advances 
`lastReceivedEventId`. When
   `waitForAsyncData` is eventually called for that job from ChartAction.ts 
(lines 36-45),
   FilterValue.tsx (lines 25-37), or FiltersConfigForm.tsx (lines 24-34), no 
further events
   for that job ever reach the listener, so the Promise returned by 
`waitForAsyncData` never
   resolves or rejects and the associated chart or filter stays stuck in a 
loading state.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=89e07b5049e54b86b3b355834bed1022&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=89e07b5049e54b86b3b355834bed1022&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/middleware/asyncEvent.ts
   **Line:** 204:211
   **Comment:**
        *Race Condition: The retry path drops an event permanently after 
`MAX_RETRIES`, but there is no fallback to deliver that terminal event when the 
listener is registered later. This can leave the corresponding async chart 
request hanging forever (no resolve/reject) if the event arrives before 
listener registration and registration is delayed (for example in heavy UI work 
or background-tab throttling). Keep unmatched terminal events in a per-job 
buffer and flush them from `addListener`, or explicitly fail pending jobs when 
retries are exhausted.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42033&comment_hash=22d750aaaa676f780db6c92a1044197ec5cef5b0b3483a599ad961401fd9ce3d&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42033&comment_hash=22d750aaaa676f780db6c92a1044197ec5cef5b0b3483a599ad961401fd9ce3d&reaction=dislike'>👎</a>



##########
superset-frontend/src/middleware/asyncEvent.ts:
##########
@@ -190,7 +226,7 @@ const getPollingDelay = () => {
 const loadEventsFromApi = async () => {
   const generation = pollingGeneration;
   const eventArgs = lastReceivedEventId ? { last_id: lastReceivedEventId } : 
{};
-  if (listenersByJobId.size) {
+  if (Object.keys(listenersByJobId).length) {

Review Comment:
   **Suggestion:** Conditionally skipping `fetchEvents` when there are no 
active listeners prevents `lastReceivedEventId` from advancing during idle 
periods, so the next active request can trigger a large backlog fetch and 
replay of stale events. That creates avoidable latency spikes and extra timer 
churn before current jobs are processed. Continue polling to advance 
`lastReceivedEventId` (even without listeners), and only gate listener 
dispatching. [performance]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   ⚠️ First async chart after idle can incur backlog delay.
   ⚠️ Async polling performs extra retries for stale unhandled events.
   ⚠️ Potential spikes in network and timer work on resume.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. On frontend startup, the async middleware `init`
   (superset-frontend/src/middleware/asyncEvent.ts:34-60) is executed (line 
62), loading
   `lastReceivedEventId` from localStorage (lines 48-52) and starting polling 
via
   `loadEventsFromApi` when `transport === TRANSPORT_POLLING` (lines 54-56).
   
   2. While there are no active async chart jobs, `listenersByJobId` remains 
empty because
   the only place that registers listeners is `waitForAsyncData` 
(asyncEvent.ts:107-171),
   which is invoked from chart and native filter code when an HTTP 202 
chart-data response is
   received (ChartAction.ts:17-45 at 680-759, FilterValue.tsx:18-37 at 270-329,
   FiltersConfigForm.tsx:15-35 at 520-579).
   
   3. During this idle period, any async events emitted on the server-side for 
this user are
   not fetched by the client because `loadEventsFromApi` 
(asyncEvent.ts:226-246) wraps the
   call to `fetchEvents(eventArgs)` and subsequent `processEvents(events)` 
inside `if
   (Object.keys(listenersByJobId).length) { ... }` (lines 229-240); with zero 
listeners, the
   function returns after the guard and `lastReceivedEventId` (used in 
`eventArgs` at line
   228) never advances.
   
   4. When a new chart-data request later receives a 202 and registers a 
listener via
   `waitForAsyncData`, the next invocation of `loadEventsFromApi` finally calls
   `fetchEvents(eventArgs)` with a stale `lastReceivedEventId`. The backend 
returns the
   backlog of all events since that cursor, and `processEvents(events)`
   (asyncEvent.ts:191-215) iterates them: events for jobs without listeners go 
through the
   retry loop (lines 199-211) before being dropped, and only then are current 
job events
   processed. This creates an avoidable latency spike for the first active 
async chart and
   extra `setTimeout` churn as stale events are retried and logged before the 
latest job’s
   data is delivered.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=2878e2de98094754ae607a2ecc2b70cf&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=2878e2de98094754ae607a2ecc2b70cf&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/middleware/asyncEvent.ts
   **Line:** 229:240
   **Comment:**
        *Performance: Conditionally skipping `fetchEvents` when there are no 
active listeners prevents `lastReceivedEventId` from advancing during idle 
periods, so the next active request can trigger a large backlog fetch and 
replay of stale events. That creates avoidable latency spikes and extra timer 
churn before current jobs are processed. Continue polling to advance 
`lastReceivedEventId` (even without listeners), and only gate listener 
dispatching.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42033&comment_hash=95ad4554b5c8b527282b2fa9dbaa5c94836cead9eaf2b4874775ebe3a916c4e6&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F42033&comment_hash=95ad4554b5c8b527282b2fa9dbaa5c94836cead9eaf2b4874775ebe3a916c4e6&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to