Skip to content

Conversation

@chandrasekharan-zipstack
Copy link
Contributor

@chandrasekharan-zipstack chandrasekharan-zipstack commented Nov 2, 2025

What

  • Added proper polling lifecycle management to prevent infinite polling for in-progress executions
  • Implemented timeout mechanism to stop polling after 1 hour for potentially stuck executions
  • Added proper cleanup of polling intervals and timeouts on component unmount

Why

  • Executions in EXECUTING/PENDING state were being polled indefinitely even when stuck
  • No mechanism existed to detect and stop polling for stale executions
  • Memory leaks possible from uncleared intervals/timeouts

How

Frontend Changes (DetailedLogs.jsx):

  • Added useRef to store polling interval ID for proper cleanup
  • Implemented shouldPoll() function to check execution status and age
  • Stop polling if execution is older than 1 hour from creation time
  • Added clearPolling() to properly cleanup intervals
  • Updated useEffect hooks to clear polling on component unmount

Frontend Changes (ExecutionLogs.jsx):

  • Added useRef to store polling timeout IDs for multiple executions
  • Implemented shouldPoll() using modified_at timestamp
  • Added clearPollingTimeout() and clearAllPolling() for cleanup
  • Updated polling logic to stop for executions older than 1 hour

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

No, this PR does not break existing features:

  • Polling still works for active EXECUTING/PENDING executions
  • Only stops polling for stale executions (>1 hour old) which are likely stuck
  • Proper cleanup prevents memory leaks without affecting normal operation
  • All existing polling behavior preserved for active executions

Related Issues or PRs

  • JIRA: UN-2454

Notes on Testing

  • Test with an execution that completes normally (polling should stop on completion)
  • Test with an execution stuck in EXECUTING state for >1 hour (polling should stop)
  • Test component unmount during active polling (no memory leaks)
  • Verify no infinite polling in browser dev tools Network tab
  • Check DetailedLogs page with EXECUTING status
  • Check ExecutionLogs page with multiple EXECUTING items

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

- Update DetailedLogs and ExecutionLogs components to properly handle polling state - Update index.js dependencies - Update workers dependencies in pyproject.toml and uv.lock 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 2, 2025

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved polling stability and data freshness for execution logs and detailed logs. Polling now automatically stops for logs older than one hour, reducing unnecessary background activity and improving performance.
  • Performance

    • Enhanced polling lifecycle management to prevent stale interval overlap and ensure proper cleanup on navigation and component unmount.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds refs-based polling and cleanup to DetailedLogs and ExecutionLogs; introduces shouldPoll (EXECUTING/PENDING + one-hour freshness using createdAtRaw), surfaces createdAtRaw/totalFiles/workflowName/pipelineName from fetches, and centralizes interval/timeout management with cleanup on dependency changes and unmount.

Changes

Cohort / File(s) Summary
Detailed Logs Component
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx
Added useRef for pollingIntervalRef and executionDetailsRef; added shouldPoll(executionDetails) (EXECUTING/PENDING + <1hr) and clearPolling(); extended fetches to include createdAtRaw; replaced inline interval handling with a ref-driven 5s polling loop and ensured cleanup on deps/unmount; updated rendering to use createdAtRaw.
Execution Logs Component
frontend/src/components/logging/execution-logs/ExecutionLogs.jsx
Replaced state-based polling set with refs: pollingTimeoutsRef and pollingIdsRef; added shouldPoll(item) (EXECUTING/PENDING + <1hr), clearPollingTimeout(id), and clearAllPolling(); per-item polling now uses timeouts and for...of; fetch mapping adds createdAtRaw, totalFiles, workflowName, pipelineName; clears polling on unmount and path/tab changes; updates items with progress/derived fields.

Sequence Diagram(s)

sequenceDiagram participant UI as Component (DetailedLogs / ExecutionLogs) participant Fetch as fetchExecutionDetails / fetchExecutionFiles / fetchLogs participant Gate as shouldPoll() participant Timer as Interval/Timeout Ref UI->>Fetch: initial fetch (include createdAtRaw, totalFiles, names) Fetch-->>UI: data (items / executionDetails with createdAtRaw) UI->>Gate: evaluate status + createdAtRaw alt shouldPoll == true Gate-->>UI: allow polling UI->>Timer: start interval/timeout (store ref) loop Poll cycles (every 5s or per-item timeout) Timer->>Fetch: poll update for item/execution Fetch-->>UI: updated item/details UI->>Gate: re-evaluate status + createdAtRaw alt continue polling Gate-->>Timer: keep timer/timeout else stop polling Gate-->>UI: stop polling UI->>Timer: clear ref (clearPolling / clearAllPolling) end end else shouldPoll == false Gate-->>UI: no polling started end UI->>Timer: on unmount / navigate / tab change Timer-->>UI: clear timers/timeouts 
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to inspect:
    • One-hour freshness logic using createdAtRaw and timezone/format assumptions.
    • Correct clearing of intervals/timeouts before creating new ones and on unmount.
    • Per-item polling bookkeeping in pollingTimeoutsRef / pollingIdsRef to avoid leaks/duplicates.
    • Data mapping additions (createdAtRaw, totalFiles, workflowName, pipelineName) and downstream uses (rendering, progress calculation).

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies the main fix—preventing infinite polling for in-progress executions—and is concise and specific.
Description check ✅ Passed The PR description covers all required template sections with substantial detail: What (polling lifecycle management), Why (infinite polling issue and memory leaks), How (implementation details for both files), breaking changes assessment, testing notes, and related issues.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch UN-2454-avoid-polling-infinitely-for-inprogress-execution

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 and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 66 passed, 0 failed (66 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_time\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_delay\_would\_exceed\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{66}}$$ $$\textcolor{#23d18b}{\tt{66}}$$
Reverted files that were not part of the intended fix: - frontend/src/index.js - workers/pyproject.toml - workers/uv.lock 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between e340c35 and cc00f33.

📒 Files selected for processing (2)
  • frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (3 hunks)
  • frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (5 hunks)
- Fixed date calculation bug: use raw ISO timestamps instead of formatted display strings - Added createdAtRaw and modified_atRaw fields to preserve parseable date values - Implement stale interval re-check: stop polling when execution is >1 hour old via executionDetailsRef - Replace forEach loops with for...of for improved performance (4 instances) Fixes CodeRabbit comments about infinite polling and ensures logging components correctly handle timestamp calculations and respect staleness thresholds. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (2)

168-179: LGTM!

The refactoring to for...of loops is cleaner and more idiomatic. No functional changes.


357-360: Remove redundant unmount cleanup effect.

The effect at lines 334-355 already returns clearPolling in its cleanup function, which runs on unmount. This separate empty-dependency effect is redundant—both cleanup functions will call clearPolling() when the component unmounts. While safe (due to the existence check in clearPolling), it adds unnecessary code.

Apply this diff to remove the duplicate:

- // Clear polling when component unmounts - useEffect(() => { - return clearPolling; - }, []); -
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between cc00f33 and 01f088f.

📒 Files selected for processing (2)
  • frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (5 hunks)
  • frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (6 hunks)
🔇 Additional comments (12)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (5)

1-1: LGTM!

The addition of useRef and the two refs for polling management is appropriate for tracking interval IDs and latest execution state across renders.

Also applies to: 64-67


95-101: LGTM!

The clearPolling implementation correctly clears the interval and resets the ref. The existence check makes it safe to call multiple times.


103-134: LGTM!

Adding createdAtRaw at line 113 correctly preserves the raw ISO timestamp for staleness checks, addressing previous review feedback.


329-332: LGTM!

This effect correctly keeps executionDetailsRef in sync with the latest executionDetails state, enabling mid-interval staleness checks as recommended in previous reviews.


334-355: LGTM!

The polling effect correctly:

  • Clears existing intervals before creating new ones
  • Re-checks staleness mid-interval using executionDetailsRef.current
  • Cleans up on dependency changes and unmount

This addresses the past review feedback about re-evaluating the staleness check during the polling cycle.

frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (7)

3-3: LGTM!

Adding useRef and pollingTimeoutsRef for tracking multiple timeout IDs is appropriate. The object structure allows efficient keyed cleanup by execution ID.

Also applies to: 47-48


123-138: LGTM!

Both clearPollingTimeout and clearAllPolling correctly manage timeout cleanup. The existence checks and state resets are properly implemented.


140-202: LGTM!

pollExecutingRecord correctly:

  • Populates createdAtRaw for staleness checks (line 162)
  • Uses shouldPoll to determine when to stop polling (lines 174-175)
  • Cleans up timeouts when polling stops (line 181)
  • Stores new timeout IDs in the ref for proper lifecycle management (line 189)

204-215: LGTM!

startPollingForExecuting correctly uses shouldPoll to filter records and prevents duplicate polling with the pollingIds check. The for...of loop is clean and idiomatic.


217-267: LGTM!

Line 243 correctly populates createdAtRaw with the raw ISO timestamp from the API response, enabling accurate staleness checks.


296-299: LGTM!

The effect correctly clears all polling when the view changes (id or activeTab) or when the component unmounts, preventing memory leaks and stale polling.


301-308: LGTM!

Line 304 correctly clears all existing polling before fetching new logs, ensuring that timeouts for records no longer in the view are properly cleaned up.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Chandrasekharan M <117059509+chandrasekharan-zipstack@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (2)

170-174: Good loop improvements (incidental refactor).

The changes from for...in to for...of with Object.entries() and array iteration are more idiomatic and avoid potential issues with inherited properties, though they're not directly related to the PR's polling lifecycle goal.

Also applies to: 178-180


359-362: Optional: Redundant unmount cleanup.

This cleanup effect is redundant with the cleanup returned by the polling effect (line 356), which already runs on unmount. While harmless (since clearPolling() is idempotent), this could be removed for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 01f088f and 1461cec.

📒 Files selected for processing (1)
  • frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (6)

1-1: LGTM! Proper ref-based interval management.

The addition of useRef and the two refs (pollingIntervalRef for interval cleanup and executionDetailsRef for mid-cycle freshness checks) follows React best practices for managing side effects with intervals.

Also applies to: 64-67


71-95: LGTM! Previous critical validation issues now resolved.

The shouldPoll() implementation correctly:

  • Validates all inputs before computing staleness
  • Handles Invalid Date scenarios with isNaN(createdAt.getTime())
  • Uses the raw ISO timestamp for accurate time calculations
  • Stops polling for non-EXECUTING/PENDING statuses or executions older than 1 hour

This addresses the previous critical issues raised about timestamp parsing and validation failures.


97-103: LGTM! Clean interval cleanup utility.

The clearPolling() function is idempotent and properly resets the ref, making it safe to call from multiple cleanup paths.


115-115: LGTM! Raw timestamp preserved for accurate staleness checks.

Adding createdAtRaw preserves the ISO timestamp from the backend, enabling reliable Date parsing and time calculations in shouldPoll(). This resolves the previous critical issue with formatted string parsing.


331-334: LGTM! Ref synchronization enables fresh staleness checks.

This effect ensures executionDetailsRef always holds the latest execution details, allowing the polling interval to re-evaluate staleness mid-cycle using fresh data.


336-357: LGTM! Robust polling lifecycle with mid-cycle staleness detection.

The polling implementation correctly:

  • Clears any existing interval before starting a new one (prevents overlaps)
  • Re-checks shouldPoll() inside the interval callback using the latest execution details from the ref
  • Stops polling mid-cycle when the 1-hour threshold is crossed
  • Cleans up on dependency changes and unmount

This addresses the previous critical issue where intervals continued running past the 1-hour mark.

…mponents Replace isNaN() with isFinite() for date validation in ExecutionLogs and DetailedLogs to prevent infinite polling when invalid timestamps occur. - DetailedLogs.jsx: Changed isNaN() to isFinite() check - ExecutionLogs.jsx: Added missing isFinite() date validation 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1461cec and 944448c.

📒 Files selected for processing (2)
  • frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (5 hunks)
  • frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
Use ref instead of state for pollingIds to prevent stale closure issues in polling logic: - Added pollingIdsRef to track actively polling execution IDs - Updated all polling operations to directly mutate ref (no re-renders needed) - Removed redundant pollingIds state to eliminate unnecessary re-renders - Prevents duplicate polling loops and potential memory leaks 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@jagadeeswaran-zipstack jagadeeswaran-zipstack left a comment

Choose a reason for hiding this comment

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

Please address the minor changes requested.

@chandrasekharan-zipstack chandrasekharan-zipstack changed the title UN-2454 [FIX] Avoid polling infinitely for in-progress execution UN-2454 [WIP] Avoid polling infinitely for in-progress execution Nov 19, 2025
@chandrasekharan-zipstack chandrasekharan-zipstack changed the title UN-2454 [WIP] Avoid polling infinitely for in-progress execution UN-2454 [FIX] Avoid polling infinitely for in-progress execution Dec 1, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (1)

424-427: Potential TypeError if status is undefined.

executionDetails?.status.toLowerCase() will throw if status is null or undefined because optional chaining doesn't guard the subsequent .toLowerCase() call.

Apply this diff to add defensive access:

- {executionDetails?.status.toLowerCase() === "executing" + {executionDetails?.status?.toLowerCase() === "executing" ? "Running for" : "Ran for"}

The same pattern should be applied on line 437.

🧹 Nitpick comments (2)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (1)

359-362: Consider removing redundant unmount cleanup effect.

The cleanup function returned on line 356 already runs on unmount (React invokes cleanup when the component unmounts, regardless of which dependencies triggered it). This separate effect duplicates that behavior.

You can safely remove this effect:

- // Clear polling when component unmounts - useEffect(() => { - return clearPolling; - }, []);
frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (1)

100-123: Timestamp validation correctly handles invalid dates.

The Number.isFinite(createdAt.getTime()) check properly addresses the past comment about invalid timestamps. When createdAtRaw and created_at are both missing, this returns false and stops polling as expected.

Consider guarding against future timestamps.

If createdAt is in the future (due to clock skew or invalid data), timeDifference would be negative and the 1-hour check would never trigger, allowing indefinite polling. Consider adding a check:

 const timeDifference = now - createdAt; +  + // Guard against future timestamps + if (timeDifference < 0) return false; if (timeDifference > oneHourInMs) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 2be28eb and 444541d.

📒 Files selected for processing (2)
  • frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (5 hunks)
  • frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (14)
frontend/src/components/logging/detailed-logs/DetailedLogs.jsx (6)

64-67: Good use of refs for polling state management.

Using useRef for the interval ID and latest execution details is the correct pattern for managing mutable values that need to persist across renders and be accessible in interval callbacks without causing stale closure issues.


71-95: Well-implemented polling eligibility check.

The shouldPoll function correctly validates:

  • Execution details exist
  • Status is EXECUTING or PENDING
  • Timestamp is present and valid (using Number.isFinite)
  • Execution is not stale (< 1 hour old)

The early returns for invalid states ensure defensive behavior.


97-103: LGTM!

The clearPolling helper properly clears the interval and nullifies the ref to prevent duplicate cleanup attempts.


114-128: Correct fix for timestamp parsing.

Adding createdAtRaw preserves the original ISO timestamp from the API, enabling reliable date parsing in shouldPoll. This addresses the previous review feedback about formatted display strings causing Invalid Date.


331-334: LGTM!

Keeping executionDetailsRef synchronized with the latest executionDetails state ensures the interval callback always has access to current data, avoiding stale closure issues.


336-357: Polling logic correctly addresses previous review feedback.

The implementation now:

  • Clears existing intervals before setting new ones (prevents leaks)
  • Re-checks staleness inside the interval callback using the ref
  • Returns cleanup function for dependency changes

One consideration: the pagination.current dependency means the polling interval restarts on every page change. This is likely intentional to fetch the correct page, but be aware it couples pagination navigation with polling lifecycle.

frontend/src/components/logging/execution-logs/ExecutionLogs.jsx (8)

3-3: LGTM!

The addition of useRef to the imports is correct and necessary for the polling lifecycle management implemented in this PR.


46-49: LGTM!

Using refs to store polling timeouts and IDs is the correct approach to avoid stale closure issues and prevent unnecessary re-renders during polling updates.


125-131: LGTM!

The clearPollingTimeout function correctly clears the timeout and removes the entry from the ref, preventing memory leaks.


133-141: LGTM!

The clearAllPolling function correctly clears all active timeouts and resets both refs, ensuring thorough cleanup when switching views or unmounting.


143-199: LGTM!

The pollExecutingRecord function correctly:

  • Updates the record in dataList with fresh execution data
  • Uses shouldPoll to determine whether to continue polling
  • Synchronizes pollingIdsRef by removing IDs when polling stops
  • Cleans up timeouts when polling ends or on error
  • Schedules the next poll with a 5-second delay for active executions

This implementation properly addresses the past comment about stale closures by maintaining ref synchronization.


201-209: LGTM!

The startPollingForExecuting function correctly:

  • Uses pollingIdsRef to track which records are already being polled
  • Adds the record key to the ref before initiating polling to prevent race conditions
  • Only starts polling for records that pass the shouldPoll check

This implementation avoids the stale closure issue mentioned in past comments by using refs instead of state.


211-261: LGTM!

The fetchLogs function correctly:

  • Maps createdAtRaw from the API response (line 237) to enable staleness checks in shouldPoll
  • Initiates polling for executing/pending records after updating dataList (line 255)
  • Works in conjunction with clearAllPolling (called before fetch in the useEffect) to prevent stale polling

290-302: LGTM!

The cleanup logic correctly:

  • Clears all polling on component unmount or when id/activeTab changes (lines 290-293)
  • Clears polling before fetching new logs to prevent stale polls (line 298)

The activeTab dependency appears in both useEffect hooks, causing clearAllPolling to be called twice when the tab changes. This is safe (idempotent cleanup) but creates slight redundancy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants