This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 087caa46 [runtime][python] Use descriptive thread names for the async 
executors (#1028)
087caa46 is described below

commit 087caa460d596853a8141f33bbb534186de405bf
Author: Edson <[email protected]>
AuthorDate: Fri Aug 21 04:15:58 2026 -0400

    [runtime][python] Use descriptive thread names for the async executors 
(#1028)
    
    Generated-by: Claude Fable 5
---
 .../flink_agents/runtime/flink_runner_context.py   | 14 +++-
 .../runtime/tests/test_async_thread_pool.py        | 46 ++++++++++++
 .../runtime/async/AsyncExecutorThreadFactory.java  | 47 ++++++++++++
 .../runtime/async/ContinuationActionExecutor.java  |  2 +-
 .../async/AsyncExecutorThreadFactoryTest.java      | 83 ++++++++++++++++++++++
 5 files changed, 190 insertions(+), 2 deletions(-)

diff --git a/python/flink_agents/runtime/flink_runner_context.py 
b/python/flink_agents/runtime/flink_runner_context.py
index 95539084..9f6ce590 100644
--- a/python/flink_agents/runtime/flink_runner_context.py
+++ b/python/flink_agents/runtime/flink_runner_context.py
@@ -15,6 +15,7 @@
 #  See the License for the specific language governing permissions and
 # limitations under the License.
 
#################################################################################
+import itertools
 import json
 import logging
 import os
@@ -881,14 +882,25 @@ def close_flink_runner_context(
     ctx.close()
 
 
+_ASYNC_POOL_ID = itertools.count(1)
+"""Process-unique pool ids keeping multiple async executors distinguishable."""
+
+
 def create_async_thread_pool(max_workers: int | None) -> ThreadPoolExecutor:
     """Used to create a thread pool to execute asynchronous
     code block in action.
+
+    Worker threads are named 
``flink-agents-python-async-<pool-id>_<worker-id>``
+    (the default ``ThreadPoolExecutor-N_M`` names make Flink Agents workers 
hard
+    to attribute in TaskManager thread dumps and profiler output).
     """
     logging.info(
         f"Initialize fixed thread pool for async task with {max_workers} 
threads"
     )
-    return ThreadPoolExecutor(max_workers=max_workers or os.cpu_count() * 2)
+    return ThreadPoolExecutor(
+        max_workers=max_workers or os.cpu_count() * 2,
+        thread_name_prefix=f"flink-agents-python-async-{next(_ASYNC_POOL_ID)}",
+    )
 
 
 def close_async_thread_pool(executor: ThreadPoolExecutor) -> None:
diff --git a/python/flink_agents/runtime/tests/test_async_thread_pool.py 
b/python/flink_agents/runtime/tests/test_async_thread_pool.py
new file mode 100644
index 00000000..0be6f0b3
--- /dev/null
+++ b/python/flink_agents/runtime/tests/test_async_thread_pool.py
@@ -0,0 +1,46 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+import re
+import threading
+
+from flink_agents.runtime.flink_runner_context import (
+    close_async_thread_pool,
+    create_async_thread_pool,
+)
+
+
+def test_async_workers_carry_descriptive_names() -> None:
+    pool = create_async_thread_pool(2)
+    try:
+        name = pool.submit(lambda: threading.current_thread().name).result()
+        # ThreadPoolExecutor appends _<worker-id> to the prefix.
+        assert re.fullmatch(r"flink-agents-python-async-\d+_\d+", name), name
+    finally:
+        close_async_thread_pool(pool)
+
+
+def test_pool_prefixes_distinct_across_instances() -> None:
+    first = create_async_thread_pool(1)
+    second = create_async_thread_pool(1)
+    try:
+        first_name = first.submit(lambda: 
threading.current_thread().name).result()
+        second_name = second.submit(lambda: 
threading.current_thread().name).result()
+        assert first_name.rsplit("_", 1)[0] != second_name.rsplit("_", 1)[0]
+    finally:
+        close_async_thread_pool(first)
+        close_async_thread_pool(second)
diff --git 
a/runtime/src/main/java/org/apache/flink/agents/runtime/async/AsyncExecutorThreadFactory.java
 
b/runtime/src/main/java/org/apache/flink/agents/runtime/async/AsyncExecutorThreadFactory.java
new file mode 100644
index 00000000..788a9c3e
--- /dev/null
+++ 
b/runtime/src/main/java/org/apache/flink/agents/runtime/async/AsyncExecutorThreadFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.flink.agents.runtime.async;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * {@link ThreadFactory} for the Flink Agents Java async executor, producing 
descriptive,
+ * collision-resistant thread names of the form {@code 
flink-agents-java-async-pool-N-thread-M}.
+ *
+ * <p>Default executor names such as {@code pool-N-thread-M} make Flink Agents 
async workers hard to
+ * attribute in TaskManager thread dumps and profiler output, where many 
unrelated pools coexist.
+ *
+ * <p>Thread creation is delegated to {@link 
Executors#defaultThreadFactory()}, which normalizes
+ * daemon status and priority regardless of the calling thread (a directly 
constructed {@code new
+ * Thread(...)} would inherit both from it). This factory only prepends the 
{@code
+ * flink-agents-java-async-} prefix to the delegate's pool- and 
worker-numbered name.
+ */
+public final class AsyncExecutorThreadFactory implements ThreadFactory {
+
+    private static final String NAME_PREFIX = "flink-agents-java-async-";
+
+    private final ThreadFactory delegate = Executors.defaultThreadFactory();
+
+    @Override
+    public Thread newThread(Runnable runnable) {
+        Thread thread = delegate.newThread(runnable);
+        thread.setName(NAME_PREFIX + thread.getName());
+        return thread;
+    }
+}
diff --git 
a/runtime/src/main/java21/org/apache/flink/agents/runtime/async/ContinuationActionExecutor.java
 
b/runtime/src/main/java21/org/apache/flink/agents/runtime/async/ContinuationActionExecutor.java
index 4dc30d81..695d5345 100644
--- 
a/runtime/src/main/java21/org/apache/flink/agents/runtime/async/ContinuationActionExecutor.java
+++ 
b/runtime/src/main/java21/org/apache/flink/agents/runtime/async/ContinuationActionExecutor.java
@@ -43,7 +43,7 @@ public class ContinuationActionExecutor {
     public ContinuationActionExecutor(int numAsyncThreads) {
         LOG.info("Initialize fixed thread pool for async task with {} 
threads", numAsyncThreads);
         this.asyncExecutor =
-                Executors.newFixedThreadPool(numAsyncThreads);
+                Executors.newFixedThreadPool(numAsyncThreads, new 
AsyncExecutorThreadFactory());
     }
 
     /**
diff --git 
a/runtime/src/test/java/org/apache/flink/agents/runtime/async/AsyncExecutorThreadFactoryTest.java
 
b/runtime/src/test/java/org/apache/flink/agents/runtime/async/AsyncExecutorThreadFactoryTest.java
new file mode 100644
index 00000000..125d1812
--- /dev/null
+++ 
b/runtime/src/test/java/org/apache/flink/agents/runtime/async/AsyncExecutorThreadFactoryTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.flink.agents.runtime.async;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link AsyncExecutorThreadFactory} thread naming. */
+class AsyncExecutorThreadFactoryTest {
+
+    @Test
+    @DisplayName("Threads carry the descriptive flink-agents-java-async name")
+    void testThreadNamesCarryDescriptivePrefix() throws Exception {
+        ExecutorService executor =
+                Executors.newFixedThreadPool(2, new 
AsyncExecutorThreadFactory());
+        try {
+            String name = executor.submit(() -> 
Thread.currentThread().getName()).get();
+            
assertThat(name).matches("flink-agents-java-async-pool-\\d+-thread-\\d+");
+        } finally {
+            executor.shutdownNow();
+            executor.awaitTermination(5, TimeUnit.SECONDS);
+        }
+    }
+
+    @Test
+    @DisplayName("Distinct factories produce distinct pool ids, distinct 
workers distinct ids")
+    void testNamesDistinctAcrossPoolsAndWorkers() {
+        AsyncExecutorThreadFactory first = new AsyncExecutorThreadFactory();
+        AsyncExecutorThreadFactory second = new AsyncExecutorThreadFactory();
+
+        String firstPoolWorker1 = first.newThread(() -> {}).getName();
+        String firstPoolWorker2 = first.newThread(() -> {}).getName();
+        String secondPoolWorker1 = second.newThread(() -> {}).getName();
+
+        assertThat(firstPoolWorker1).isNotEqualTo(firstPoolWorker2);
+        assertThat(firstPoolWorker1).isNotEqualTo(secondPoolWorker1);
+        // Pool segment differs between factories.
+        String firstPool = firstPoolWorker1.replaceAll("-thread-\\d+$", "");
+        String secondPool = secondPoolWorker1.replaceAll("-thread-\\d+$", "");
+        assertThat(firstPool).isNotEqualTo(secondPool);
+    }
+
+    @Test
+    @DisplayName(
+            "Daemon status and priority are normalized like the default 
factory, not inherited")
+    void testDaemonStatusAndPriorityNormalizedNotInherited() throws Exception {
+        // Create workers from a daemon, max-priority thread: a plain new 
Thread(...) would
+        // inherit both attributes, while the default-factory delegate 
normalizes them.
+        AtomicReference<Thread> created = new AtomicReference<>();
+        Thread creator =
+                new Thread(() -> created.set(new 
AsyncExecutorThreadFactory().newThread(() -> {})));
+        creator.setDaemon(true);
+        creator.setPriority(Thread.MAX_PRIORITY);
+        creator.start();
+        creator.join(5000);
+
+        assertThat(created.get()).isNotNull();
+        assertThat(created.get().isDaemon()).isFalse();
+        
assertThat(created.get().getPriority()).isEqualTo(Thread.NORM_PRIORITY);
+    }
+}

Reply via email to