pgjdbc/pgjdbc GitHub issues and pull requests (mirror)  
help / color / mirror / Atom feed
From: sehrope (@sehrope) <[email protected]>
To: pgjdbc/pgjdbc <[email protected]>
Subject: [pgjdbc/pgjdbc] PR #4165: Replace connectThreadFactory with connectExecutor
Date: Thu, 11 Jun 2026 18:35:59 +0000
Message-ID: <[email protected]> (raw)

Replaces the (unreleased) `connectThreadFactory` / `connectThreadFactoryArg` connection properties with `connectExecutor` / `connectExecutorArg`. Instead of a `ThreadFactory` that produces the worker thread, the driver now hands the connection-attempt task to an `Executor` when `loginTimeout` is in effect.

Default behavior is the same as before all of these changes, i.e. a daemon thread named "PostgreSQL JDBC driver connection thread".

Majority of the diff is updating the tests. The actual changes to swap it into the Driver is straightforward.

One thing to keep in mind of with this change is that it's possible for a caller to specify a (bad) Executor that runs in the invoking thread:

```java
class ForegroundExecutor implements Executor {
   public void execute(Runnable r) {
     r.run();
   }
}
```

Supplying an executor like that would defeat the login timeout code from operating as it'd be busy running the connection attempt. I've called that out in the docs / changelog.

There's also some interesting situations that can arise where an Executor is reusing Threads. Say this happens:

1. Connection attempt initiated in main Thread T0 with non-zero login timeout
2. Executor creates Thread T1 to attempt connection creation in background
3. Attempt times out and T0 cancels / fires interrupt to T1
4. Executor re-uses T1 for a subsequent connection attempt

Unless something is explicitly clearling the interrupt of T1 then the new connection attempt will start interrupted. And for something like SCRAM iteration (with the updated lib) it'll always early exit out because it will think it's being interrupted.

Classes like the built-in ThreadPoolExecutor already handle this by clearing the interrupt flag when they complete / start a new task. But leaving this note here in case someone ever shows up with errors due to a custom Executor that causes this situation.

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1429474664..3c2cb3f2e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 ### Security
 ### Added
 * feat: invalidate the prepared-statement cache after CREATE/DROP/ALTER so callers no longer trip on "cached plan must not change result type" without opting into `autosave=ALWAYS`. Controlled by the new `flushCacheOnDdl` connection property (default `true`); set to `false` for the prior behaviour.
-* feat: add `connectThreadFactory` connection property to customize the `ThreadFactory` used to spawn the worker thread that runs the connection attempt when `loginTimeout` is in effect. The value is the fully qualified name of a class implementing `java.util.concurrent.ThreadFactory`. With a null value, the default, the driver retains the prior behavior of using a daemon thread named `"PostgreSQL JDBC driver connection thread"`. Useful for testing timeout behaviour or for applications that want detailed control of all driver-created threads.
+* feat: add `connectExecutor` connection property to customize the `Executor` used to run the worker task that performs the connection attempt when `loginTimeout` is in effect. The value is the fully qualified name of a class implementing `java.util.concurrent.Executor`. With a null value, the default, the driver retains the prior behavior of running the connection attempt on a daemon thread named `"PostgreSQL JDBC driver connection thread"`. The executor must run the task on a thread other than the caller's. Running the attempt on a named thread lets applications that monitor driver-created threads identify it.
 
 ### Changed
 * refactor: the worker that runs the connection attempt under `loginTimeout` is now a `FutureTask` (`ConnectTask`) instead of the hand-rolled `ConnectThread`. When the caller hits the timeout, the task is now cancelled with `cancel(true)`, which interrupts the worker thread rather than letting it run to completion. This makes the connection attempt interruptible, so `loginTimeout` can stop a slow connection attempt instead of leaking a thread. As before, a connection that the worker still manages to establish after the caller gives up is closed by the worker so that it does not leak. There are no public API changes and this should only lead to faster background resource cleanup for connections that time out.
diff --git a/docs/content/documentation/use.md b/docs/content/documentation/use.md
index b87d221132..315dff6672 100644
--- a/docs/content/documentation/use.md
+++ b/docs/content/documentation/use.md
@@ -254,12 +254,14 @@ The timeout value in seconds that the driver will wait for a query to execute if
 * **`loginTimeout (`*int*`)`** *Default `0`*\
 Specify how long to wait for establishment of a database connection. The timeout is specified in seconds max(2147484).
 
-* **`connectThreadFactory (`*String*`)`** *Default `null`*\
-The fully qualified name of a class implementing `java.util.concurrent.ThreadFactory`. When `loginTimeout` is in effect, `Driver.connect(...)` uses the configured factory to produce the `ThreadFactory` that spawns the worker thread running the connection attempt.
-With a null value, the driver uses a daemon thread named `"PostgreSQL JDBC driver connection thread"`. This indirection can be used to customize testing of timeout behavior or for applications that want detailed control of all driver created threads.
-
-* **`connectThreadFactoryArg (`*String*`)`** \
-An optional String argument passed to the constructor of the `connectThreadFactory` class.
+* **`connectExecutor (`*String*`)`** *Default `null`*\
+The fully qualified name of a class implementing `java.util.concurrent.Executor`. When `loginTimeout` is in effect, `Driver.connect(...)` hands the worker task that runs the connection attempt to the configured `Executor`.
+If the value is null, the driver runs the connection attempt on its own daemon thread named `"PostgreSQL JDBC driver connection thread"` (the name may change in future releases).
+Running the attempt on a named thread lets applications that monitor driver-created threads identify it.
+The executor **must** run the submitted task on a thread other than the caller's and it must support thread interruption.
+
+* **`connectExecutorArg (`*String*`)`** \
+An optional String argument passed to the constructor of the `connectExecutor` class.
 
 * **`connectTimeout (`*int*`)`** *Default `10`*\
 The timeout value used for socket connect operations. If connecting to the server takes longer than this value, the connection is broken. 
diff --git a/pgjdbc/src/main/java/org/postgresql/Driver.java b/pgjdbc/src/main/java/org/postgresql/Driver.java
index d0ccf88779..a394fedd55 100644
--- a/pgjdbc/src/main/java/org/postgresql/Driver.java
+++ b/pgjdbc/src/main/java/org/postgresql/Driver.java
@@ -40,8 +40,8 @@
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
 import java.util.concurrent.FutureTask;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
@@ -290,27 +290,21 @@ private Properties loadDefaultProperties() throws IOException {
 
       LOGGER.log(Level.FINE, "Connecting with URL: {0}", url);
 
-      // Enforce login timeout, if specified, by running the connection
-      // attempt in a separate thread. If we hit the timeout without the
-      // connection completing, we abandon the connection attempt in
-      // the calling thread and try to cancel the worker thread.
-      // If cancellation does not take effect immediately, the worker
-      // cleans up any connection it manages to establish after
-      // abandonment. See ConnectTask for more details.
+      // Enforce login timeout, if specified, by handing the connection
+      // attempt to an Executor, which must run it on a thread other than
+      // this one. If we hit the timeout without the connection completing,
+      // we abandon the connection attempt in the calling thread and try to
+      // cancel the worker thread. If cancellation does not take effect
+      // immediately, the worker cleans up any connection it manages to
+      // establish after abandonment. See ConnectTask for more details.
       long timeout = timeout(props);
       if (timeout <= 0) {
         return makeConnection(url, props);
       }
 
       ConnectTask ct = new ConnectTask(url, props);
-      ThreadFactory threadFactory = resolveConnectThreadFactory(props);
-      Thread thread = threadFactory.newThread(ct);
-      if (thread == null) {
-        throw new PSQLException(
-            GT.tr("ThreadFactory returned a null Thread for the connection attempt."),
-            PSQLState.UNEXPECTED_ERROR);
-      }
-      thread.start();
+      Executor executor = resolveConnectExecutor(props);
+      executor.execute(ct);
       return ct.getResult(timeout);
     } catch (PSQLException ex1) {
       LOGGER.log(Level.FINE, "Connection error: ", ex1);
@@ -704,24 +698,24 @@ private static HostSpec[] hostSpecs(Properties props) {
     return hostSpecs;
   }
 
-  private static final ThreadFactory DEFAULT_THREAD_FACTORY = r -> {
+  private static final Executor DEFAULT_EXECUTOR = r -> {
     Thread thread = new Thread(r, "PostgreSQL JDBC driver connection thread");
     thread.setDaemon(true); // Don't prevent the VM from shutting down
-    return thread;
+    thread.start();
   };
 
-  private static ThreadFactory resolveConnectThreadFactory(Properties props)
+  private static Executor resolveConnectExecutor(Properties props)
       throws PSQLException {
-    String className = PGProperty.CONNECT_THREAD_FACTORY.getOrDefault(props);
+    String className = PGProperty.CONNECT_EXECUTOR.getOrDefault(props);
     if (className == null || className.isEmpty()) {
-      return DEFAULT_THREAD_FACTORY;
+      return DEFAULT_EXECUTOR;
     }
     try {
-      return ObjectFactory.instantiate(ThreadFactory.class, className, props, true,
-          PGProperty.CONNECT_THREAD_FACTORY_ARG.getOrDefault(props));
+      return ObjectFactory.instantiate(Executor.class, className, props, true,
+          PGProperty.CONNECT_EXECUTOR_ARG.getOrDefault(props));
     } catch (Exception ex) {
       throw new PSQLException(
-          GT.tr("Could not instantiate connectThreadFactory: {0}", className),
+          GT.tr("Could not instantiate connectExecutor: {0}", className),
           PSQLState.INVALID_PARAMETER_VALUE, ex);
     }
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
index 01782ae47c..bb5a9e51b6 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
@@ -170,22 +170,26 @@ public enum PGProperty {
       new String[]{"true", "false"}),
 
   /**
-   * Factory class used to produce the helper thread used to enforce {@code loginTimeout} during
-   * connection establishment. Value must be the name of a class implementing {@link java.util.concurrent.ThreadFactory}.
-   * With a null value, which is the default, the driver uses a daemon thread named {@code "PostgreSQL JDBC driver connection thread"}.
+   * Executor used to run the connection attempt that enforces {@code loginTimeout} during
+   * connection establishment. Value must be the name of a class implementing {@link java.util.concurrent.Executor}.
+   * With a null value, which is the default, the driver runs the connection attempt on a daemon
+   * thread named {@code "PostgreSQL JDBC driver connection thread"}.
+   *
+   * <p>The executor <b>must</b> run the submitted task on a thread other than the caller's and support
+   * thread interruption to handle canceled connection attempts.
    */
-  CONNECT_THREAD_FACTORY(
-      "connectThreadFactory",
+  CONNECT_EXECUTOR(
+      "connectExecutor",
       null,
-      "Factory class to instantiate the Thread used for connection attempts with loginTimeout"),
+      "Executor class used to run connection attempts with loginTimeout. It must support thread interrupts and clear interrupt flags after task execution."),
 
   /**
-   * The String argument to give to the constructor of the connectThreadFactory class.
+   * The String argument to give to the constructor of the connectExecutor class.
    */
-  CONNECT_THREAD_FACTORY_ARG(
-      "connectThreadFactoryArg",
+  CONNECT_EXECUTOR_ARG(
+      "connectExecutorArg",
       null,
-      "Argument forwarded to constructor of connectThreadFactory class."),
+      "Argument forwarded to constructor of connectExecutor class."),
 
   /**
    * The timeout value used for socket connect operations. If connecting to the server takes longer
diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
index 892f606cd9..4a5dd4ce14 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -1865,20 +1865,20 @@ public void setXmlFactoryFactory(@Nullable String xmlFactoryFactory) {
     PGProperty.XML_FACTORY_FACTORY.set(properties, xmlFactoryFactory);
   }
 
-  public @Nullable String getConnectThreadFactory() {
-    return PGProperty.CONNECT_THREAD_FACTORY.getOrDefault(properties);
+  public @Nullable String getConnectExecutor() {
+    return PGProperty.CONNECT_EXECUTOR.getOrDefault(properties);
   }
 
-  public void setConnectThreadFactory(@Nullable String connectThreadFactory) {
-    PGProperty.CONNECT_THREAD_FACTORY.set(properties, connectThreadFactory);
+  public void setConnectExecutor(@Nullable String connectExecutor) {
+    PGProperty.CONNECT_EXECUTOR.set(properties, connectExecutor);
   }
 
-  public @Nullable String getConnectThreadFactoryArg() {
-    return PGProperty.CONNECT_THREAD_FACTORY_ARG.getOrDefault(properties);
+  public @Nullable String getConnectExecutorArg() {
+    return PGProperty.CONNECT_EXECUTOR_ARG.getOrDefault(properties);
   }
 
-  public void setConnectThreadFactoryArg(@Nullable String connectThreadFactoryArg) {
-    PGProperty.CONNECT_THREAD_FACTORY_ARG.set(properties, connectThreadFactoryArg);
+  public void setConnectExecutorArg(@Nullable String connectExecutorArg) {
+    PGProperty.CONNECT_EXECUTOR_ARG.set(properties, connectExecutorArg);
   }
 
   public @Nullable String getPemKeyAlgorithm() {
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ConnectExecutorTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ConnectExecutorTest.java
new file mode 100644
index 0000000000..52ccd25cf4
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ConnectExecutorTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.postgresql.PGProperty;
+import org.postgresql.test.TestUtil;
+
+import org.junit.jupiter.api.Test;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ConnectExecutorTest {
+  @Test
+  void customExecutorIsInvoked() throws Exception {
+    AtomicInteger threadsCreated = new AtomicInteger();
+    Executor executor = r -> {
+      threadsCreated.incrementAndGet();
+      Thread t = new Thread(r, "ConnectExecutorTest worker");
+      t.setDaemon(true);
+      t.start();
+    };
+
+    ThreadLocalExecutor.DELEGATE.set(executor);
+    try {
+      Properties props = new Properties();
+      PGProperty.LOGIN_TIMEOUT.set(props, "10");
+      PGProperty.CONNECT_EXECUTOR.set(props,
+          ThreadLocalExecutor.class.getName());
+
+      try (Connection conn = TestUtil.openDB(props)) {
+        assertNotNull(conn);
+        assertTrue(conn.isValid(1));
+      }
+
+      assertEquals(1, threadsCreated.get(),
+          "Configured Executor should produce exactly one thread for a connect attempt "
+              + "with loginTimeout > 0");
+    } finally {
+      ThreadLocalExecutor.DELEGATE.remove();
+    }
+  }
+
+  @Test
+  void executorArgMatching() throws Exception {
+    Properties props = new Properties();
+    PGProperty.LOGIN_TIMEOUT.set(props, "10");
+    PGProperty.CONNECT_EXECUTOR.set(props,
+        ArgValidatingExecutor.class.getName());
+    PGProperty.CONNECT_EXECUTOR_ARG.set(props,
+        ArgValidatingExecutor.EXPECTED_ARG);
+
+    try (Connection conn = TestUtil.openDB(props)) {
+      assertNotNull(conn);
+      assertTrue(conn.isValid(1));
+    }
+  }
+
+  @Test
+  void executorArgMismatchFailsConnect() {
+    Properties props = new Properties();
+    PGProperty.LOGIN_TIMEOUT.set(props, "10");
+    PGProperty.CONNECT_EXECUTOR.set(props,
+        ArgValidatingExecutor.class.getName());
+    PGProperty.CONNECT_EXECUTOR_ARG.set(props, "wrong-arg");
+
+    assertThrows(SQLException.class, () -> TestUtil.openDB(props),
+        "Connect should fail when the executor's constructor rejects the configured arg");
+  }
+
+  /**
+   * Test-only {@link java.util.concurrent.Executor} that delegates to whatever the current thread
+   * has stashed in {@link #DELEGATE}. Lets each test install its own executor without sharing
+   * static state across tests.
+   */
+  public static class ThreadLocalExecutor implements Executor {
+    static final ThreadLocal<Executor> DELEGATE = new ThreadLocal<>();
+
+    @Override
+    public void execute(Runnable r) {
+      Executor ex = DELEGATE.get();
+      if (ex == null) {
+        throw new IllegalStateException(
+            "No Executor configured in ThreadLocalExecutor.DELEGATE");
+      }
+      ex.execute(r);
+    }
+  }
+
+  /**
+   * Test-only {@link java.util.concurrent.Executor} whose constructor validates the
+   * {@code connectExecutorArg} String, throwing if it does not match the expected value. A
+   * mismatch surfaces as a failed executor instantiation (and thus a failed connect), without
+   * spawning a worker thread or waiting for loginTimeout.
+   */
+  public static class ArgValidatingExecutor implements Executor {
+    static final String EXPECTED_ARG = "expected-arg-value";
+
+    public ArgValidatingExecutor(String arg) {
+      if (!EXPECTED_ARG.equals(arg)) {
+        throw new IllegalArgumentException("Unexpected connectExecutorArg: " + arg);
+      }
+    }
+
+    @Override
+    public void execute(Runnable r) {
+      Thread t = new Thread(r, "ArgValidatingExecutor worker");
+      t.setDaemon(true);
+      t.start();
+    }
+  }
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ConnectThreadFactoryTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ConnectThreadFactoryTest.java
deleted file mode 100644
index 6871280ef3..0000000000
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ConnectThreadFactoryTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (c) 2026, PostgreSQL Global Development Group
- * See the LICENSE file in the project root for more information.
- */
-
-package org.postgresql.test.jdbc2;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.postgresql.PGProperty;
-import org.postgresql.test.TestUtil;
-
-import org.junit.jupiter.api.Test;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.Properties;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class ConnectThreadFactoryTest {
-  @Test
-  void customFactoryIsInvoked() throws Exception {
-    AtomicInteger threadsCreated = new AtomicInteger();
-    ThreadFactory threadFactory = r -> {
-      threadsCreated.incrementAndGet();
-      Thread t = new Thread(r, "ConnectThreadFactoryTest worker");
-      t.setDaemon(true);
-      return t;
-    };
-
-    ThreadLocalThreadFactory.DELEGATE.set(threadFactory);
-    try {
-      Properties props = new Properties();
-      PGProperty.LOGIN_TIMEOUT.set(props, "10");
-      PGProperty.CONNECT_THREAD_FACTORY.set(props,
-          ThreadLocalThreadFactory.class.getName());
-
-      try (Connection conn = TestUtil.openDB(props)) {
-        assertNotNull(conn);
-        assertTrue(conn.isValid(1));
-      }
-
-      assertEquals(1, threadsCreated.get(),
-          "Configured ThreadFactory should produce exactly one thread for a connect attempt "
-              + "with loginTimeout > 0");
-    } finally {
-      ThreadLocalThreadFactory.DELEGATE.remove();
-    }
-  }
-
-  @Test
-  void factoryArgMatching() throws Exception {
-    Properties props = new Properties();
-    PGProperty.LOGIN_TIMEOUT.set(props, "10");
-    PGProperty.CONNECT_THREAD_FACTORY.set(props,
-        ArgValidatingThreadFactory.class.getName());
-    PGProperty.CONNECT_THREAD_FACTORY_ARG.set(props,
-        ArgValidatingThreadFactory.EXPECTED_ARG);
-
-    try (Connection conn = TestUtil.openDB(props)) {
-      assertNotNull(conn);
-      assertTrue(conn.isValid(1));
-    }
-  }
-
-  @Test
-  void factoryArgMismatchFailsConnect() {
-    Properties props = new Properties();
-    PGProperty.LOGIN_TIMEOUT.set(props, "10");
-    PGProperty.CONNECT_THREAD_FACTORY.set(props,
-        ArgValidatingThreadFactory.class.getName());
-    PGProperty.CONNECT_THREAD_FACTORY_ARG.set(props, "wrong-arg");
-
-    assertThrows(SQLException.class, () -> TestUtil.openDB(props),
-        "Connect should fail when the worker thread's run() rejects the configured arg");
-  }
-
-  /**
-   * Test-only {@link java.util.concurrent.ThreadFactory} that returns delegates to whatever the
-   * current thread has stashed in {@link #DELEGATE}. Lets each test install its own factory
-   * without sharing static state across tests.
-   */
-  public static class ThreadLocalThreadFactory implements ThreadFactory {
-    static final ThreadLocal<ThreadFactory> DELEGATE = new ThreadLocal<>();
-
-    @Override
-    public Thread newThread(Runnable r) {
-      ThreadFactory tf = DELEGATE.get();
-      if (tf == null) {
-        throw new IllegalStateException(
-            "No ThreadFactory configured in ThreadLocalThreadFactory.DELEGATE");
-      }
-      return tf.newThread(r);
-    }
-  }
-
-  /**
-   * Test-only {@link java.util.concurrent.ThreadFactory} whose constructor captures the
-   * {@code connectThreadFactoryArg} String. The produced ThreadFactory wraps the
-   * connection task in a Runnable that throws if the captured arg does not match the expected
-   * value so the failure happens inside the worker thread's run() rather than in the
-   * factory's constructor.
-   */
-  public static class ArgValidatingThreadFactory implements ThreadFactory {
-    static final String EXPECTED_ARG = "expected-arg-value";
-
-    private final String arg;
-
-    public ArgValidatingThreadFactory(String arg) {
-      this.arg = arg;
-    }
-
-    @Override
-    public Thread newThread(Runnable r) {
-      String capturedArg = this.arg;
-      Thread t = new Thread(() -> {
-        if (!EXPECTED_ARG.equals(capturedArg)) {
-          throw new IllegalArgumentException(
-              "Unexpected connectThreadFactoryArg: " + capturedArg);
-        }
-        r.run();
-      }, "ArgValidatingThreadFactory worker");
-      t.setDaemon(true);
-      return t;
-    }
-  }
-}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutInterruptTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutInterruptTest.java
index fd2dac8918..d36556cfeb 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutInterruptTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutInterruptTest.java
@@ -22,7 +22,7 @@
 
 import java.sql.SQLException;
 import java.util.Properties;
-import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
@@ -43,14 +43,14 @@ void loginTimeoutInterruptsAuthPluginSleep() throws Exception {
     SleepThenThrowAuthPlugin.PLUGIN_INVOKED.set(false);
     SleepThenThrowAuthPlugin.INTERRUPTED_DURING_SLEEP.set(false);
     SleepThenThrowAuthPlugin.POST_SLEEP_REACHED.set(false);
-    CapturingThreadFactory.CAPTURED.set(null);
+    CapturingExecutor.CAPTURED.set(null);
 
     Properties props = new Properties();
     PGProperty.LOGIN_TIMEOUT.set(props, Long.toString(LOGIN_TIMEOUT_SECONDS));
     PGProperty.AUTHENTICATION_PLUGIN_CLASS_NAME.set(props,
         SleepThenThrowAuthPlugin.class.getName());
-    PGProperty.CONNECT_THREAD_FACTORY.set(props,
-        CapturingThreadFactory.class.getName());
+    PGProperty.CONNECT_EXECUTOR.set(props,
+        CapturingExecutor.class.getName());
 
     long startNanos = System.nanoTime();
     SQLException ex = assertThrows(SQLException.class, () -> TestUtil.openDB(props));
@@ -60,8 +60,8 @@ void loginTimeoutInterruptsAuthPluginSleep() throws Exception {
     // in ConnectTask.abandon() does not actually deliver an interrupt, the worker stays in
     // Thread.sleep for the full PLUGIN_SLEEP_MS, join() times out, and isAlive() is still
     // true.
-    Thread worker = CapturingThreadFactory.CAPTURED.get();
-    assertNotNull(worker, "Custom ThreadFactory should have captured the worker thread");
+    Thread worker = CapturingExecutor.CAPTURED.get();
+    assertNotNull(worker, "Custom Executor should have captured the worker thread");
     worker.join(WORKER_JOIN_TIMEOUT_MS);
     assertFalse(worker.isAlive(),
         "Worker thread should have exited after cancel(true) interrupted its Thread.sleep,"
@@ -87,19 +87,19 @@ void loginTimeoutInterruptsAuthPluginSleep() throws Exception {
   }
 
   /**
-   * Test-only {@link java.util.concurrent.ThreadFactory} that captures the worker thread the driver spawns
+   * Test-only {@link java.util.concurrent.Executor} that captures the worker thread the driver spawns
    * for the connection attempt, so the test can {@link Thread#join} it and observe whether
    * cancel(true) actually caused the worker to exit.
    */
-  public static class CapturingThreadFactory implements ThreadFactory {
+  public static class CapturingExecutor implements Executor {
     static final AtomicReference<Thread> CAPTURED = new AtomicReference<>();
 
     @Override
-    public Thread newThread(Runnable r) {
+    public void execute(Runnable r) {
       Thread t = new Thread(r, "LoginTimeoutInterruptTest worker");
       t.setDaemon(true);
       CAPTURED.set(t);
-      return t;
+      t.start();
     }
   }
 


reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: github://pgjdbc/pgjdbc
  Cc: [email protected], [email protected]
  Subject: Re: [pgjdbc/pgjdbc] PR #4165: Replace connectThreadFactory with connectExecutor
  In-Reply-To: <<[email protected]>>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox