Skip to content

Add semaphore to limit concurrent API calls during Kubernetes observer startup#11

Open
tomerqodo wants to merge 2 commits intocoderabbit_full_base_add_semaphore_to_limit_concurrent_api_calls_during_kubernetes_observer_startup_pr11from
coderabbit_full_head_add_semaphore_to_limit_concurrent_api_calls_during_kubernetes_observer_startup_pr11
Open

Add semaphore to limit concurrent API calls during Kubernetes observer startup#11
tomerqodo wants to merge 2 commits intocoderabbit_full_base_add_semaphore_to_limit_concurrent_api_calls_during_kubernetes_observer_startup_pr11from
coderabbit_full_head_add_semaphore_to_limit_concurrent_api_calls_during_kubernetes_observer_startup_pr11

Conversation

@tomerqodo
Copy link
Copy Markdown

@tomerqodo tomerqodo commented Jan 30, 2026

Benchmark PR from agentic-review-benchmarks#11

Summary by CodeRabbit

  • New Features

    • Added configurable concurrency control for API requests during Kubernetes observer startup to prevent API server overload. Defaults to 5 concurrent requests.
  • Tests

    • Removed observer test suite.

desertaxle and others added 2 commits January 25, 2026 12:10
…r startup

closes PrefectHQ#19937

When the observer starts, it receives events for all existing pods/jobs
and checks for duplicates via API calls. This can overwhelm the server
when there are many pods. This change adds a configurable semaphore
(default: 5 concurrent calls) to rate-limit these startup checks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

A semaphore-based concurrency control mechanism is implemented to throttle API calls during Kubernetes observer startup. The new startup_event_concurrency setting (default: 5) is introduced in the settings module and utilized in the observer to gate event filtering requests. The test file for the observer is removed.

Changes

Cohort / File(s) Summary
Concurrency Control Implementation
src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py, src/integrations/prefect-kubernetes/prefect_kubernetes/settings.py
Adds module-level semaphore to throttle startup-related API calls. Semaphore initialized from new startup_event_concurrency setting (default: 5) and wraps event filtering logic to limit concurrent requests during observer startup.
Test Removal
src/integrations/prefect-kubernetes/tests/test_observer.py
Entire test module removed, including fixtures and comprehensive test coverage for pod event replication, event deduplication, lifecycle management, and logging configuration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A semaphore arrives with grace,
To throttle startup's rapid pace,
Five requests dance in sync so true,
No API strain for me and you!
✨ The startup dreams now gently flow,

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is minimal and only states 'Benchmark PR from...' without addressing the required template sections for overview, issue references, or test coverage. Provide a comprehensive description including an overview of changes, reference to issue #19937, and confirmation that unit tests were added or removed tests were addressed.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add semaphore to limit concurrent API calls during Kubernetes observer startup' accurately and clearly summarizes the main change in the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 coderabbit_full_head_add_semaphore_to_limit_concurrent_api_calls_during_kubernetes_observer_startup_pr11

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.

@tomerqodo
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@tomerqodo
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@tomerqodo
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@bar-qodo
Copy link
Copy Markdown

bar-qodo commented Feb 2, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@bar-qodo
Copy link
Copy Markdown

bar-qodo commented Feb 2, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@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

🤖 Fix all issues with AI agents
In `@src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py`:
- Around line 135-159: The semaphore _startup_event_semaphore currently only
guards creation of the EventFilter, leaving the actual API call
orchestration_client.request("/events/filter", ...) unthrottled; move the await
orchestration_client.request(...) call (which sends the
event_filter.model_dump(...)) inside the same async with
_startup_event_semaphore block so the request is executed while the semaphore is
held, ensuring the startup API calls are properly rate-limited and still use the
event_filter constructed earlier.

In `@src/integrations/prefect-kubernetes/prefect_kubernetes/settings.py`:
- Around line 59-64: The Field definition for startup_event_concurrency
currently allows zero or negative values which can create asyncio.Semaphore(0)
and deadlock; update the Field on startup_event_concurrency to include a minimum
constraint by adding ge=1 (e.g., Field(..., ge=1, description=...)) so the
Pydantic model enforces a lower bound and prevents passing 0 or negative values
to the observer startup logic.

Comment on lines +135 to 159
# Use semaphore to limit concurrent API calls during startup to prevent
# overwhelming the API server when there are many existing pods/jobs
async with _startup_event_semaphore:
# Use the Kubernetes event timestamp for the filter to avoid "Query time range is too large" error
event_filter = EventFilter(
event=EventNameFilter(name=[f"prefect.kubernetes.pod.{phase.lower()}"]),
resource=EventResourceFilter(
id=[f"prefect.kubernetes.pod.{uid}"],
),
occurred=EventOccurredFilter(
since=(
k8s_created_time
if k8s_created_time
else (datetime.now(timezone.utc) - timedelta(hours=1))
)
),
)

response = await orchestration_client.request(
"POST",
"/events/filter",
json=dict(filter=event_filter.model_dump(exclude_unset=True, mode="json")),
json=dict(
filter=event_filter.model_dump(exclude_unset=True, mode="json")
),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Semaphore doesn’t cover the API call, so throttling is ineffective.

Right now only EventFilter construction is inside the semaphore; the /events/filter request at Line 153 runs unbounded. Move the request into the async with block.

🐛 Suggested fix
         async with _startup_event_semaphore:
             # Use the Kubernetes event timestamp for the filter to avoid "Query time range is too large" error
             event_filter = EventFilter(
                 event=EventNameFilter(name=[f"prefect.kubernetes.pod.{phase.lower()}"]),
                 resource=EventResourceFilter(
                     id=[f"prefect.kubernetes.pod.{uid}"],
                 ),
                 occurred=EventOccurredFilter(
                     since=(
                         k8s_created_time
                         if k8s_created_time
                         else (datetime.now(timezone.utc) - timedelta(hours=1))
                     )
                 ),
             )
-
-        response = await orchestration_client.request(
-            "POST",
-            "/events/filter",
-            json=dict(
-                filter=event_filter.model_dump(exclude_unset=True, mode="json")
-            ),
-        )
+            response = await orchestration_client.request(
+                "POST",
+                "/events/filter",
+                json=dict(
+                    filter=event_filter.model_dump(exclude_unset=True, mode="json")
+                ),
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Use semaphore to limit concurrent API calls during startup to prevent
# overwhelming the API server when there are many existing pods/jobs
async with _startup_event_semaphore:
# Use the Kubernetes event timestamp for the filter to avoid "Query time range is too large" error
event_filter = EventFilter(
event=EventNameFilter(name=[f"prefect.kubernetes.pod.{phase.lower()}"]),
resource=EventResourceFilter(
id=[f"prefect.kubernetes.pod.{uid}"],
),
occurred=EventOccurredFilter(
since=(
k8s_created_time
if k8s_created_time
else (datetime.now(timezone.utc) - timedelta(hours=1))
)
),
)
response = await orchestration_client.request(
"POST",
"/events/filter",
json=dict(filter=event_filter.model_dump(exclude_unset=True, mode="json")),
json=dict(
filter=event_filter.model_dump(exclude_unset=True, mode="json")
),
)
# Use semaphore to limit concurrent API calls during startup to prevent
# overwhelming the API server when there are many existing pods/jobs
async with _startup_event_semaphore:
# Use the Kubernetes event timestamp for the filter to avoid "Query time range is too large" error
event_filter = EventFilter(
event=EventNameFilter(name=[f"prefect.kubernetes.pod.{phase.lower()}"]),
resource=EventResourceFilter(
id=[f"prefect.kubernetes.pod.{uid}"],
),
occurred=EventOccurredFilter(
since=(
k8s_created_time
if k8s_created_time
else (datetime.now(timezone.utc) - timedelta(hours=1))
)
),
)
response = await orchestration_client.request(
"POST",
"/events/filter",
json=dict(
filter=event_filter.model_dump(exclude_unset=True, mode="json")
),
)
🤖 Prompt for AI Agents
In `@src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py` around
lines 135 - 159, The semaphore _startup_event_semaphore currently only guards
creation of the EventFilter, leaving the actual API call
orchestration_client.request("/events/filter", ...) unthrottled; move the await
orchestration_client.request(...) call (which sends the
event_filter.model_dump(...)) inside the same async with
_startup_event_semaphore block so the request is executed while the semaphore is
held, ensuring the startup API calls are properly rate-limited and still use the
event_filter constructed earlier.

Comment on lines +59 to +64
startup_event_concurrency: int = Field(
default=5,
description="Maximum number of concurrent API calls when checking for "
"duplicate events during observer startup. This helps prevent overloading "
"the API server when there are many existing pods/jobs in the cluster.",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and read the relevant section
head -70 src/integrations/prefect-kubernetes/prefect_kubernetes/settings.py | tail -20

Repository: agentic-review-benchmarks/prefect-combined-coderabbit

Length of output: 989


🏁 Script executed:

# Search for usage of startup_event_concurrency in the codebase
rg "startup_event_concurrency" -A 3 -B 3

Repository: agentic-review-benchmarks/prefect-combined-coderabbit

Length of output: 1607


🏁 Script executed:

# Search for asyncio.Semaphore usage to see if it's directly related to this field
rg "asyncio\.Semaphore|Semaphore" -A 2 -B 2

Repository: agentic-review-benchmarks/prefect-combined-coderabbit

Length of output: 6193


🌐 Web query:

Pydantic v2 Field constraints ge parameter minimum value int

💡 Result:

In Pydantic v2, ge on an int field means “greater than or equal to” the value you provide (i.e., it sets the inclusive minimum). You choose the minimum—there’s no special built-in “minimum int” beyond Python’s int. (docs.pydantic.dev)

Example (model validation)

from pydantic import BaseModel, Field

class Model(BaseModel):
    x: int = Field(ge=0)   # x must be >= 0

Model(x=-1) will fail validation; Model(x=0) and Model(x=5) are valid. (docs.pydantic.dev)

JSON Schema output

Field(ge=0) becomes JSON Schema "minimum": 0 (and le=... becomes "maximum": ...). (zenn.dev)

Alternative: constrained int type

from pydantic import BaseModel, conint

class Model(BaseModel):
    x: conint(ge=0)

conint explicitly documents ge as int | None for integer constraints. (docs.pydantic.dev)

If you have a union, apply ge only to the int branch

from typing import Annotated
from pydantic import BaseModel, Field

class Foo(BaseModel):
    bar: str | list | Annotated[int, Field(ge=2)]

Otherwise, Pydantic may try to apply ge to non-numeric values and raise a TypeError. (stackoverflow.com)

Citations:


Add validation to prevent startup_event_concurrency from causing deadlock.

Without a lower bound, setting this to 0 or negative will create asyncio.Semaphore(0), which blocks all acquisitions and causes observer initialization to hang. Add ge=1 to enforce a minimum value.

Suggested fix
 startup_event_concurrency: int = Field(
     default=5,
+    ge=1,
     description="Maximum number of concurrent API calls when checking for "
     "duplicate events during observer startup. This helps prevent overloading "
     "the API server when there are many existing pods/jobs in the cluster.",
 )
🤖 Prompt for AI Agents
In `@src/integrations/prefect-kubernetes/prefect_kubernetes/settings.py` around
lines 59 - 64, The Field definition for startup_event_concurrency currently
allows zero or negative values which can create asyncio.Semaphore(0) and
deadlock; update the Field on startup_event_concurrency to include a minimum
constraint by adding ge=1 (e.g., Field(..., ge=1, description=...)) so the
Pydantic model enforces a lower bound and prevents passing 0 or negative values
to the observer startup logic.

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.

3 participants