diff --git a/client/src/stores/entryPointStore.ts b/client/src/stores/entryPointStore.ts index f9f0e7d98c7..2d0ee6c4ab7 100644 --- a/client/src/stores/entryPointStore.ts +++ b/client/src/stores/entryPointStore.ts @@ -66,7 +66,7 @@ export const useEntryPointStore = defineStore("entryPointStore", () => { const configStore = useConfigStore(); const decide = () => { - if (configStore.config?.enable_sse_entry_point_updates) { + if (configStore.config?.enable_sse_updates) { // Baseline fetch + SSE. Reconnect-refetch closes the "user // navigated away and missed events" window. fetchEntryPoints().catch((err) => console.warn("Initial entry-point load failed", err)); diff --git a/client/src/stores/historyStore.test.ts b/client/src/stores/historyStore.test.ts index dab156af848..bf04fe8fc0c 100644 --- a/client/src/stores/historyStore.test.ts +++ b/client/src/stores/historyStore.test.ts @@ -41,7 +41,7 @@ function registerDefaultHandlers({ enableSse }: { enableSse: boolean }) { server.use( http.get("/api/configuration", ({ response }) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - return response(200).json({ enable_sse_history_updates: enableSse } as any); + return response(200).json({ enable_sse_updates: enableSse } as any); }), ); } @@ -73,7 +73,7 @@ describe("historyStore — config-driven SSE vs polling", () => { vi.useRealTimers(); }); - describe("when enable_sse_history_updates is true (SSE scenario)", () => { + describe("when enable_sse_updates is true (SSE scenario)", () => { beforeEach(() => { registerDefaultHandlers({ enableSse: true }); }); @@ -148,7 +148,7 @@ describe("historyStore — config-driven SSE vs polling", () => { }); }); - describe("when enable_sse_history_updates is false (polling scenario)", () => { + describe("when enable_sse_updates is false (polling scenario)", () => { beforeEach(() => { registerDefaultHandlers({ enableSse: false }); }); diff --git a/client/src/stores/historyStore.ts b/client/src/stores/historyStore.ts index a5086771c78..f0f6b73fc4b 100644 --- a/client/src/stores/historyStore.ts +++ b/client/src/stores/historyStore.ts @@ -423,7 +423,7 @@ export const useHistoryStore = defineStore("historyStore", () => { } // Choose between SSE and polling based on the server config flag - // `enable_sse_history_updates`. SSE success at the socket level is not a + // `enable_sse_updates`. SSE success at the socket level is not a // reliable proxy: the `/api/events/stream` endpoint accepts connections // even when the HistoryAuditMonitor is disabled, so relying on the // EventSource `connected` state would silently stop polling without any @@ -443,7 +443,7 @@ export const useHistoryStore = defineStore("historyStore", () => { const configStore = useConfigStore(); const decide = () => { - if (configStore.config?.enable_sse_history_updates) { + if (configStore.config?.enable_sse_updates) { // SSE delivers incremental updates only; the store still needs // a baseline fetch so the history panel isn't empty until the // first change arrives. diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst index e87d6ac0ef4..bff4e8e3e55 100644 --- a/doc/source/admin/galaxy_options.rst +++ b/doc/source/admin/galaxy_options.rst @@ -281,6 +281,23 @@ :Type: int +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``kombu_sqla_transport_cleanup_interval`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:Description: + Time (in seconds) between attempts to delete fully-consumed rows + from the Kombu SQLAlchemy transport tables (``kombu_message``). + Only relevant when ``amqp_internal_connection`` uses a + ``sqlalchemy+*`` scheme (the default with an on-disk + control.sqlite); the SQLAlchemy transport has no built-in TTL, so + without this task the tables grow unbounded. The task no-ops on + non-SQLAlchemy brokers (RabbitMQ/Redis honor per-message + expiration natively). Set to 0 to disable the cleanup task. +:Default: ``900`` +:Type: int + + ~~~~~~~~~~~~~ ``file_path`` ~~~~~~~~~~~~~ @@ -5816,30 +5833,23 @@ :Type: str -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``enable_sse_history_updates`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~ +``enable_sse_updates`` +~~~~~~~~~~~~~~~~~~~~~~ :Description: - Enables real-time history update notifications via Server-Sent - Events (SSE). When enabled, a background monitor watches for - history changes (via PostgreSQL LISTEN/NOTIFY or audit table - polling as a fallback for SQLite) and pushes update signals to - connected browsers, replacing aggressive 3-second polling. -:Default: ``false`` -:Type: bool - - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``enable_sse_entry_point_updates`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:Description: - Enables real-time interactive-tool entry-point update - notifications via Server-Sent Events. When enabled, the client - subscribes to entry_point_update SSE events and refetches the - entry-point list on each event, replacing the 10-second polling - loop. When disabled, polling remains the source of updates. + Enables real-time updates via Server-Sent Events (SSE), replacing + the history (3 s), entry-point (10 s) and notification (30 s) + polling loops with push events delivered over a single + ``/api/events/stream`` connection per browser tab. A background + monitor watches for history changes (via PostgreSQL LISTEN/NOTIFY, + or audit-table polling as a fallback for SQLite); entry-point + changes are dispatched directly from the code paths that mutate + them; in-app notifications and broadcasts are pushed when + ``enable_notification_system`` is also true. When disabled, + polling remains the source of updates for all three. See the admin + guide "Server-Sent Events for real-time updates" for the full + architecture, monitoring guidance and proxy configuration. :Default: ``false`` :Type: bool @@ -5851,9 +5861,9 @@ :Description: The interval in seconds between history audit table polls when using the polling fallback (SQLite or when PostgreSQL - LISTEN/NOTIFY is unavailable). Only used when - enable_sse_history_updates is true. Lower values mean faster - updates but more database queries. Recommended range: 1-5 seconds. + LISTEN/NOTIFY is unavailable). Only used when enable_sse_updates + is true. Lower values mean faster updates but more database + queries. Recommended range: 1-5 seconds. :Default: ``2`` :Type: int @@ -5869,6 +5879,9 @@ finished, etc. The system allows notification scheduling and expiration, and users can opt-out of specific notification categories or channels. + Delivery is push-based via Server-Sent Events when + ``enable_sse_updates`` is also true, and falls back to 30-second + polling against ``/api/notifications/status`` otherwise. Admins can schedule and broadcast notifications that will be visible to all users, including special server-wide announcements such as scheduled maintenance, high load warnings, and event diff --git a/lib/galaxy/app/__init__.py b/lib/galaxy/app/__init__.py index bee410a17b1..02c1b47c423 100644 --- a/lib/galaxy/app/__init__.py +++ b/lib/galaxy/app/__init__.py @@ -1018,7 +1018,7 @@ class UniverseApplication(StructuredApp, GalaxyManagerApplication, InstallationT # daemon wins that election when present, otherwise one webapp picks it # up. start/stop are driven by heartbeat role transitions rather than # postfork, so the monitor cleanly migrates when the leader dies. - if self.config.enable_sse_history_updates: + if self.config.enable_sse_updates: monitor = self._register_singleton(HistoryAuditMonitor) self.database_heartbeat.add_audit_monitor_change_callback(monitor.on_role_change) @@ -1057,7 +1057,7 @@ class UniverseApplication(StructuredApp, GalaxyManagerApplication, InstallationT self.database_heartbeat.shutdown() def _shutdown_history_audit_monitor(self): - if not self.config.enable_sse_history_updates: + if not self.config.enable_sse_updates: return self[HistoryAuditMonitor].shutdown() diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample index 34a98eb03b9..0f08881b5c7 100644 --- a/lib/galaxy/config/sample/galaxy.yml.sample +++ b/lib/galaxy/config/sample/galaxy.yml.sample @@ -512,8 +512,8 @@ galaxy: # history_audit database table. Set to 0 to disable pruning. #history_audit_table_prune_interval: 3600 - # Time (in seconds) between attempts to delete fully-consumed rows from - # the Kombu SQLAlchemy transport tables (``kombu_message``). Only + # Time (in seconds) between attempts to delete fully-consumed rows + # from the Kombu SQLAlchemy transport tables (``kombu_message``). Only # relevant when ``amqp_internal_connection`` uses a ``sqlalchemy+*`` # scheme (the default with an on-disk control.sqlite); the SQLAlchemy # transport has no built-in TTL, so without this task the tables grow @@ -3144,25 +3144,25 @@ galaxy: # Message to display on the export citations tool page #citations_export_message_html: When writing up your analysis, remember to include all references that should be cited in order to completely describe your work. Also, please remember to cite Galaxy. - # Enables real-time history update notifications via Server-Sent - # Events (SSE). When enabled, a background monitor watches for history - # changes (via PostgreSQL LISTEN/NOTIFY or audit table polling as a - # fallback for SQLite) and pushes update signals to connected - # browsers, replacing aggressive 3-second polling. - #enable_sse_history_updates: false - - # Enables real-time interactive-tool entry-point update notifications - # via Server-Sent Events. When enabled, the client subscribes to - # entry_point_update SSE events and refetches the entry-point list on - # each event, replacing the 10-second polling loop. When disabled, - # polling remains the source of updates. - #enable_sse_entry_point_updates: false + # Enables real-time updates via Server-Sent Events (SSE), replacing + # the history (3 s), entry-point (10 s) and notification (30 s) + # polling loops with push events delivered over a single + # ``/api/events/stream`` connection per browser tab. A background + # monitor watches for history changes (via PostgreSQL LISTEN/NOTIFY, + # or audit-table polling as a fallback for SQLite); entry-point + # changes are dispatched directly from the code paths that mutate + # them; in-app notifications and broadcasts are pushed when + # ``enable_notification_system`` is also true. When disabled, polling + # remains the source of updates for all three. See the admin guide + # "Server-Sent Events for real-time updates" for the full + # architecture, monitoring guidance and proxy configuration. + #enable_sse_updates: false # The interval in seconds between history audit table polls when using # the polling fallback (SQLite or when PostgreSQL LISTEN/NOTIFY is - # unavailable). Only used when enable_sse_history_updates is true. - # Lower values mean faster updates but more database queries. - # Recommended range: 1-5 seconds. + # unavailable). Only used when enable_sse_updates is true. Lower + # values mean faster updates but more database queries. Recommended + # range: 1-5 seconds. #history_audit_monitor_poll_interval: 2 # Enables the Notification System integrated in Galaxy. @@ -3171,6 +3171,9 @@ galaxy: # etc. # The system allows notification scheduling and expiration, and users # can opt-out of specific notification categories or channels. + # Delivery is push-based via Server-Sent Events when + # ``enable_sse_updates`` is also true, and falls back to 30-second + # polling against ``/api/notifications/status`` otherwise. # Admins can schedule and broadcast notifications that will be visible # to all users, including special server-wide announcements such as # scheduled maintenance, high load warnings, and event announcements, diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index c12276211af..7b5b21505f8 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -4313,25 +4313,22 @@ mapping: desc: | Message to display on the export citations tool page - enable_sse_history_updates: + enable_sse_updates: type: bool default: false required: false desc: | - Enables real-time history update notifications via Server-Sent Events (SSE). - When enabled, a background monitor watches for history changes (via PostgreSQL - LISTEN/NOTIFY or audit table polling as a fallback for SQLite) and pushes update - signals to connected browsers, replacing aggressive 3-second polling. - - enable_sse_entry_point_updates: - type: bool - default: false - required: false - desc: | - Enables real-time interactive-tool entry-point update notifications via - Server-Sent Events. When enabled, the client subscribes to entry_point_update - SSE events and refetches the entry-point list on each event, replacing the - 10-second polling loop. When disabled, polling remains the source of updates. + Enables real-time updates via Server-Sent Events (SSE), replacing the + history (3 s), entry-point (10 s) and notification (30 s) polling loops + with push events delivered over a single ``/api/events/stream`` + connection per browser tab. A background monitor watches for history + changes (via PostgreSQL LISTEN/NOTIFY, or audit-table polling as a + fallback for SQLite); entry-point changes are dispatched directly from + the code paths that mutate them; in-app notifications and broadcasts + are pushed when ``enable_notification_system`` is also true. + When disabled, polling remains the source of updates for all three. + See the admin guide "Server-Sent Events for real-time updates" for the + full architecture, monitoring guidance and proxy configuration. history_audit_monitor_poll_interval: type: int @@ -4340,7 +4337,7 @@ mapping: desc: | The interval in seconds between history audit table polls when using the polling fallback (SQLite or when PostgreSQL LISTEN/NOTIFY is unavailable). Only used when - enable_sse_history_updates is true. Lower values mean faster updates but more + enable_sse_updates is true. Lower values mean faster updates but more database queries. Recommended range: 1-5 seconds. enable_notification_system: @@ -4354,6 +4351,10 @@ mapping: The system allows notification scheduling and expiration, and users can opt-out of specific notification categories or channels. + Delivery is push-based via Server-Sent Events when ``enable_sse_updates`` + is also true, and falls back to 30-second polling against + ``/api/notifications/status`` otherwise. + Admins can schedule and broadcast notifications that will be visible to all users, including special server-wide announcements such as scheduled maintenance, high load warnings, and event announcements, to name a few examples. enable_mcp_server: diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py index 7637520c728..b85d474c02b 100644 --- a/lib/galaxy/managers/configuration.py +++ b/lib/galaxy/managers/configuration.py @@ -229,8 +229,7 @@ class ConfigSerializer(base.ModelSerializer): "tool_training_recommendations_link": _use_config, "tool_training_recommendations_api_url": _use_config, "enable_notification_system": _use_config, - "enable_sse_history_updates": _use_config, - "enable_sse_entry_point_updates": _use_config, + "enable_sse_updates": _use_config, "instance_resource_url": _use_config, "instance_access_url": _use_config, "organization_name": _use_config, diff --git a/lib/galaxy/managers/history_audit_monitor.py b/lib/galaxy/managers/history_audit_monitor.py index 9f2b33a641a..c98b8e59cc1 100644 --- a/lib/galaxy/managers/history_audit_monitor.py +++ b/lib/galaxy/managers/history_audit_monitor.py @@ -4,7 +4,7 @@ Detects history changes via PostgreSQL LISTEN/NOTIFY (instant) or by polling the history_audit table (SQLite fallback). Dispatches SSE events to connected users via Kombu control tasks. -Only active when ``enable_sse_history_updates`` is True in the Galaxy config. +Only active when ``enable_sse_updates`` is True in the Galaxy config. """ import logging diff --git a/lib/galaxy/sse_monitor/__main__.py b/lib/galaxy/sse_monitor/__main__.py index 8d9de0eb34d..9b1098de9fd 100644 --- a/lib/galaxy/sse_monitor/__main__.py +++ b/lib/galaxy/sse_monitor/__main__.py @@ -56,13 +56,13 @@ def main() -> int: heartbeat = DatabaseHeartbeat(application_stack=app.application_stack) monitor = None - if app.config.enable_sse_history_updates: + if app.config.enable_sse_updates: from galaxy.managers.history_audit_monitor import HistoryAuditMonitor monitor = app[HistoryAuditMonitor] heartbeat.add_audit_monitor_change_callback(monitor.on_role_change) else: - log.warning("enable_sse_history_updates is False — galaxy-sse-monitor will idle with no producers") + log.warning("enable_sse_updates is False — galaxy-sse-monitor will idle with no producers") heartbeat.start() diff --git a/test/integration/test_history_sse.py b/test/integration/test_history_sse.py index b3bb3341c7d..f60bfef1b15 100644 --- a/test/integration/test_history_sse.py +++ b/test/integration/test_history_sse.py @@ -19,7 +19,7 @@ class TestHistorySSEIntegration(IntegrationTestCase): def handle_galaxy_config_kwds(cls, config): super().handle_galaxy_config_kwds(config) config["enable_celery_tasks"] = False - config["enable_sse_history_updates"] = True + config["enable_sse_updates"] = True def setUp(self): super().setUp() diff --git a/test/integration_selenium/test_entry_point_sse.py b/test/integration_selenium/test_entry_point_sse.py index 102a61ad837..0011a7d12df 100644 --- a/test/integration_selenium/test_entry_point_sse.py +++ b/test/integration_selenium/test_entry_point_sse.py @@ -36,10 +36,10 @@ class TestEntryPointSSESeleniumIntegration(SeleniumIntegrationTestCase): config["enable_celery_tasks"] = False # App.vue only calls entryPointStore.startWatchingEntryPoints() when # interactivetools_enable is True, and the store only opens an SSE - # connection when enable_sse_entry_point_updates is True. Without both, + # connection when enable_sse_updates is True. Without both, # __galaxy_sse_connected never becomes true and the gate below times out. config["interactivetools_enable"] = True - config["enable_sse_entry_point_updates"] = True + config["enable_sse_updates"] = True def _wait_for_sse_connected(self) -> None: """Block until the frontend confirms the SSE pipeline is live."""