mabe02 / lanterna

Java library for creating text-based GUIs
GNU Lesser General Public License v3.0
2.26k stars 242 forks source link

AnimatedLabel Thread hangs after WaitingDialog closed #595

Open Kapral67 opened 7 months ago

Kapral67 commented 7 months ago

It is possible for the AnimatedLabel::createSpinningLine component's thread to hang after the WaitingDialog that owns it is closed.

A quick fix could be to explicitly call AnimatedLabel::stopAnimation in WaitingDIalog::close (the spinning line would need to become an instance variable of the WaitingDialog class).

Although, I think a better solution would be some fix within the AnimatedLabel class itself.

Proof of Concept:

final DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory();
try (final Screen screen = terminalFactory.createScreen()) {
    screen.startScreen();

    // POC
    final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen);
    final WaitingDialog waitingDialog = WaitingDialog.createDialog("TITLE", "TEXT");
    waitingDialog.showDialog(textGUI, false);
    CompletableFuture.runAsync(() -> {
                         try {
                             Thread.sleep(5000);
                         } catch (final InterruptedException e) {
                             Thread.currentThread().interrupt();
                             throw new RuntimeException(e);
                         } finally {
                             waitingDialog.close();
                         }
                     }, Executors.newSingleThreadExecutor())
                     .exceptionally(e -> {
                         throw new RuntimeException(e);
                     });
    waitingDialog.waitUntilClosed();
    System.out.println("WAIT DIALOG CLOSED");

} catch (final IOException e) {
    throw new RuntimeException(e);
}
mabe02 commented 7 months ago

Interesting, looks like the WeakReference that's supposed to shut down the thread doesn't trigger until you force GC after the window is closed. We should probably improve on that.

avl42 commented 7 months ago

Probably, the thread just ought to check validity of the objects it tries to deal with... and stop itself, if its prerequisites aren't met.

One should NEVER let program logic depend on actual GC runs.

On Tue, Feb 13, 2024 at 2:41 PM mabe02 @.***> wrote:

Interesting, looks like the WeakReference that's supposed to shut down the thread doesn't trigger until you force GC after the window is closed. We should probably improve on that.

— Reply to this email directly, view it on GitHub https://github.com/mabe02/lanterna/issues/595#issuecomment-1941547386, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIDBMW7F5W6RVDFQS4J4XLYTNUSNAVCNFSM6AAAAABDDY3SQWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNBRGU2DOMZYGY . You are receiving this because you are subscribed to this thread.Message ID: @.***>

mabe02 commented 7 months ago

I think this will fix it: https://github.com/mabe02/lanterna/pull/596

TheFunnyBear commented 4 months ago

Workaround for me:


        private fun waitDialog(
            textGUI: WindowBasedTextGUI,
            action: Runnable
        ) {
            val waitDialog = WaitingDialog.createDialog("TETST", "TEST MESSAGE")

            runBlocking {
                val job = CoroutineScope(Dispatchers.IO).launch {
                    action.run()
                    waitDialog.setCloseWindowWithEscape(true)
                    val robot = Robot()
                    robot.keyPress(KeyEvent.VK_ESCAPE)
                    robot.keyRelease(KeyEvent.VK_ESCAPE)
                }
                waitDialog.showDialog(textGUI, true)

                job.join()
            }
        }
    }