Message-ID: From: "davecramer (@davecramer)" To: "pgjdbc/pgjdbc" Date: Fri, 15 May 2026 20:28:18 +0000 Subject: [pgjdbc/pgjdbc] PR #4074: Use NIO List-Id: X-GitHub-Author-Id: 406518 X-GitHub-Author-Login: davecramer X-GitHub-Issue: 4074 X-GitHub-Repo: pgjdbc/pgjdbc X-GitHub-State: closed X-GitHub-Type: pull_request X-GitHub-Url: https://github.com/pgjdbc/pgjdbc/pull/4074 Content-Type: text/plain; charset=utf-8 docs: add full NIO refactor design document Outlines the architecture for replacing PGStream's blocking I/O with SocketChannel + ByteBuffer + Selector. This enables true async notification detection and eliminates socket sharing races. Key decisions: - Channel always non-blocking, blocking via Selector.select() - Selector.selectNow() for thread-safe async readability checks - SSLEngine for NIO-compatible SSL - Backward compatibility with existing PGStream API wip: NIO full refactor - NIOInputStream/NIOOutputStream and PGStream SocketChannel scaffolding Adds NIOInputStream (Selector-based blocking reads via ByteBuffer) and NIOOutputStream (buffered channel writes). PGStream creates a SocketChannel but still uses VisibleBufferedInputStream for reads — next step is to replace all reads with NIOInputStream. The channel is created in non-blocking mode for Selector compatibility. The full refactor requires replacing pgInput usage throughout PGStream with NIOInputStream to eliminate the InputStream/blocking mode conflict. wip: NIO full refactor - NIOInputStream/NIOOutputStream and PGStream SocketChannel scaffolding Adds NIOInputStream (Selector-based blocking reads via ByteBuffer) and NIOOutputStream (buffered channel writes). PGStream creates a SocketChannel but still uses VisibleBufferedInputStream for reads — next step is to replace all reads with NIOInputStream. The channel is created in non-blocking mode for Selector compatibility. The full refactor requires replacing pgInput usage throughout PGStream with NIOInputStream to eliminate the InputStream/blocking mode conflict. wip: PGStream uses NIOInputStream/NIOOutputStream in non-SSL mode changeSocket now creates NIOInputStream (Selector-based) and NIOOutputStream (channel-based) when a SocketChannel is available. Falls back to traditional InputStream/OutputStream for SSL connections. pgInput is now nullable — next step is to update all read methods in PGStream to delegate to nioInput when in NIO mode. feat: full NIO refactor - PGStream uses SocketChannel + Selector after setup PGStream now creates a SocketChannel-backed socket and switches to NIO mode after connection setup (including SSL negotiation) completes: - createSocket() uses SocketChannel.open() for default socket factory - Channel stays in blocking mode during connection setup for SSL compat - enableNIO() called from PgConnection constructor switches to non-blocking - NIOInputStream uses Selector.select() for blocking reads via ByteBuffer - NIOOutputStream writes to channel via ByteBuffer on flush - VisibleBufferedInputStream wraps NIOInputStream (existing API unchanged) - SSL connections fall back to traditional InputStream (no NIO) Key benefit: NIOInputStream.isReadable() uses Selector.selectNow() which is thread-safe — a background NotificationPoller can check for incoming async data without reading from the socket or conflicting with the main thread's reads. All 26 pipeline tests pass. feat: add NotificationPoller for async LISTEN/NOTIFY detection Background daemon thread that uses NIOInputStream.isReadable() (Selector.selectNow()) to detect incoming async data without reading from the socket. Thread-safe — no socket sharing conflicts. When data is detected, sets a flag. getNotifications() checks the flag and processes any pending NotificationResponse or ParameterStatus messages before returning. - NotificationPoller started in enableNIO() (after connection setup) - Shut down on connection close/abort - Only active for non-SSL connections (SSL falls back to polling in getNotifications via hasMessagePending) - 100ms poll interval when idle, 10ms spin when waiting for main thread All 26 pipeline tests pass. fix: resolve checkstyle and checker-framework violations - Remove unused SelectionKey/Selector imports from PGStream - Fix PGNotification import ordering in QueryExecutorImpl - Use local variable for nullable notificationPoller dereference - Use local variable for nullable nioChannel in enableNIO() chore: remove development artifacts and binary blobs from repo Remove files that should not be in the PR: - 30MB heap dump (.hprof) - Development scripts and design documents - Generated pom.xml - IDE settings (.kiro/) fix: address NIO review feedback - NIOOutputStream: replace Thread.yield() spin-loop with Selector OP_WRITE wait when OS send buffer is full - NotificationPoller: use Selector.select(timeout) for efficient blocking instead of Thread.sleep(100) polling loop - processPolledNotifications: acquire main lock to prevent concurrent socket access with query execution - PGStream.close: explicitly close NIOInputStream and its Selector - Add NIOInputStream.waitForData(timeout) for blocking select fix: replace Thread.sleep spin with Object.wait/notify in NotificationPoller Use wait/notify for the dataAvailable flag instead of Thread.sleep(10) polling. The poller now parks efficiently until clearDataAvailable() is called by the main thread, eliminating unnecessary wakeups.