pgjdbc/pgjdbc GitHub issues and pull requests (mirror)  
help / color / mirror / Atom feed
[pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
11+ messages / 3 participants
[nested] [flat]

* [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-03 08:14  "vlsi (@vlsi)" <[email protected]>
  0 siblings, 0 replies; 11+ messages in thread

From: vlsi (@vlsi) @ 2025-12-03 08:14 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

`Thread.inheritedAccessControlContext` inherits the caller `ProtectionDomain`, thus it effectively holds its classloader.
There's no way to "unset `inheritedAccessControlContext`".

We workaround that by going for ForkJoinPool + ManagedBlocker so it is JDK that creates a thread, thus it should have no `inheritedAccessControlContext`.

The logic is as follows:
1) ForkJoinPool task runs on CPU and it processes the queue
2) If the queue is empty, the blocking call delegates to ManagedBlocker

It fits nice with FJP: CPU tasks do not create extra threads, and blocking queue operations do not starve FJP CPU threads.

See https://stackoverflow.com/questions/78163385/does-postgres-jdbc-lazycleaner-cause-a-classloader-memo...

---

An alternative option could be going for native `java.lang.ref.Cleaner` when running with Java 9+, however, it would still be nice to have a solution for Java 8.


^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-03 12:50  "vlsi (@vlsi)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: vlsi (@vlsi) @ 2025-12-03 12:50 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

For reference, I tried difftastic by @wilfred, and it produces reasonable diff:
<img width="1678" height="983" alt="diff with difftastic" src="https://github.com/user-attachments/assets/b74d120a-917a-4c1f-9c02-8b2fb4f7e9db"; />


^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-04 10:44  "davecramer (@davecramer)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: davecramer (@davecramer) @ 2025-12-04 10:44 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

Using claude it suggested 
```
ForkJoinPool.commonPool().execute(() -> {
    RefQueueBlocker<Object> blocker =
        new RefQueueBlocker<>(queue, threadName, threadTtl, () -> !checkEmpty());

    while (!checkEmpty()) {
        try {
            ref.onClean(true);
            ForkJoinPool.managedBlock(blocker);

            Node<?> ref = (Node<?>) blocker.drainOne();
            if (ref != null) {
                ref.onClean(true);
            }
        } catch (InterruptedException e) {
            if (checkEmpty()) {
                LOGGER.log(Level.FINE,
                    "Cleanup queue is empty, and got interrupt, will terminate the cleanup thread");
                break;
            }
            LOGGER.log(Level.FINE, "Ignoring interrupt since the cleanup queue is non-empty");
        } catch (Throwable e) {
            LOGGER.log(Level.WARNING, "Unexpected exception while executing onClean", e);
        }
    }
});
``` 
as a simplification to the try catch logic

Also had this to say

5. Missing edge cases
- What happens if ForkJoinPool.commonPool() is unavailable (custom security manager)?
- No handling for RejectedExecutionException from execute()
- The threadFactory.newThread() can return null but only logs a warning
-


^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-04 10:54  "davecramer (@davecramer)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: davecramer (@davecramer) @ 2025-12-04 10:54 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

What about creating a multi-release jar and have java 9+ code in there as well?  I expect the number of people actually using java 8 is declining.

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-04 11:51  "vlsi (@vlsi)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: vlsi (@vlsi) @ 2025-12-04 11:51 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

> What happens if ForkJoinPool.commonPool() is unavailable (custom security manager)?

Frankly, I do not think it is a real scenario.

> No handling for RejectedExecutionException from execute()

Frankly, `commonPool()` should not reject unless it shuts down.
I think we should not add workarounds for RejectedExecutionException.



^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-04 12:08  "davecramer (@davecramer)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: davecramer (@davecramer) @ 2025-12-04 12:08 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

> > What happens if ForkJoinPool.commonPool() is unavailable (custom security manager)?
> 
> Frankly, I do not think it is a real scenario.
> 
> > No handling for RejectedExecutionException from execute()
> 
> Frankly, `commonPool()` should not reject unless it shuts down. I think we should not add workarounds for RejectedExecutionException.

Fair enough. After giving this more thought I really do think we should consider leaving the code as is and providing a multi-release jar that targets java 9+. I would imagine most folks that are running into the memory leak issue are using Java 9 or greater

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-08 09:25  "vlsi (@vlsi)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: vlsi (@vlsi) @ 2025-12-08 09:25 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

I realized that with multi-release jar we can have the same class name for Java 8 and Java 11 implementations, and we do not need reflection in the runtime

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-08 14:37  "vlsi (@vlsi)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: vlsi (@vlsi) @ 2025-12-08 14:37 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

I think the code is good to go now

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2025-12-15 12:44  "davecramer (@davecramer)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: davecramer (@davecramer) @ 2025-12-15 12:44 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

sorry, I was away last week, Great work on this!

^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2026-02-17 14:22  "philipwhiuk (@philipwhiuk)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: philipwhiuk (@philipwhiuk) @ 2026-02-17 14:22 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

Switching to multi-release JAR broke my assembly build at runtime on Java 17 because it couldn't find LazyCleanerImpl until I added `<Multi-Release>true</Multi-Release>` to the assembled JAR manifest. Might be worth flagging up a bit more in the release notes (wasn't expecting a breaking change in a bugfix release).

```
Unhandled error occurred: org/postgresql/util/LazyCleanerImpl$CleanableWrapper
java.lang.NoClassDefFoundError: org/postgresql/util/LazyCleanerImpl$CleanableWrapper
        at org.postgresql.util.LazyCleanerImpl.register(LazyCleanerImpl.java:65)
        at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:403)
        at org.postgresql.Driver.makeConnection(Driver.java:448)
        at org.postgresql.Driver.connect(Driver.java:298)
        at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:137)
```



^ permalink  raw  reply  [nested|flat] 11+ messages in thread

* Re: [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext
@ 2026-02-17 14:30  "vlsi (@vlsi)" <[email protected]>
  9 siblings, 0 replies; 11+ messages in thread

From: vlsi (@vlsi) @ 2026-02-17 14:30 UTC (permalink / raw)
  To: pgjdbc/pgjdbc <[email protected]>

@philipwhiuk , could you clarify?
Do you mean you shade pgjdbc within your app?

Do you mean pgjdbc misses multi-release in jar manifest?

I just checked postgresql-42.7.10.jar, and it does seem to have `Multi-Release: true`

^ permalink  raw  reply  [nested|flat] 11+ messages in thread


end of thread, other threads:[~2026-02-17 14:30 UTC | newest]

Thread overview: 11+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-12-03 08:14 [pgjdbc/pgjdbc] PR #3886: fix: avoid memory leaks in Java <= 21 caused by Thread.inheritedAccessControlContext "vlsi (@vlsi)" <[email protected]>
2025-12-03 12:50 ` "vlsi (@vlsi)" <[email protected]>
2025-12-04 10:44 ` "davecramer (@davecramer)" <[email protected]>
2025-12-04 10:54 ` "davecramer (@davecramer)" <[email protected]>
2025-12-04 11:51 ` "vlsi (@vlsi)" <[email protected]>
2025-12-04 12:08 ` "davecramer (@davecramer)" <[email protected]>
2025-12-08 09:25 ` "vlsi (@vlsi)" <[email protected]>
2025-12-08 14:37 ` "vlsi (@vlsi)" <[email protected]>
2025-12-15 12:44 ` "davecramer (@davecramer)" <[email protected]>
2026-02-17 14:22 ` "philipwhiuk (@philipwhiuk)" <[email protected]>
2026-02-17 14:30 ` "vlsi (@vlsi)" <[email protected]>

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