Skip to content

refactor(coordination): centralize distributed coordination in a single service - #43316

Merged
villebro merged 13 commits into
apache:gaq-to-gtffrom
villebro:villebro/coordination-svc
Aug 21, 2026
Merged

refactor(coordination): centralize distributed coordination in a single service#43316
villebro merged 13 commits into
apache:gaq-to-gtffrom
villebro:villebro/coordination-svc

Conversation

@villebro

@villebro villebro commented Aug 18, 2026

Copy link
Copy Markdown
Member

Part of the GAQ→GTF epic — feature branch gaq-to-gtf. This is step 0 of a 7-PR migration of Global Async Queries onto the Global Task Framework. Every step PR targets gaq-to-gtf; the branch merges to master in one go once the epic is complete. An umbrella gaq-to-gtf → master tracker PR will be opened once this PR merges into the branch.

Step Scope Status PR
0 Coordination Service 🔵 In review #43316 (this PR)
1 GTF task dependencies (DAG) via task_dependencies junction table ⚪ Not started
2 Canonical QueryObject serialization ⚪ Not started
3 GTF chart-data tasks + orchestrator (integration point) ⚪ Not started (needs 1+2)
4 AsyncQueryManager → notification layer + flag auto-enable ⚪ Not started (needs 3)
5 Frontend re-request result model ⚪ Not started (needs 3)
6 Remove dead qc-<hash> path ⚪ Not started (after 3–5)

SUMMARY

Introduces superset.coordination.CoordinationService, a single entry point for the Valkey/Redis coordination primitives Superset relies on, and moves the repeated "publish a signal / await a signal with a timeout, else poll" business logic into it so consumers stay thin.

Previously these were wired up ad hoc: the Global Task Framework (GTF) used DISTRIBUTED_COORDINATION_CONFIG for pub/sub + locking, Global Async Queries (GAQ) used a separate GLOBAL_ASYNC_QUERIES_CACHE_BACKEND for its event streams, and the distributed lock and GTF's wait/abort loops each reached into the raw backend directly. This PR consolidates them behind one service to modularize and simplify the architecture, and to pave the way for further cleanup of GAQ.

Public API — method by method

CoordinationService is a stateless, class-method service. It resolves its backend from DISTRIBUTED_COORDINATION_CONFIG on each call — this is the single source of truth for the coordinator's consumers (distributed locks, GTF, and future stream/pub-sub users). It does not consult the deprecated GLOBAL_ASYNC_QUERIES_CACHE_BACKEND; GAQ owns its own backend resolution (see below) and passes it to the primitives via an optional backend= argument.

Backend / availability

  • get_backend() -> RedisCacheBackend | RedisSentinelCacheBackend | None — resolve the shared coordinator backend from DISTRIBUTED_COORDINATION_CONFIG, or None if unconfigured. Escape hatch for the rare caller that needs the native connection (e.g. a long-lived pub/sub subscription loop).
  • is_backend_defined() -> bool — whether the coordinator backend is configured. Best-effort callers branch on this before invoking the backend-only ops below.

Pub/Sub

  • publish(channel, message, backend=None) -> int — fire-and-forget publish; returns the subscriber count. Subscribing is intentionally not wrapped (it needs the native long-lived connection), so subscribers use get_backend() — or, more commonly, the higher-level await/notify methods below.

Key/Value (backend-agnostic flag names, mapped to Redis SET/DEL under the hood)

  • get_value(key, backend=None) -> Any — return the raw (bytes) value at key, or None if absent.
  • set_value(key, value, ttl=None, if_absent=False, if_present=False, backend=None) -> bool | None — store value; ttl sets an expiry (seconds), if_absent = set-only-if-missing (SET NX), if_present = set-only-if-exists (SET XX). Returns True, or None when a condition prevented the write.
  • delete_value(*keys, backend=None) -> int — delete one or more keys; returns the number deleted.

Streams

  • stream_add(stream, data, event_id="*", max_len=None, backend=None) -> str — append an event to a stream; returns the generated event id.
  • stream_range(stream, start="-", end="+", count=None, backend=None) -> list — read a range of events from a stream.

Every raw primitive above accepts an optional backend= so a caller with its own connection — GAQ, during the deprecation window — can run against it instead of the shared coordinator. Omitted (the default), they resolve get_backend() and raise CoordinationBackendUnavailableError if no coordinator is configured.

Await / notify (combine a pub/sub channel with a caller-supplied predicate; the message is only a wake-up nudge, the predicate is the source of truth)

  • wait_for_signal(channel, check, *, timeout=None, poll_interval=1.0) -> T — block until check() returns a non-None value, and return it. check() is evaluated once before any subscription, so an already-satisfied wait returns straight from the predicate without touching the backend. When a backend is defined it then wakes promptly on a message published to channel (re-checking the predicate each tick); when none is defined it polls check every poll_interval. Raises TimeoutError on timeout.
  • listen_for_signal(channel, check, on_signal, *, poll_interval, name=None) -> SignalListener — run a background daemon that invokes on_signal() once check() becomes true (woken by a published message when a backend is defined, else by polling). Returns a handle.

Supporting types

  • SignalListener.stop() — signal the background listener to stop, close its subscription (so a thread parked in get_message wakes immediately), and join.
  • CoordinationBackendUnavailableError — raised by the backend-only ops (publish, *_value, stream_*) when no backend is available, instead of silently no-op'ing. Callers with their own fallback (e.g. DistributedLock's DB lock) gate on is_backend_defined() rather than catching it.

Distributed locking is deliberately not on this service: DistributedLock (superset/distributed_lock/) remains the user-facing lock interface and now draws on this service's backend when one is defined, falling back to a database-backed lock otherwise.

Consumers refactored onto the service

  • DistributedLock acquire/release go through set_value/delete_value (gated on is_backend_defined()) instead of reaching into cache_manager.distributed_coordination._cache; the redundant get_redis_client() helper is removed. Backend selection is unchanged from before this PR (coordinator only, DB fallback).
  • GTF TaskManagerwait_for_completion / listen_for_abort are now thin: they supply a channel + a task-status predicate (TERMINAL_STATES/ABORT_STATES) and delegate to wait_for_signal/listen_for_signal; publish_abort/publish_completion use publish. Removes ~250 lines of duplicated pub/sub-vs-poll branching and the bespoke AbortListener (now SignalListener). Task edge cases (success/failure/abort/timeout) stay in GTF via the predicate over the authoritative Task row. Backend selection is unchanged (coordinator only, DB polling fallback).
  • AsyncQueryManager resolves its own backend once at init_app — the shared coordinator when DISTRIBUTED_COORDINATION_CONFIG is set, otherwise its dedicated (deprecated) GLOBAL_ASYNC_QUERIES_CACHE_BACKEND — and passes it to the service's stream / KV primitives via backend=, so GAQ's traffic stays scoped to the connection it resolved.

Deprecation (non-breaking)

GLOBAL_ASYNC_QUERIES_CACHE_BACKEND is deprecated in favor of DISTRIBUTED_COORDINATION_CONFIG. The two backends stay scoped during the deprecation window: distributed locks and GTF use the coordinator exclusively (falling back to the metadata database when it is unset, unchanged from before), and GAQ uses the coordinator whenever it is set — falling back to the dedicated GLOBAL_ASYNC_QUERIES_CACHE_BACKEND (with a one-time deprecation warning) only when it is not. Configuring DISTRIBUTED_COORDINATION_CONFIG therefore lets a deployment retire the separate GAQ backend rather than maintain two configs. All params are supported identically under the new key. The dual-backend arrangement is removed in Superset 8.0, when GAQ moves onto DISTRIBUTED_COORDINATION_CONFIG.

No functional change to async-query or task behavior; internal consolidation + config deprecation.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — internal refactor, no UI changes.

TESTING INSTRUCTIONS

  • Unit: pytest tests/unit_tests/coordination/test_service.py tests/unit_tests/distributed_lock/distributed_lock_tests.py tests/unit_tests/tasks/test_manager.py tests/unit_tests/async_events/async_query_manager_tests.py
  • With DISTRIBUTED_COORDINATION_CONFIG set (Redis/Valkey): verify GTF abort/completion notifications, sync join-and-wait, distributed locking, and Global Async Queries all work end-to-end (GAQ rides the coordinator).
  • With only the deprecated GLOBAL_ASYNC_QUERIES_CACHE_BACKEND set (no DISTRIBUTED_COORDINATION_CONFIG): verify GAQ still works and a one-time deprecation warning is logged, while distributed locks / GTF use the metadata database.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@github-actions github-actions Bot added the doc Namespace | Anything related to documentation label Aug 18, 2026
@netlify

netlify Bot commented Aug 18, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 947ce35
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a879844e70c0a000881caba
😎 Deploy Preview https://deploy-preview-43316--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@villebro
villebro force-pushed the villebro/coordination-svc branch 4 times, most recently from fb0f815 to d3112e4 Compare August 19, 2026 02:01
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.53846% with 75 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.83%. Comparing base (18fc2c6) to head (2d81d1c).
⚠️ Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
superset/coordination/base.py 66.66% 26 Missing and 8 partials ⚠️
superset/async_events/async_query_manager.py 42.30% 11 Missing and 4 partials ⚠️
superset/coordination/utils.py 38.46% 8 Missing ⚠️
superset/tasks/manager.py 65.00% 3 Missing and 4 partials ⚠️
superset/coordination/types.py 73.68% 2 Missing and 3 partials ⚠️
superset/commands/distributed_lock/acquire.py 40.00% 2 Missing and 1 partial ⚠️
superset/commands/distributed_lock/release.py 50.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master   #43316    +/-   ##
========================================
  Coverage   66.83%   66.83%            
========================================
  Files        2876     2880     +4     
  Lines      164061   164215   +154     
  Branches    37860    37900    +40     
========================================
+ Hits       109657   109761   +104     
- Misses      52230    52265    +35     
- Partials     2174     2189    +15     
Flag Coverage Δ
hive 38.13% <31.79%> (+0.03%) ⬆️
mysql 57.84% <61.53%> (+0.02%) ⬆️
postgres 57.87% <61.53%> (+0.01%) ⬆️
presto 40.07% <31.79%> (+0.03%) ⬆️
python 59.31% <61.53%> (+0.01%) ⬆️
sqlite 57.56% <61.53%> (+0.02%) ⬆️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@villebro
villebro force-pushed the villebro/coordination-svc branch from d3112e4 to 8264ac8 Compare August 19, 2026 15:43
@villebro
villebro force-pushed the villebro/coordination-svc branch 2 times, most recently from 80e7a76 to db35479 Compare August 19, 2026 16:13

@nytai nytai left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pretty straightforward refactor, approve with a minor comment

Comment thread superset/coordination/__init__.py
@bito-code-review

Copy link
Copy Markdown
Contributor

The __init__.py file in superset/coordination/ is a standard Python package initialization file. In this PR, it is used to expose the CoordinationService class, which is a common and accepted practice in Python to simplify imports for users of the package. If you prefer to keep the implementation logic elsewhere, you can move the class definition to a separate file (e.g., superset/coordination/service.py) and re-export it from __init__.py using from .service import CoordinationService.

@villebro
villebro marked this pull request as ready for review August 20, 2026 01:14
@dosubot dosubot Bot added the change:backend Requires changing the backend label Aug 20, 2026
@bito-code-review

bito-code-review Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #99bcd5

Actionable Suggestions - 0
Additional Suggestions - 4
  • superset/commands/distributed_lock/release.py - 1
    • Incomplete exception handling · Line 55-55
      The `except redis.RedisError` block on line 55 won't catch `CoordinationBackendUnavailableError` raised by `CoordinationService.delete_value()` when the backend becomes unavailable between the `is_backend_defined()` guard and `_require_backend()`. Add this exception to the handler.
  • superset/coordination/types.py - 1
    • Test coverage gap on warning path · Line 65-71
      The `stop()` method's warning path (lines 67-71) for threads that fail to terminate is not covered by the existing test at line 216. The test at lines 217-224 mocks `is_alive` returning `[True, False]`, which only exercises the early-exit path where the thread terminates successfully. Add a second test that uses `[True, True]` for `is_alive` side_effect to validate the `logger.warning` call.
  • superset/config.py - 1
    • Incomplete example config block · Line 3251-3260
      The standard-Redis example block (lines 3251-3260) shows only `CACHE_REDIS_HOST`, `CACHE_REDIS_PORT`, `CACHE_REDIS_USER`, `CACHE_REDIS_DB`, and `CACHE_DEFAULT_TIMEOUT`, but the parameter list above (lines 3237-3244) explicitly enumerates a larger set including `CACHE_KEY_PREFIX`, `CACHE_REDIS_SSL`, `CACHE_REDIS_SSL_CERTFILE`, `CACHE_REDIS_SSL_KEYFILE`, `CACHE_REDIS_SSL_CERT_REQS`, `CACHE_REDIS_SSL_CA_CERTS`, `CACHE_REDIS_SOCKET_TIMEOUT`, and `CACHE_REDIS_SOCKET_CONNECT_TIMEOUT`. The Sentinel example (lines 3263+) also lacks these. This leaves operators with an incomplete example for an apparently fully-supported config path.
  • superset/async_events/async_query_manager.py - 1
    • Dead code: unused get_cache_backend function · Line 87-87
      `get_cache_backend` is defined but never invoked after the `__init__` refactor. The function is dead code in the diff.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/async_events/async_query_manager.py - 1
  • tests/unit_tests/tasks/test_manager.py - 1
  • superset/tasks/manager.py - 1
    • CWE-390: Uncaught Exception in publish_completion · Line 152-152
  • tests/integration_tests/tasks/test_sync_join_wait.py - 1
  • superset/coordination/base.py - 1
Review Details
  • Files reviewed - 23 · Commit Range: db35479..2bcd31a
    • docs/admin_docs/configuration/cache.mdx
    • superset/async_events/async_query_manager.py
    • superset/commands/distributed_lock/acquire.py
    • superset/commands/distributed_lock/base.py
    • superset/commands/distributed_lock/release.py
    • superset/config.py
    • superset/coordination/__init__.py
    • superset/coordination/base.py
    • superset/coordination/exceptions.py
    • superset/coordination/types.py
    • superset/coordination/utils.py
    • superset/tasks/context.py
    • superset/tasks/manager.py
    • tests/integration_tests/async_events/api_tests.py
    • tests/integration_tests/tasks/async_queries_tests.py
    • tests/integration_tests/tasks/test_sync_join_wait.py
    • tests/unit_tests/async_events/async_query_manager_tests.py
    • tests/unit_tests/coordination/__init__.py
    • tests/unit_tests/coordination/test_service.py
    • tests/unit_tests/distributed_lock/distributed_lock_tests.py
    • tests/unit_tests/tasks/test_handlers.py
    • tests/unit_tests/tasks/test_manager.py
    • tests/unit_tests/tasks/test_timeout.py
  • Files skipped - 1
    • UPDATING.md - Reason: Filter setting
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread superset/coordination/base.py Outdated
Comment thread superset/coordination/base.py
…ved cache_manager and renamed set_value flags
…AL_ASYNC_QUERIES is enabled

The deprecated GLOBAL_ASYNC_QUERIES_CACHE_BACKEND ships a populated default, so
CoordinationService.get_backend() treated it as a live backend everywhere — making
is_backend_defined() return True even with GAQ off and no Redis, and pushing all
DistributedLock/GTF callers at a nonexistent Redis. Gate the legacy fallback on the
GLOBAL_ASYNC_QUERIES feature flag (the only real signal that GAQ is in use).
Move SignalListener + the CoordinationBackend alias to coordination/types.py and the
close_pubsub helper to coordination/utils.py, mirroring the superset/distributed_lock
layout (types.py + utils.py) and the existing coordination/exceptions.py. __init__
re-exports the public names, so `from superset.coordination import ...` is unchanged.
…nit__ thin

Addresses review feedback: no implementation in __init__.py. CoordinationService now
lives in coordination/base.py (matching the base.py convention used elsewhere, e.g.
commands/distributed_lock/base.py); __init__ only holds the package docstring and
re-exports (CoordinationService, SignalListener, CoordinationBackendUnavailableError),
so `from superset.coordination import ...` is unchanged.
Drop the re-exports and __all__ from superset/coordination/__init__.py so
the package init carries only the docstring, per review feedback that we
don't use the __init__ re-export pattern here. All consumers now import
directly from the submodule that owns the symbol:

  - CoordinationService              -> superset.coordination.base
  - SignalListener                  -> superset.coordination.types
  - CoordinationBackendUnavailableError -> superset.coordination.exceptions

Test patch targets and docstring cross-refs updated to the .base. paths.
…_signal fast path

Addresses review feedback on apache#43316.

get_backend() now resolves DISTRIBUTED_COORDINATION_CONFIG only. Previously it
fell back to the deprecated GLOBAL_ASYNC_QUERIES_CACHE_BACKEND for *all* callers,
so a deployment with GAQ enabled but no DISTRIBUTED_COORDINATION_CONFIG silently
moved distributed locks (DB -> GAQ Redis) and GTF (DB polling -> GAQ Redis pub/sub)
onto the GAQ backend — a rolling-upgrade split-brain for non-GAQ consumers
(OAuth2/thumbnails/reports locks included). Locks and GTF are now byte-for-byte
master behavior (DB when no coordinator is configured).

Global Async Queries keep their own separate backend during the deprecation
window: AsyncQueryManager resolves it (get_cache_backend, preferring the dedicated
GLOBAL_ASYNC_QUERIES_CACHE_BACKEND and otherwise the coordinator) and passes it to
the CoordinationService primitives via a new optional backend= param. The
dual-backend arrangement is deprecated and collapses to
DISTRIBUTED_COORDINATION_CONFIG in 8.0 (config.py + UPDATING.md).

wait_for_signal now runs check() once before opening the pub/sub subscription and
returns immediately if satisfied, so an already-terminal task never requires Redis
to be reachable (and skips an unnecessary round-trip).
@villebro
villebro force-pushed the villebro/coordination-svc branch from 2bcd31a to 947ce35 Compare August 21, 2026 00:13
Comment thread superset/coordination/utils.py
Comment thread superset/async_events/async_query_manager.py Outdated
Comment thread superset/async_events/async_query_manager.py
Comment thread superset/coordination/base.py
Comment thread superset/coordination/base.py
…end only as fallback

Refines the GAQ backend resolution: AsyncQueryManager now uses the shared
coordinator (DISTRIBUTED_COORDINATION_CONFIG) whenever it is configured, and only
falls back to its dedicated (deprecated) GLOBAL_ASYNC_QUERIES_CACHE_BACKEND when the
coordinator is unset — emitting the one-time deprecation warning only in that
fallback case. A deployment that configures DISTRIBUTED_COORDINATION_CONFIG can thus
retire the separate GAQ backend instead of maintaining two configs (also covers the
Helm chart, which renders a GAQ backend block whenever cache is enabled). Non-breaking:
the shipped GLOBAL_ASYNC_QUERIES_CACHE_BACKEND default is unchanged, so enabling GAQ
without a coordinator still works. Docs (config.py, UPDATING.md, base.py) updated.
@bito-code-review

bito-code-review Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #2adb26

Actionable Suggestions - 0
Additional Suggestions - 3
  • superset/coordination/__init__.py - 1
    • Incorrect RST role for function · Line 23-23
      Line 23 calls ``DistributedLock`` a class, but `DistributedLock` is a `@contextmanager`-decorated function at `superset/distributed_lock/__init__.py:29`, not a class. The RST role should be `:func:` to avoid misleading documentation generators and callers.
  • tests/unit_tests/distributed_lock/distributed_lock_tests.py - 1
    • Incorrect exception assertion type · Line 134-142
      The test `test_distributed_lock_redis_already_taken` expects `AcquireDistributedLockFailedException`, but the implementation raises `LockAlreadyHeldException` (a subclass) when the lock is already held. While this passes due to subclass relationship, it misleads future maintainers about the intended contract and weakens test specificity.
  • superset/coordination/exceptions.py - 1
    • Missing N818 noqa on exception class · Line 22-22
      The class name follows the `Error` suffix convention (required by flake8 N818), but the noqa comment used by every other exception class in this codebase (e.g., `AsyncQueryJobException`, `UnsupportedCacheBackendError`) is missing. Add the flag to keep the linter quiet.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • tests/unit_tests/coordination/test_service.py - 1
    • Missing constructor argument causes runtime TypeError · Line 235-235
  • tests/integration_tests/tasks/async_queries_tests.py - 3
Review Details
  • Files reviewed - 23 · Commit Range: 6c8c4b9..5e34de6
    • docs/admin_docs/configuration/cache.mdx
    • superset/async_events/async_query_manager.py
    • superset/commands/distributed_lock/acquire.py
    • superset/commands/distributed_lock/base.py
    • superset/commands/distributed_lock/release.py
    • superset/config.py
    • superset/coordination/__init__.py
    • superset/coordination/base.py
    • superset/coordination/exceptions.py
    • superset/coordination/types.py
    • superset/coordination/utils.py
    • superset/tasks/context.py
    • superset/tasks/manager.py
    • tests/integration_tests/async_events/api_tests.py
    • tests/integration_tests/tasks/async_queries_tests.py
    • tests/integration_tests/tasks/test_sync_join_wait.py
    • tests/unit_tests/async_events/async_query_manager_tests.py
    • tests/unit_tests/coordination/__init__.py
    • tests/unit_tests/coordination/test_service.py
    • tests/unit_tests/distributed_lock/distributed_lock_tests.py
    • tests/unit_tests/tasks/test_handlers.py
    • tests/unit_tests/tasks/test_manager.py
    • tests/unit_tests/tasks/test_timeout.py
  • Files skipped - 1
    • UPDATING.md - Reason: Filter setting
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

close_pubsub now attempts unsubscribe() and close() independently, so a failing
unsubscribe no longer skips close() and leak the underlying Redis/pub-sub connection
(the helper runs from every listener teardown and wait_for_signal finally block).

Also corrects the CoordinationService docstring: the await/notify layer wakes on a
published message when a backend is defined and polls only when none is defined; it
does not poll as an outage fallback once a backend is configured.
The wait_for_signal fast-path fix evaluates check() once before opening the pub/sub
subscription, so the mocked tests that assumed the first check happens after subscribe
needed one extra predicate value: test_wait_for_signal_wakes_via_pubsub_and_cleans_up
(add a None so a pub/sub nudge still occurs) and TaskManager's
test_pubsub_success_subscribes_and_cleans_up (add a pending read so we still subscribe).
The write is skipped only when no coordination backend is configured; when one is
configured a write failure propagates and fails job submission (it is not swallowed).
The prior 'best-effort' wording overstated the guarantee.
…ocstring

is_job_cancelled was the one GAQ method missing the self._gaq_backend is None guard,
so with no dedicated backend it would fall through _require_backend() to the shared
coordinator — the cross-backend resolution GAQ is meant to avoid. Restores the guard
(matching the pre-refactor behavior of returning False). Also corrects the
CoordinationService class docstring to note the primitives accept an explicit backend
rather than always using the shared connection.
@rusackas

Copy link
Copy Markdown
Member

Went through this properly before approving, not just trusting the bot summaries. Good consolidation overall, and every open review thread turned out to already be fixed in code (moved __init__.py's contents to base.py, get_backend() scoped to DISTRIBUTED_COORDINATION_CONFIG only, wait_for_signal checks before subscribing), so I replied and resolved those.

One real blocker though: CI's actually red here, not flaky. test-mysql/test-postgres/test-postgres-required/test-sqlite all fail the same 6 tests in tests/integration_tests/async_events/api_tests.py::TestAsyncEventApi. Cause is in run_test_with_cache_backend: self.login(ADMIN_USERNAME) now runs before async_query_manager_factory.init_app(app), so login()'s request trips Flask's after_request setup-method guard before init_app gets a chance to re-register it. Move init_app(app) (with the _got_first_request reset) back ahead of login() and it should go green. That also explains the scary-looking Codecov -8.90% line — mysql/postgres/sqlite just never uploaded a report because the run failed.

One optional follow-up for later, not blocking: CodeAnt flagged that a mid-wait Redis outage in wait_for_signal propagates instead of falling back to DB polling. Checked it against the pre-refactor code, that's pre-existing (_wait_via_pubsub had the identical gap), so not something this PR needs to fix, but could be worth hardening separately sometime.

The GAQ backend must be resolved under the get_cache_backend patch, but init_app also
re-registers the after_request handler and must run before login(): with login() first,
its request tripped Flask's 'setup method after first request' guard before init_app
re-registered the handler, failing all 6 TestAsyncEventApi cases. Resolve the mock
under the patch during init_app (the backend is captured there), then log in and run.
@bito-code-review

bito-code-review Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #5d3cb5

Actionable Suggestions - 0
Review Details
  • Files reviewed - 6 · Commit Range: 5e34de6..2d81d1c
    • superset/coordination/base.py
    • superset/coordination/utils.py
    • tests/unit_tests/coordination/test_service.py
    • tests/unit_tests/tasks/test_manager.py
    • superset/async_events/async_query_manager.py
    • tests/integration_tests/async_events/api_tests.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@villebro
villebro changed the base branch from master to gaq-to-gtf August 21, 2026 22:46
@villebro
villebro merged commit 78536bb into apache:gaq-to-gtf Aug 21, 2026
77 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend doc Namespace | Anything related to documentation size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants