Subject: [pgjdbc/pgjdbc] issue #3957: Connection.isValid(timeout) hangs indefinitely after network interruption during PGCopyOutputStream COPY

From: "jptx1234 (@jptx1234)" <[email-masked]>
Date: Wed, 11 Mar 2026 16:09:20 +0000

**Describe the issue**
When using `PGCopyOutputStream` to perform a COPY operation and the network is interrupted (e.g., network cable disconnected or PostgreSQL server restarted), calling `Connection.isValid(timeout)` after catching the exception hangs indefinitely instead of returning `false` within the specified timeout.

**Driver Version?**
42.7.10

**Java Version?**
21

**OS Version?**
Windows 11

**PostgreSQL Version?**
PostgreSQL 18

**To Reproduce**
1. Run the demo code below
2. During the COPY operation, interrupt the network (disconnect and reconnect network cable, or restart PostgreSQL server)
3. Observe that `conn.isValid(5)` hangs indefinitely instead of returning within 5 seconds

**Expected behaviour**
`Connection.isValid(5)` should return `false` within 5 seconds after network interruption.

**Actual behaviour**
`Connection.isValid(5)` hangs indefinitely and never returns.

**Logs**
Thread dump captured via `jstack -l <pid>` shows the main thread is stuck waiting on a lock in `QueryExecutorImpl.waitOnLock()`
```
"main" #1 [28072] prio=5 os_prio=0 cpu=343.75ms elapsed=21.93s tid=0x0000025914a32250 nid=28072 waiting on condition  [0x000000dc522fe000]
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park([email-masked]/Native Method)
        - parking to wait for  <0x00000006239b2788> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park([email-masked]/LockSupport.java:371)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block([email-masked]/AbstractQueuedSynchronizer.java:519)
        at java.util.concurrent.ForkJoinPool.unmanagedBlock([email-masked]/ForkJoinPool.java:3780)
        at java.util.concurrent.ForkJoinPool.managedBlock([email-masked]/ForkJoinPool.java:3725)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await([email-masked]/AbstractQueuedSynchronizer.java:1712)
        at org.postgresql.core.v3.QueryExecutorImpl.waitOnLock(QueryExecutorImpl.java:295)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:395)
        at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:526)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:436)
        at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:358)
        at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:343)
        at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:319)
        at org.postgresql.jdbc.PgConnection.isValid(PgConnection.java:1584)
        at io.github.jptx1234.pgjdbc_copy_demo.PgjdbcCopyDemoStream.copyData(PgjdbcCopyDemoStream.java:55)
        at io.github.jptx1234.pgjdbc_copy_demo.PgjdbcCopyDemoStream.main(PgjdbcCopyDemoStream.java:29)

   Locked ownable synchronizers:
        - <0x00000006251520f0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
```

**Minimal Reproducible Example**
```java
import org.postgresql.copy.PGCopyOutputStream;
import org.postgresql.jdbc.PgConnection;

import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.UUID;

public class PgjdbcCopyDemoStream {

    private static final String DB_URL = "jdbc:postgresql://localhost:5432/postgres";
    private static final String DB_USER = "postgres";
    private static final String DB_PASSWORD = "password";
    private static final String TABLE_NAME = "copy_test";

    public static void main(String[] args) throws Exception {
        try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
            createTable(conn);
            copyData(conn);
        }
    }

    private static void createTable(Connection conn) throws Exception {
        try (Statement stmt = conn.createStatement()) {
            stmt.execute("DROP TABLE IF EXISTS " + TABLE_NAME);
            stmt.execute("CREATE TABLE " + TABLE_NAME + " (id SERIAL PRIMARY KEY, data TEXT)");
        }
    }

    private static void copyData(Connection conn) throws Exception {
        String copySql = "COPY " + TABLE_NAME + " (data) FROM STDIN WITH (FORMAT CSV)";

        try (PGCopyOutputStream copyOut = new PGCopyOutputStream(conn.unwrap(PgConnection.class), copySql)) {
            System.out.println("Start copying. Please interrupt network or restart PostgreSQL server.");
            // Interrupt network during this phase
            for (int i = 0; i < 500; i++) {
                copyOut.write((UUID.randomUUID() + "\n").getBytes(StandardCharsets.UTF_8));
                copyOut.flush();
                Thread.sleep(100);
            }
        } catch (Exception e) {
            System.err.println("COPY failed: " + e.getMessage());
            System.out.println("Check isValid(5)");
            // conn.isValid(5) should return false within 5 seconds, but hangs indefinitely
            System.out.println("isValid: " + conn.isValid(5));
        }
    }
}

---

From: "vlsi (@vlsi)" <[email-masked]>
Date: Sat, 14 Mar 2026 06:11:14 +0000

Fixed in https://github.com/pgjdbc/pgjdbc/pull/3960