You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by ad...@apache.org on 2026/06/04 17:29:23 UTC
(polaris) branch main updated: Execute event listeners on a dedicated thread pool (#4616)
This is an automated email from the ASF dual-hosted git repository.
adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new ad6aec1b64 Execute event listeners on a dedicated thread pool (#4616)
ad6aec1b64 is described below
commit ad6aec1b649fb89443af06ef3aa9c27137af04b4
Author: Alexandre Dutra <ad...@apache.org>
AuthorDate: Thu Jun 4 19:29:17 2026 +0200
Execute event listeners on a dedicated thread pool (#4616)
This change introduces a new, separate thread pool for event listener execution; listeners now execute always on this executor's threads, instead of on the Vert.x event bus threads.
Since event listeners are often blocking, because they execute I/O, running a blocking listener on the event bus thread should be avoided.
---
CHANGELOG.md | 4 ++-
.../polaris/service/config/ServiceProducers.java | 29 ++++++++++++++++++++++
.../events/PolarisEventListenerConfiguration.java | 18 ++++++++++++++
.../service/events/PolarisEventListeners.java | 24 ++++++++++++++++--
.../service/events/PolarisEventListenersTest.java | 3 +++
.../smallrye-polaris_event_listener.md | 2 ++
6 files changed, 77 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 904ad5dcf3..17c77b54b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,9 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
### Highlights
### Upgrade notes
+- Event listeners are now executed on a dedicated executor. **This executor does not propagate the original request's CDI context**; listeners that were improperly relying on that should instead manage their own CDI request scope from now on. Furthermore, two new configuration options were introduced to configure the executor:
+ - `polaris.event-listener.executor.pool-size` configures the thread pool size.
+ - `polaris.event-listener.executor.queue-size` configures the queue size for pending events when all threads are busy.
### Breaking changes
- The `MaintenanceService.performMaintenance()` signature now requires an explicit `OptionalLong overrideRunId` argument to supersede the latest unfinished maintenance run.
@@ -46,7 +49,6 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
- Added documentation for BigQuery Metastore Catalog federation. Build with `-PNonRESTCatalogs=BIGQUERY` to include the BigQueryMetastoreCatalog federation extension. See `site/content/in-dev/unreleased/federation/bigquery-metastore-federation.md`.
- Support for view registration has been added.
- Python client: added support for Python 3.14
-
- Added support for `register table` overwrite semantics in the Iceberg REST catalog flow (`overwrite=true`) for internal Polaris catalogs. With overwrite enabled, existing table pointers can be updated to a new metadata location while preserving default behavior for `overwrite=false`.
- Added `REGISTER_TABLE_OVERWRITE` authorization operation mapped to `TABLE_FULL_METADATA` for deterministic overwrite authorization.
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
index d9b97f07f6..cec98cf4dc 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
@@ -32,6 +32,8 @@ import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;
import java.time.Clock;
import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Collectors;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
@@ -74,6 +76,7 @@ import org.apache.polaris.service.catalog.io.FileIOFactory;
import org.apache.polaris.service.context.RealmContextConfiguration;
import org.apache.polaris.service.context.RealmContextResolver;
import org.apache.polaris.service.credentials.PolarisCredentialManagerConfiguration;
+import org.apache.polaris.service.events.PolarisEventListenerConfiguration;
import org.apache.polaris.service.persistence.PersistenceConfiguration;
import org.apache.polaris.service.ratelimiter.RateLimiter;
import org.apache.polaris.service.ratelimiter.RateLimiterFilterConfiguration;
@@ -459,4 +462,30 @@ public class ServiceProducers {
MetricsReportingConfiguration config, @Any Instance<PolarisMetricsReporter> reporters) {
return reporters.select(Identifier.Literal.of(config.type())).get();
}
+
+ @Produces
+ @Singleton
+ @Identifier("event-listener-executor")
+ public Executor eventListenerExecutor(PolarisEventListenerConfiguration config) {
+ if (config.types().isEmpty()) {
+ return runnable -> {
+ throw new RejectedExecutionException(
+ "Event listener executor is not available because no event listeners are configured");
+ };
+ }
+ return SmallRyeManagedExecutor.builder()
+ .injectionPointName("event-listener-executor")
+ .propagated(ThreadContext.ALL_REMAINING)
+ .cleared(ThreadContext.CDI)
+ .maxAsync(config.executor().poolSize())
+ .maxQueued(config.executor().queueSize())
+ .build();
+ }
+
+ public void closeEventListenerExecutor(
+ @Disposes @Identifier("event-listener-executor") Executor executor) {
+ if (executor instanceof ManagedExecutor managedExecutor) {
+ managedExecutor.close();
+ }
+ }
}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListenerConfiguration.java b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListenerConfiguration.java
index 1924afb2bb..6ad16d7171 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListenerConfiguration.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListenerConfiguration.java
@@ -20,6 +20,7 @@ package org.apache.polaris.service.events;
import io.quarkus.runtime.annotations.StaticInitSafe;
import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
import io.smallrye.config.WithParentName;
import java.util.Map;
import java.util.Optional;
@@ -67,4 +68,21 @@ public interface PolarisEventListenerConfiguration {
*/
Optional<Set<PolarisEventType.Category>> enabledEventCategories();
}
+
+ /** Configuration for the thread pool running event listeners. */
+ Executor executor();
+
+ interface Executor {
+
+ /**
+ * The thread pool size. The default is -1, which is interpreted as the number of available
+ * cores.
+ */
+ @WithDefault("-1")
+ int poolSize();
+
+ /** The queue size. The default is -1, which is interpreted as unbounded. */
+ @WithDefault("-1")
+ int queueSize();
+ }
}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListeners.java b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListeners.java
index b12cebc27e..870fd8ff6b 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListeners.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventListeners.java
@@ -33,6 +33,7 @@ import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import java.util.EnumSet;
import java.util.HashSet;
+import java.util.concurrent.Executor;
import org.apache.polaris.service.events.listeners.PolarisEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,6 +46,10 @@ public class PolarisEventListeners {
@Inject @Any Instance<PolarisEventListener> eventListeners;
@Inject PolarisEventListenerConfiguration configuration;
+ @Inject
+ @Identifier("event-listener-executor")
+ Executor executor;
+
private final EnumSet<PolarisEventType> enabledEventTypes = EnumSet.allOf(PolarisEventType.class);
public void onStartup(@Observes StartupEvent event) {
@@ -81,7 +86,23 @@ public class PolarisEventListeners {
PolarisEvent event, String listenerName, PolarisEventListener listener) {
LOGGER.debug("Delivering {} event to listener '{}' ({})", event.type(), listenerName, listener);
try {
- listener.onEvent(event);
+ executor.execute(
+ () -> {
+ LOGGER.debug(
+ "Delivering {} event to listener '{}' ({})", event.type(), listenerName, listener);
+ try {
+ listener.onEvent(event);
+ } catch (Exception e) {
+ LOGGER.error(
+ "Error while delivering {} event to listener '{}' ({})",
+ event.type(),
+ listenerName,
+ listener,
+ e);
+ }
+ LOGGER.debug(
+ "Delivered {} event to listener '{}' ({})", event.type(), listenerName, listener);
+ });
} catch (Exception e) {
LOGGER.error(
"Error while delivering {} event to listener '{}' ({})",
@@ -90,7 +111,6 @@ public class PolarisEventListeners {
listener,
e);
}
- LOGGER.debug("Delivered {} event to listener '{}' ({})", event.type(), listenerName, listener);
}
public boolean hasListeners(PolarisEventType polarisEventType) {
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventListenersTest.java b/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventListenersTest.java
index 93fd1346b6..39553e9f8b 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventListenersTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/events/PolarisEventListenersTest.java
@@ -19,6 +19,7 @@
package org.apache.polaris.service.events;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -28,6 +29,7 @@ import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.TestProfile;
import io.smallrye.common.annotation.Identifier;
+import io.vertx.core.Context;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.HashSet;
@@ -61,6 +63,7 @@ public class PolarisEventListenersTest {
@Override
public void onEvent(PolarisEvent event) {
+ assertThat(Context.isOnEventLoopThread()).isFalse();
if (predicate.test(event)) {
expectedEvents.add(event);
} else {
diff --git a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_event_listener.md b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_event_listener.md
index 4d0a544550..c19a7088dc 100644
--- a/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_event_listener.md
+++ b/site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_event_listener.md
@@ -29,3 +29,5 @@ build:
| `polaris.event-listener.types` | | `list of string` | Comma separated list of event listeners, each item must be a registered (`PolarisEventListener`) identifier. |
| `polaris.event-listener.`_`<name>`_`.enabled-event-types` | | <span title="BEFORE_CREATE_CATALOG, AFTER_CREATE_CATALOG, BEFORE_DELETE_CATALOG, AFTER_DELETE_CATALOG, BEFORE_GET_CATALOG, AFTER_GET_CATALOG, BEFORE_UPDATE_CATALOG, AFTER_UPDATE_CATALOG, BEFORE_LIST_CATALOGS, AFTER_LIST_CATALOGS, BEFORE_CREATE_CATALOG_ROLE, AFTER_CREATE_CATALOG_ROLE, BEFORE_DELETE_CATALOG_ROLE, AFTER_DELETE_CATALOG_ROLE, BEFORE_GET_CATALOG_ROLE, AFTER_GET_CATALOG_ROLE, BEFORE_UPDATE_CATALOG_ROLE, AFTER_UPDA [...]
| `polaris.event-listener.`_`<name>`_`.enabled-event-categories` | | <span title="CATALOG, NAMESPACE, TABLE, VIEW, POLICY, GENERIC_TABLE, PRINCIPAL_ROLE, PRINCIPAL, CATALOG_ROLE, CREDENTIAL, TRANSACTION, NOTIFICATION, CONFIG, TASK_EXECUTION, RATE_LIMITING"><code>list of enum (CATALOG, NAMESPACE, TABLE, ...)</code></span> | Comma separated list of enabled event type categories. Each category is a collection of related Polaris event types. This event listener will only receive events of [...]
+| `polaris.event-listener.executor.pool-size` | `-1` | `int` | The thread pool size. The default is -1, which is interpreted as the number of available cores. |
+| `polaris.event-listener.executor.queue-size` | `-1` | `int` | The queue size. The default is -1, which is interpreted as unbounded. |