Skip to content

fix(sidecar): cap concurrent ML segmentation proxy requests - #1631

Merged
giswqs merged 4 commits into
opengeos:mainfrom
Ayush7614:harden/ml-segment-concurrency
Aug 3, 2026
Merged

fix(sidecar): cap concurrent ML segmentation proxy requests#1631
giswqs merged 4 commits into
opengeos:mainfrom
Ayush7614:harden/ml-segment-concurrency

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Cap in-flight /ml/segment/* proxy requests (default 4) with HTTP 429 when the slot is full, matching conversion/Whitebox job caps.
  • Refuse oversized request bodies (100 MiB) with HTTP 413.
  • Always release the slot in finally.

Test plan

  • uv run --project backend/geolibre_server --extra test pytest backend/geolibre_server/tests/test_ml.py -q
  • Fire a normal segment request and confirm it still forwards
  • Hold the in-flight cap and confirm a further request returns 429

Summary by CodeRabbit

  • Bug Fixes
    • Added safeguards for segmentation requests by limiting uploads to 100 MiB.
    • Limited simultaneous segmentation requests to four.
    • Oversized requests now return HTTP 413, while requests exceeding the concurrency limit return HTTP 429.
    • Improved request-slot cleanup to ensure capacity is restored after completed or failed requests.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The segmentation proxy rejects uploads over 100 MiB and requests above four concurrent operations. It returns HTTP 413 or 429 as applicable and releases in-flight slots after processing. Tests cover both limits and cleanup.

Changes

Segmentation proxy safeguards

Layer / File(s) Summary
Segmentation limits and enforcement
backend/geolibre_server/geolibre_server/app/ml.py
The proxy defines a 100 MiB upload limit and a four-request concurrency cap. It rejects oversized requests with HTTP 413 and excess concurrent requests with HTTP 429.
Limit validation and slot cleanup
backend/geolibre_server/tests/test_ml.py
Tests cover declared-size and streamed-body rejection, concurrency rejection, upstream failure mapping, and slot release after successful and failed requests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ForwardSegment
  participant SegmentationBackend
  Client->>ForwardSegment: Submit segmentation request
  ForwardSegment->>ForwardSegment: Validate size and in-flight count
  ForwardSegment->>SegmentationBackend: Stream request body
  SegmentationBackend-->>ForwardSegment: Return response or upstream error
  ForwardSegment-->>Client: Return response, 413, 429, or 502
Loading

Possibly related PRs

  • opengeos/GeoLibre#1574: Both PRs add concurrency caps, HTTP 429 handling, and cleanup safeguards for backend processing requests.

Suggested reviewers: giswqs

Poem

A rabbit guards the gateway bright,
Four requests pass within the limit.
Large streams receive four-one-three,
Busy paths receive four-two-nine.
Each released slot is counted true. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: limiting concurrent ML segmentation proxy requests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🔍 Cloudflare PR preview

Item Value
Site https://9bad0735.geolibre-preview.pages.dev
Demo app https://9bad0735.geolibre-preview.pages.dev/demo/
Commit 7c3b12d

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 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 `@backend/geolibre_server/geolibre_server/app/ml.py`:
- Around line 77-80: Update _body_iter() to count streamed request bytes and
raise HTTP 413 once the cumulative body exceeds _MAX_SEGMENT_BODY_BYTES,
including requests without Content-Length while preserving normal streaming
below the limit. Add a regression test using a headerless request stream and a
small monkeypatched limit to verify oversized chunked bodies are rejected.

In `@backend/geolibre_server/tests/test_ml.py`:
- Around line 259-278: Update test_segment_releases_slot_after_success to
configure _FakeHttpx.post() to raise its configured HTTPError, then assert the
request returns HTTP 502 and ml._segment_in_flight is 0, covering cleanup after
an upstream failure rather than successful completion.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fbab1801-71df-4807-b5bf-8363906af400

📥 Commits

Reviewing files that changed from the base of the PR and between 02322e8 and a501af6.

📒 Files selected for processing (2)
  • backend/geolibre_server/geolibre_server/app/ml.py
  • backend/geolibre_server/tests/test_ml.py

Comment thread backend/geolibre_server/geolibre_server/app/ml.py Outdated
Comment thread backend/geolibre_server/tests/test_ml.py
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🔍 GitHub Pages PR preview

Item Value
Site Deploy failed. See the job log.
Demo app Unavailable
Commit 7c3b12d

@giswqs

giswqs commented Aug 1, 2026

Copy link
Copy Markdown
Member

/claude-review

Comment thread backend/geolibre_server/geolibre_server/app/ml.py
Comment thread backend/geolibre_server/geolibre_server/app/ml.py
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • None found. The slot increment/decrement is correctly paired via try/finally, the check-and-increment under _segment_lock is atomic, and the 413 size check runs before a slot is consumed so oversized requests don't waste a concurrency slot. High confidence.

Security

  • The 100 MiB body cap is enforced only via the Content-Length header; a client using chunked transfer-encoding (no header) or a malformed header value bypasses it entirely, since streamed bytes are never counted. Medium confidence — flagged inline at ml.py:400-409.

Performance

  • The in-flight slot is held for the entire proxy lifetime, including up to 60s of on-demand backend startup and up to 1800s of inference time. Four hung/slow requests can starve all further segmentation calls with 429s for up to ~31 minutes. Likely intentional given the cap mirrors existing conversion/Whitebox patterns, but worth confirming. Low confidence — flagged inline at ml.py:419-444.

Quality

  • Good test coverage of the new behavior (cap-reached 429, slot release on success, oversized-body 413), consistent with the module's existing test style. No issues found.

CLAUDE.md

  • No applicable guidance was violated; this change doesn't touch any of the mirrored-constant, i18n, or Whitebox-catalog areas called out in the project instructions.

Count bytes in _body_iter() and raise 413 when cumulative size exceeds
the cap, so chunked-transfer uploads without Content-Length are also
bounded.  Add test for upstream failure slot cleanup (502 path) and
test for oversized chunked stream rejection.

@coderabbitai coderabbitai Bot left a comment

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.

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 `@backend/geolibre_server/tests/test_ml.py`:
- Around line 358-363: Remove the ineffective TestClient.post assertion from
test_segment_rejects_oversized_chunked_stream, since it does not invoke
_FakeHttpx.request.stream() or exercise the streamed-body limit. Keep the test
focused on the custom _FakeHttpx streaming path that validates the 413 response.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e914e472-b23c-40f2-b25a-b006b8128fda

📥 Commits

Reviewing files that changed from the base of the PR and between a501af6 and d70a7a4.

📒 Files selected for processing (2)
  • backend/geolibre_server/geolibre_server/app/ml.py
  • backend/geolibre_server/tests/test_ml.py

Comment thread backend/geolibre_server/tests/test_ml.py
…limit

Use a generator body (no Content-Length) so the oversized-chunked-stream
test exercises _body_iter's streaming byte-count check instead of the
Content-Length header check. Narrow _FakeHttpx.HTTPError so it does not
swallow the FastAPI HTTPException(413) raised inside the generator.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 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 `@backend/geolibre_server/tests/test_ml.py`:
- Around line 361-362: Add the project-standard return annotation to the private
generator helper _oversized_body, using the annotation convention already used
for generator helpers in the surrounding test module, while preserving its
existing yielded bytes and behavior.
- Around line 65-66: Annotate the shared calls attribute on _FakeHttpx as a
ClassVar while preserving its existing list initialization and class-level
sharing behavior.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 48682917-f46e-45d8-8b0a-9ea2d8278985

📥 Commits

Reviewing files that changed from the base of the PR and between d70a7a4 and fc84cf2.

📒 Files selected for processing (1)
  • backend/geolibre_server/tests/test_ml.py

Comment thread backend/geolibre_server/tests/test_ml.py Outdated
Comment thread backend/geolibre_server/tests/test_ml.py Outdated
@Ayush7614 Ayush7614 closed this Aug 1, 2026
@Ayush7614
Ayush7614 force-pushed the harden/ml-segment-concurrency branch from fc84cf2 to 02322e8 Compare August 1, 2026 23:18
@Ayush7614 Ayush7614 reopened this Aug 3, 2026
- Update the stale `_MAX_SEGMENT_BODY_BYTES` comment. It still claimed the cap
  was unenforced for chunked uploads, which stopped being true in d70a7a4 when
  `_body_iter` started counting cumulative bytes. Documented both enforcement
  points (Content-Length up front, streamed bytes as the real backstop).
- Document that a concurrency slot is deliberately held for the whole proxied
  call — launch plus inference — and why that differs from the conversion and
  Whitebox caps, which enqueue a background job and return immediately.
- Annotate the shared `calls` buffers on `_FakeHttpx` / `_ErrorHttpx` as
  `ClassVar[list]` so the intentional class-level sharing is explicit.
- Add the missing `-> Iterator[bytes]` return annotation to `_oversized_body`.

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
backend/geolibre_server/tests/test_ml.py (1)

294-312: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use _FakeHTTPError in _ErrorHttpx.

HTTPError = Exception makes every exception from post() match the proxy's HTTPX-error handler. The test can pass when post() raises an unrelated ValueError or TypeError, so it does not isolate upstream failure mapping. (raw.githubusercontent.com)

Proposed test fix
 class _ErrorHttpx:
     calls: ClassVar[list] = []
-    HTTPError = Exception
+    HTTPError = _FakeHTTPError
...
-                raise Exception("backend down")
+                raise _FakeHTTPError("backend down")
🤖 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 `@backend/geolibre_server/tests/test_ml.py` around lines 294 - 312, Update the
test double _ErrorHttpx to define and use the dedicated _FakeHTTPError for
HTTPError instead of aliasing Exception. Make AsyncClient.post raise
_FakeHTTPError for the simulated backend failure, so only the intended HTTPX
error path is exercised.
🤖 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.

Outside diff comments:
In `@backend/geolibre_server/tests/test_ml.py`:
- Around line 294-312: Update the test double _ErrorHttpx to define and use the
dedicated _FakeHTTPError for HTTPError instead of aliasing Exception. Make
AsyncClient.post raise _FakeHTTPError for the simulated backend failure, so only
the intended HTTPX error path is exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b19a0ade-780d-4b1e-b21a-1dbf06cd25bf

📥 Commits

Reviewing files that changed from the base of the PR and between c728b7f and 7c3b12d.

📒 Files selected for processing (2)
  • backend/geolibre_server/geolibre_server/app/ml.py
  • backend/geolibre_server/tests/test_ml.py

@giswqs
giswqs merged commit 2a9d98e into opengeos:main Aug 3, 2026
38 of 39 checks passed
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