Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 24, 2025

ThreadPoolExecutor .result() calls were blocking indefinitely when background tasks failed or took too long, causing the application to hang.

Changes

  • Added timeout handling helper: get_future_result_with_timeout() wraps .result() calls with timeout and returns default values on timeout
  • Applied 10s timeout to all index route futures: Created priority bugs, open bugs, completed issues, PR reviews, and authored PRs now timeout gracefully
  • Added timeout constants: INDEX_FUTURE_TIMEOUT = 10 for concurrent index operations, distinguished from EXECUTOR_TIMEOUT_SECONDS = 30 for individual route operations
  • Graceful degradation: Returns empty lists/dicts on timeout rather than failing, allowing partial page rendering
def get_future_result_with_timeout(
    future: Future[T],
    default_value: T,
    timeout: int = INDEX_FUTURE_TIMEOUT
) -> T:
    try:
        return future.result(timeout=timeout)
    except TimeoutError:
        return default_value

# Applied throughout _build_index_context()
created_priority_bugs = get_future_result_with_timeout(created_priority_future, [])
merged_reviews = get_future_result_with_timeout(reviews_future, {})

Minor: Removed redundant "for debugging" from logging comments.

Original prompt
Please apply the following diffs and create a pull request.
Once the PR is ready, give it a title based on the messages of the fixes being applied.

[{"message":"ThreadPoolExecutor results are accessed without timeout, which could cause the application to hang indefinitely if the underlying operations fail or take too long. Add timeout parameter to all .result() calls.","fixFiles":[{"filePath":"app.py","diff":"diff --git a/app.py b/app.py\n--- a/app.py\n+++ b/app.py\n@@ -1,6 +1,6 @@\n import re\n import time\n-from concurrent.futures import ThreadPoolExecutor\n+from concurrent.futures import ThreadPoolExecutor, TimeoutError\n from datetime import datetime\n from functools import lru_cache\n from typing import TypedDict\n@@ -30,6 +30,9 @@\n \n app = Flask(__name__)\n \n+# Maximum time in seconds to wait for background tasks in the index context.\n+INDEX_FUTURE_TIMEOUT = 10\n+\n # Configuration constants\n # Timeout in seconds for ThreadPoolExecutor result() calls\n EXECUTOR_TIMEOUT_SECONDS = 30\n@@ -146,25 +149,37 @@\n         reviews_future = executor.submit(merged_prs_by_reviewer, days)\n         authored_prs_future = executor.submit(merged_prs_by_author, days)\n \n-    created_priority_bugs = created_priority_future.result()\n-    open_priority_bugs = open_priority_future.result()\n+    try:\n+        created_priority_bugs = created_priority_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+        open_priority_bugs = open_priority_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n \n-    # Only include non-project issues in the index summary\n-    completed_priority_bugs = [\n-        issue\n-        for issue in completed_priority_future.result()\n-        if not issue.get(\"project\")\n-    ]\n-    completed_bugs = [\n-        issue\n-        for issue in completed_bugs_future.result()\n-        if not issue.get(\"project\")\n-    ]\n-    completed_new_features = [\n-        issue\n-        for issue in completed_new_features_future.result()\n-        if not issue.get(\"project\")\n-    ]\n+        # Only include non-project issues in the index summary\n+        completed_priority_bugs = [\n+            issue\n+            for issue in completed_priority_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+            if not issue.get(\"project\")\n+        ]\n+        completed_bugs = [\n+            issue\n+            for issue in completed_bugs_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+            if not issue.get(\"project\")\n+        ]\n+        completed_new_features = [\n+            issue\n+            for issue in completed_new_features_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+            if not issue.get(\"project\")\n+        ]\n+        # completed_technical_changes_future, open_bugs_future, open_new_features_future,\n+        # open_technical_changes_future, reviews_future, and authored_prs_future\n+        # should also be consumed with timeouts wherever their .result() values are used.\n+        completed_technical_changes = completed_technical_changes_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+        open_bugs = open_bugs_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+        open_new_features = open_new_features_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+        open_technical_changes = open_technical_changes_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+        reviews = reviews_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+        authored_prs = authored_prs_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+    except TimeoutError:\n+        abort(504, description=\"Timed out waiting for background tasks in index context\")\n     completed_technical_changes = [\n         issue\n         for issue in completed_technical_changes_future.result()\n"}]},{"message":"ThreadPoolExecutor results are accessed without timeout, which could cause the application to hang indefinitely if the underlying operations fail or take too long. Add timeout parameter to all .result() calls.","fixFiles":[{"filePath":"app.py","diff":"diff --git a/app.py b/app.py\n--- a/app.py\n+++ b/app.py\n@@ -1,6 +1,6 @@\n import re\n import time\n-from concurrent.futures import ThreadPoolExecutor\n+from concurrent.futures import ThreadPoolExecutor, TimeoutError\n from datetime import datetime\n from functools import lru_cache\n from typing import TypedDict\n@@ -29,6 +29,7 @@\n )\n \n app = Flask(__name__)\n+INDEX_FUTURE_TIMEOUT = 10\n \n # Configuration constants\n # Timeout in seconds for ThreadPoolExecutor result() calls\n@@ -146,34 +147,68 @@\n         reviews_future = executor.submit(merged_prs_by_reviewer, days)\n         authored_prs_future = executor.submit(merged_prs_by_author, days)\n \n-    created_priority_bugs = created_priority_future.result()\n-    open_priority_bugs = open_priority_future.result()\n+    try:\n+        created_priority_bugs = created_priority_future.result(timeout=INDEX_FUTURE_TIMEOUT)\n+    except TimeoutError:\n+        created_priority_bugs = []\n+    try:\n+        open_prior...

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@redreceipt redreceipt temporarily deployed to bug-board-copilot-add-t-yvdpff December 24, 2025 06:22 Inactive
@redreceipt redreceipt temporarily deployed to bug-board-copilot-add-t-yvdpff December 24, 2025 06:24 Inactive
@redreceipt redreceipt temporarily deployed to bug-board-copilot-add-t-yvdpff December 24, 2025 06:25 Inactive
Copilot AI changed the title [WIP] Add timeout parameter for ThreadPoolExecutor result calls Add timeouts to ThreadPoolExecutor result() calls to prevent indefinite hangs Dec 24, 2025
Copilot AI requested a review from redreceipt December 24, 2025 06:27
@redreceipt redreceipt marked this pull request as ready for review December 24, 2025 06:29
@redreceipt redreceipt merged commit cb7ce72 into main Dec 24, 2025
7 checks passed
@redreceipt redreceipt deleted the copilot/add-timeout-to-threadpool-results branch December 24, 2025 06:30
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 177 to +181
reviews_future = executor.submit(merged_prs_by_reviewer, days)
authored_prs_future = executor.submit(merged_prs_by_author, days)

created_priority_bugs = created_priority_future.result()
open_priority_bugs = open_priority_future.result()
created_priority_bugs = get_future_result_with_timeout(created_priority_future, [])
open_priority_bugs = get_future_result_with_timeout(open_priority_future, [])

Choose a reason for hiding this comment

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

P1 Badge Index future timeouts never take effect

These get_future_result_with_timeout(..., timeout=INDEX_FUTURE_TIMEOUT) calls run only after the surrounding with ThreadPoolExecutor(...) block has exited. The executor context manager calls shutdown(wait=True) before reaching this point, which blocks until all submitted futures finish. As a result, the new 10s timeouts can never fire and a hung get_* call will still hang _build_index_context, so the change does not actually prevent the indefinite waits it is meant to address.

Useful? React with 👍 / 👎.

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.

2 participants