From ad5dece9cfed35132a1947fe08fec5ebe83aaf51 Mon Sep 17 00:00:00 2001 From: Martin Berglund Date: Tue, 20 Feb 2024 22:40:41 +0900 Subject: [PATCH 1/3] Issue 595: Avoid relying on WeakRef --- .../lanterna/gui2/AnimatedLabel.java | 5 +- .../googlecode/lanterna/issue/Issue595.java | 63 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/googlecode/lanterna/issue/Issue595.java diff --git a/src/main/java/com/googlecode/lanterna/gui2/AnimatedLabel.java b/src/main/java/com/googlecode/lanterna/gui2/AnimatedLabel.java index 31bbdd37b..8e1590cad 100644 --- a/src/main/java/com/googlecode/lanterna/gui2/AnimatedLabel.java +++ b/src/main/java/com/googlecode/lanterna/gui2/AnimatedLabel.java @@ -164,9 +164,8 @@ private AnimationTimerTask(AnimatedLabel label) { @Override public void run() { AnimatedLabel animatedLabel = labelRef.get(); - if(animatedLabel == null) { - cancel(); - canCloseTimer(); + if(animatedLabel == null || animatedLabel.getTextGUI() == null) { + removeTaskFromTimer(animatedLabel); } else { if(animatedLabel.getBasePane() == null) { diff --git a/src/test/java/com/googlecode/lanterna/issue/Issue595.java b/src/test/java/com/googlecode/lanterna/issue/Issue595.java new file mode 100644 index 000000000..93c3b7a59 --- /dev/null +++ b/src/test/java/com/googlecode/lanterna/issue/Issue595.java @@ -0,0 +1,63 @@ +/* + * This file is part of lanterna (https://github.com/mabe02/lanterna). + * + * lanterna is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Copyright (C) 2010-2020 Martin Berglund + */ +package com.googlecode.lanterna.issue; + +import com.googlecode.lanterna.gui2.MultiWindowTextGUI; +import com.googlecode.lanterna.gui2.WindowBasedTextGUI; +import com.googlecode.lanterna.gui2.dialogs.WaitingDialog; +import com.googlecode.lanterna.screen.Screen; +import com.googlecode.lanterna.terminal.DefaultTerminalFactory; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class Issue595 { + public static void main(String... args) throws IOException { + final DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory(); + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + 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(); + } + }, executorService) + .exceptionally(e -> { + throw new RuntimeException(e); + }); + waitingDialog.waitUntilClosed(); + System.out.println("WAIT DIALOG CLOSED"); + } catch (final IOException e) { + throw new RuntimeException(e); + } + executorService.shutdown(); + } +} From f2a40041a834caaf58453d3faf377eec15410c1f Mon Sep 17 00:00:00 2001 From: Maxwell Kapral Date: Tue, 20 Feb 2024 10:22:41 -0800 Subject: [PATCH 2/3] Prevent Test From Hanging With AnimatedLabel Thread --- .../googlecode/lanterna/issue/Issue595.java | 85 ++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/src/test/java/com/googlecode/lanterna/issue/Issue595.java b/src/test/java/com/googlecode/lanterna/issue/Issue595.java index 93c3b7a59..695308345 100644 --- a/src/test/java/com/googlecode/lanterna/issue/Issue595.java +++ b/src/test/java/com/googlecode/lanterna/issue/Issue595.java @@ -18,6 +18,7 @@ */ package com.googlecode.lanterna.issue; +import com.googlecode.lanterna.gui2.AnimatedLabel; import com.googlecode.lanterna.gui2.MultiWindowTextGUI; import com.googlecode.lanterna.gui2.WindowBasedTextGUI; import com.googlecode.lanterna.gui2.dialogs.WaitingDialog; @@ -25,39 +26,83 @@ import com.googlecode.lanterna.terminal.DefaultTerminalFactory; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; -public class Issue595 { - public static void main(String... args) throws IOException { +public +class Issue595 { + private static final int SLEEP_SECONDS = 5; + private static final long SLEEP_MILLIS = SLEEP_SECONDS * 1000L; + + public static + void main (String... args) throws IOException { + int exit_code = 0; final DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory(); - final ExecutorService executorService = Executors.newSingleThreadExecutor(); try (final Screen screen = terminalFactory.createScreen()) { screen.startScreen(); + final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen); // POC - final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen); final WaitingDialog waitingDialog = WaitingDialog.createDialog("TITLE", "TEXT"); + final ExecutorService executorService = Executors.newSingleThreadExecutor(); waitingDialog.showDialog(textGUI, false); CompletableFuture.runAsync(() -> { - try { - Thread.sleep(5000); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException(e); - } finally { - waitingDialog.close(); - } - }, executorService) - .exceptionally(e -> { - throw new RuntimeException(e); - }); + try { + Thread.sleep(SLEEP_MILLIS); + } catch (final InterruptedException e) { + Thread.currentThread() + .interrupt(); + throw new RuntimeException(e); + } finally { + waitingDialog.close(); + } + }, executorService) + .exceptionally(e -> { + throw new RuntimeException(e); + }); waitingDialog.waitUntilClosed(); - System.out.println("WAIT DIALOG CLOSED"); - } catch (final IOException e) { - throw new RuntimeException(e); + + // Ensure Executor Thread Dead + executorService.shutdownNow(); + if (!executorService.awaitTermination(SLEEP_SECONDS, TimeUnit.SECONDS)) { + throw new IllegalStateException("ExecutorService Unstoppable"); + } + + // Check for Animated Label Hanging Thread + final String animatedLabelName = AnimatedLabel.class.getSimpleName() + .toLowerCase(); + final Optional optionalAnimatedLabelThread = Thread.getAllStackTraces() + .keySet() + .stream() + .filter(thread -> thread.getName() + .toLowerCase() + .contains(animatedLabelName)) + .findAny(); + if (!optionalAnimatedLabelThread.isPresent()) { + return; + } + final Thread thread = optionalAnimatedLabelThread.get(); + thread.join(SLEEP_MILLIS); + if (thread.isAlive()) { + throw new IllegalStateException("AnimatedLabel Thread Waiting"); + } + + } catch (final Throwable e) { + if (e instanceof InterruptedException) { + Thread.currentThread() + .interrupt(); + } + ++exit_code; + final StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + System.err.print(stringWriter); + } finally { + System.exit(exit_code); } - executorService.shutdown(); } } From b10e866dd12c34eb14514201b6d2cbab90f293d7 Mon Sep 17 00:00:00 2001 From: Maxwell Kapral Date: Tue, 20 Feb 2024 12:22:47 -0800 Subject: [PATCH 3/3] highlight race condition --- src/test/java/com/googlecode/lanterna/issue/Issue595.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/googlecode/lanterna/issue/Issue595.java b/src/test/java/com/googlecode/lanterna/issue/Issue595.java index 695308345..5ff0698e6 100644 --- a/src/test/java/com/googlecode/lanterna/issue/Issue595.java +++ b/src/test/java/com/googlecode/lanterna/issue/Issue595.java @@ -50,6 +50,7 @@ void main (String... args) throws IOException { // POC final WaitingDialog waitingDialog = WaitingDialog.createDialog("TITLE", "TEXT"); final ExecutorService executorService = Executors.newSingleThreadExecutor(); + Thread.sleep(SLEEP_MILLIS); waitingDialog.showDialog(textGUI, false); CompletableFuture.runAsync(() -> { try {