Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExternalTerminal#close does not stop the pump thread #1183

Open
oehme opened this issue Apr 1, 2025 · 6 comments
Open

ExternalTerminal#close does not stop the pump thread #1183

oehme opened this issue Apr 1, 2025 · 6 comments
Milestone

Comments

@oehme
Copy link

oehme commented Apr 1, 2025

This means that the thread's context classloader is leaked and eventually leads to OutOfMemoryErrors, e.g. when repeatedly running Maven 4 in embedded mode.

@oehme
Copy link
Author

oehme commented Apr 1, 2025

Here's what a heap dump from one of our CI agents looks like:
Image

Following the code it looks like it should stop eventually, since "paused" is set to true. But this doesn't seem to happen reliably, as I see over a dozen leftover ExecTerminal pump threads in that heap dump.

@oehme
Copy link
Author

oehme commented Apr 1, 2025

Digging deeper into the stacktrace where these threads are stuck, it's

java stack of ExternalTerminal[name='Maven', type='ansi', size='Size[cols=160, rows=50]'] input pump thread
    at java.util.concurrent.locks.LockSupport.park(java.lang.Object) (line: 221)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.util.concurrent.locks.AbstractQueuedSynchronizer$Node, int, boolean, boolean, boolean, long) (line: 754)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(int) (line: 990)
    at java.util.concurrent.locks.ReentrantLock$Sync.lock() (line: 153)
    at java.util.concurrent.locks.ReentrantLock.lock() (line: 322)
    at jdk.internal.misc.InternalLock.lock() (line: 74)
    at java.io.BufferedInputStream.read(byte[ ], int, int) (line: 397)
    at java.io.FilterInputStream.read(byte[ ]) (line: 95)
    at org.jline.terminal.impl.ExternalTerminal.pump() (line: 156)
    at org.jline.terminal.impl.ExternalTerminal$$Lambda+0x0000007002013e00.run()
    at java.lang.Thread.runWith(java.lang.Object, java.lang.Runnable) (line: 1596)
    at java.lang.Thread.run() (line: 1583)

So they are waiting for input indefinitely and never reach the if-statement further down where they check whether they are paused.

@gnodet
Copy link
Member

gnodet commented Apr 1, 2025

There are two pause methods on ExternalTerminal. The doClose method seems to call pause() instead of pause(true) which would interrupt the reading thread.

@gnodet gnodet added this to the 3.29.1 milestone Apr 1, 2025
@gnodet
Copy link
Member

gnodet commented Apr 1, 2025

Seems similar to #1162

@oehme
Copy link
Author

oehme commented Apr 1, 2025

InputStream#read can't be interrupted, so that unfortunately won't fix it. You could busy-wait on input.available >0 instead and then only read as much as is available. That would ensure you never block.

@oehme
Copy link
Author

oehme commented Apr 1, 2025

I managed to work around this in our Maven tests by replacing System.in with a ByteArrayInputStream for each test. That way, the pump thread gets an EOF and shuts down.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants