Skip to content

fix: resolve SedonaDB thread pool leak on sql timeout - #1663

Merged
giswqs merged 5 commits into
opengeos:mainfrom
RohithPariki:fix/sedonadb-timeout-leak
Aug 3, 2026
Merged

fix: resolve SedonaDB thread pool leak on sql timeout#1663
giswqs merged 5 commits into
opengeos:mainfrom
RohithPariki:fix/sedonadb-timeout-leak

Conversation

@RohithPariki

@RohithPariki RohithPariki commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Description

This PR fixes a severe concurrency race condition in sedona_ops.py where long-running sql queries could cause a rust panic and hardcrash the python server worker.

The Bug:
When a spatial sql query exceeds the _STATEMENT_TIMEOUT_MS limit, the ThreadPoolExecutor raises TimeoutError. Previously , the main thread would catch this and instantly force close the SedonaDB database connection. However, the background rust thread (DataFusion/Sedona) was still executing the query. dropping the connection context out from under the actively running native thread caused memory leaks and fatal panics that crashed the entire Python worker.

The Fix:

  • when a timeout occurs, pending futures are cancelled when possible.
  • for already-running queries (which Python cannot natively preempt), connection teardown is deferred until the worker completes.
  • a future.add_done_callback is attached to the running thread. The database connection is now kept alive just long enough for the runaway Rust thread to finish its computation safely behind the scenes, after which it cleans itself up.
  • added strict type hints to the new teardown logic to satisfy ruff (ANN202/ARG001).

Testing

  • added test_sql_timeout_graceful_shutdown to explicitly simulate a slow query that breaches the timeout. It asserts that SqlTimeout is raised properly, and strictly verifies that connection.close() is not called instantly, but is deferred until the background thread completes.
  • ran the full backend test suite to ensure no regressions in standard sql execution paths.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of SQL queries that exceed their timeout, allowing background operations to finish safely before connections close.
    • Prevented premature connection closure during timed-out queries, improving reliability and reducing potential failures.
    • Added clearer reporting when connection cleanup encounters an issue.
  • Tests

    • Added coverage to verify graceful timeout behavior and proper connection cleanup after background operations complete.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

run_sql now keeps the SedonaDB connection open while a timed-out query continues. Cleanup closes the connection after completion and logs close failures. Tests verify deferred cleanup.

Changes

Sedona SQL cleanup

Layer / File(s) Summary
Timeout handling and deferred cleanup
backend/geolibre_server/geolibre_server/sedona_ops.py, backend/geolibre_server/tests/test_sedona_ops.py
run_sql tracks the query future, defers connection closure while execution continues, and logs close failures. Tests verify SqlTimeout, deferred closure, and final cleanup.

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

Possibly related PRs

Suggested reviewers: ayush7614

Poem

A rabbit watched the query run,
The timeout came, but closure did not.
The connection waited while work stayed true,
Then closed when the query was through.
Logs marked any cleanup loop. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: fixing a SedonaDB thread pool leak during SQL timeouts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 3, 2026

Copy link
Copy Markdown
Contributor

🔍 Cloudflare PR preview

Item Value
Site https://d6f46505.geolibre-preview.pages.dev
Demo app https://d6f46505.geolibre-preview.pages.dev/demo/
Commit abc4b90

@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/sedona_ops.py`:
- Around line 228-231: Update the cleanup exception handler around close() in
the connection path to log a warning containing the caught exception details
while keeping cleanup non-throwing. Use the existing logger, if available, and
ensure the warning does not include SQL text or query contents.
- Line 225: Replace the ignored variadic argument annotations with object in
_close_connection and _slow_sql, including both *args and **kwargs where
present, while keeping the ignored arguments visible. Update
backend/geolibre_server/tests/test_sedona_ops.py at lines 33-33 similarly; no
other behavior changes are needed.
🪄 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: d2353a13-4fdd-4641-b2bc-bcb8ea4f55f9

📥 Commits

Reviewing files that changed from the base of the PR and between da72fe7 and d05cc22.

📒 Files selected for processing (2)
  • backend/geolibre_server/geolibre_server/sedona_ops.py
  • backend/geolibre_server/tests/test_sedona_ops.py

Comment thread backend/geolibre_server/geolibre_server/sedona_ops.py Outdated
Comment thread backend/geolibre_server/geolibre_server/sedona_ops.py Outdated
@giswqs

giswqs commented Aug 3, 2026

Copy link
Copy Markdown
Member

/claude-review

Comment thread backend/geolibre_server/geolibre_server/sedona_ops.py Outdated
Comment thread backend/geolibre_server/tests/test_sedona_ops.py Outdated
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • No correctness bugs found in the core fix. Traced the future.done() check in the deferred-close path against concurrent.futures' documented semantics (add_done_callback fires immediately if the future is already complete when attached), and the apparent check-then-act race between the timeout branch and the final finally block is actually safe — confidence: high.

Security

  • None found; no new user input handling, injection surface, or secret exposure in this diff.

Performance

  • future.cancel() (sedona_ops.py:175) is effectively dead code for this pool shape (max_workers=1, single submitted task) — the task will already be running by the time a timeout fires, so cancel() will almost always return False with no effect. Harmless but misleading relative to the PR description's claim. Confidence: medium (see inline comment).
  • Deferred connection teardown means a pathological stream of timed-out queries could accumulate multiple live background threads/connections simultaneously until each naturally completes. This is an inherent, acknowledged tradeoff of the fix (better than crashing) rather than a regression — confidence: low, flagged for awareness only.

Quality

  • New test test_sql_timeout_graceful_shutdown synchronizes via fixed time.sleep() calls with a tight (~0.2s) margin between when the background thread finishes and when the close assertion runs, which risks flakiness on loaded CI runners. Confidence: medium (see inline comment).
  • Minor naming nit: the fixture mock_sedona_db actually yields the connection mock (sedona_db.connect()'s return value), not the sedona.db module — mildly confusing given the module itself is also mocked inside the fixture. Confidence: low.

CLAUDE.md

  • No applicable guidelines are implicated by this change (no dependency/lockfile changes, no catalog/menu/CSP/i18n touch points).

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🔍 GitHub Pages PR preview

Item Value
Site Deploy failed. See the job log.
Demo app Unavailable
Commit 3c9fb67

- Drop the no-op `future.cancel()` on timeout. The pool has a single worker
  and a single task, so the query is always already running by then and
  `cancel()` would return False. Replaced with a comment explaining why the
  statement runs to completion and cleanup is deferred instead.
- Log a warning (without the SQL text) when `connection.close()` fails, so a
  close that strands Rust-backed resources is not silently swallowed.
- Annotate the ignored variadics on `_close_connection` / `_slow_sql` as
  `object` rather than `Any` — nothing meaningful flows through them.
- Poll for the deferred close in `test_sql_timeout_graceful_shutdown` instead
  of a fixed 0.4s sleep, so scheduling jitter on a loaded CI host cannot make
  the assertion flaky.

@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/geolibre_server/sedona_ops.py (1)

243-246: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound the lifetime of timed-out Sedona SQL work.

runner.result() times out, but the submitted _execute() task continues to run and the connection remains open. This call is exposed from POST /sql/run, so repeated long-running queries can accumulate live threads and Rust-backed connection resources. Enforce a global limit on in-flight timed-out calls, add backpressure, or bound process/subprocess lifetime in the sidecar.

🤖 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/geolibre_server/sedona_ops.py` around lines 243 -
246, Update the timeout handling around _execute and _close_connection so a
timed-out Sedona SQL task cannot accumulate indefinitely: enforce a global bound
on in-flight timed-out calls with backpressure, or otherwise bound the
worker/sidecar lifetime and ensure the connection is released when that bound is
reached. Preserve normal completion cleanup while making repeated POST /sql/run
timeouts consume only the configured finite capacity.
🤖 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/geolibre_server/sedona_ops.py`:
- Around line 243-246: Update the timeout handling around _execute and
_close_connection so a timed-out Sedona SQL task cannot accumulate indefinitely:
enforce a global bound on in-flight timed-out calls with backpressure, or
otherwise bound the worker/sidecar lifetime and ensure the connection is
released when that bound is reached. Preserve normal completion cleanup while
making repeated POST /sql/run timeouts consume only the configured finite
capacity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ac784d38-1fe9-4917-9c2d-33b8588952d2

📥 Commits

Reviewing files that changed from the base of the PR and between d05cc22 and 3c9fb67.

📒 Files selected for processing (2)
  • backend/geolibre_server/geolibre_server/sedona_ops.py
  • backend/geolibre_server/tests/test_sedona_ops.py

@giswqs
giswqs merged commit b793919 into opengeos:main Aug 3, 2026
12 of 13 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