Repository: movingblocks/terasology
Issue: Advanced chunk monitor prevents game from closing cleanly

### What you were trying to do

Run and close the game cleanly with `monitoringEnabled` set to true (for example to test #3006)

### What actually happened

Game windows (there are two with the monitor visible) closed, but the game didn't exit. Some thread triggered by the monitor isn't exiting on game exit

### How to reproduce

* Toggle `"monitoringEnabled": false,` to `true` in `config.cfg` (run the game once to get one)
* Run the game
* Close the game
* Protip: Trigger a thread dump in IntelliJ to inspect threads that are still alive after exiting (little camera looking icon near the bottom left, in the run window)

Changed files:
engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorDisplay.java
engine/src/main/java/org/terasology/monitoring/gui/PerformanceMonitorPanel.java
engine/src/main/java/org/terasology/engine/subsystem/common/MonitoringSubsystem.java
engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorPanel.java
engine/src/main/java/org/terasology/monitoring/gui/AdvancedMonitor.java
engine/src/main/java/org/terasology/monitoring/gui/ThreadMonitorPanel.java

diff --git a/engine/src/main/java/org/terasology/engine/subsystem/common/MonitoringSubsystem.java b/engine/src/main/java/org/terasology/engine/subsystem/common/MonitoringSubsystem.java
index 2b02119e9..04e574d6a 100644
--- a/engine/src/main/java/org/terasology/engine/subsystem/common/MonitoringSubsystem.java
+++ b/engine/src/main/java/org/terasology/engine/subsystem/common/MonitoringSubsystem.java
@@ -21,9 +21,6 @@
 import org.terasology.engine.subsystem.EngineSubsystem;
 import org.terasology.monitoring.gui.AdvancedMonitor;
 
-/**
- *
- */
 public class MonitoringSubsystem implements EngineSubsystem {
 
     private AdvancedMonitor advancedMonitor;
@@ -44,7 +41,7 @@ public void initialise(GameEngine engine, Context rootContext) {
     @Override
     public void shutdown() {
         if (advancedMonitor != null) {
-            advancedMonitor.setVisible(false);
+            advancedMonitor.close();
         }
     }
 }
diff --git a/engine/src/main/java/org/terasology/monitoring/gui/AdvancedMonitor.java b/engine/src/main/java/org/terasology/monitoring/gui/AdvancedMonitor.java
index 1b083560b..b9f0d7b3d 100644
--- a/engine/src/main/java/org/terasology/monitoring/gui/AdvancedMonitor.java
+++ b/engine/src/main/java/org/terasology/monitoring/gui/AdvancedMonitor.java
@@ -15,17 +15,16 @@
  */
 package org.terasology.monitoring.gui;
 
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JTabbedPane;
+import javax.swing.JFrame;
+import java.awt.BorderLayout;
 
 @SuppressWarnings("serial")
 public class AdvancedMonitor extends JFrame {
 
-    private final JTabbedPane tabs;
-
     private final ThreadMonitorPanel threadMonitor;
     private final ChunkMonitorPanel chunkMonitor;
-    private final PerformanceMonitorPanel perfMonitor;
+    private final PerformanceMonitorPanel performanceMonitor;
 
     public AdvancedMonitor() {
         this("Advanced Monitoring Tool", 10, 10, 800, 600);
@@ -36,7 +35,7 @@ public AdvancedMonitor(String title, int x, int y, int width, int height) {
         setBounds(x, y, width, height);
         setLayout(new BorderLayout());
 
-        tabs = new JTabbedPane();
+        JTabbedPane tabs = new JTabbedPane();
 
         threadMonitor = new ThreadMonitorPanel();
         threadMonitor.setVisible(true);
@@ -44,13 +43,30 @@ public AdvancedMonitor(String title, int x, int y, int width, int height) {
         chunkMonitor = new ChunkMonitorPanel();
         chunkMonitor.setVisible(true);
 
-        perfMonitor = new PerformanceMonitorPanel();
-        perfMonitor.setVisible(true);
+        performanceMonitor = new PerformanceMonitorPanel();
+        performanceMonitor.setVisible(true);
 
         tabs.add("Threads", threadMonitor);
         tabs.add("Chunks", chunkMonitor);
-        tabs.add("Performance", perfMonitor);
+        tabs.add("Performance", performanceMonitor);
 
         add(tabs, BorderLayout.CENTER);
     }
+
+    /**
+     * Closes advanced monitor panel.
+     *
+     * This method goes through three steps:
+     *  1. Hides this windows.
+     *  2. Releases all of the native screen resources used by this windows.
+     *  3. Stops monitoring threads gracefully.
+     */
+    public void close() {
+        setVisible(false);
+        dispose();
+
+        threadMonitor.stopThread();
+        chunkMonitor.stopThread();
+        performanceMonitor.stopThread();
+    }
 }
diff --git a/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorDisplay.java b/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorDisplay.java
index 01bae7f85..cf31f9098 100644
--- a/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorDisplay.java
+++ b/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorDisplay.java
@@ -62,16 +62,11 @@
 @SuppressWarnings("serial")
 public class ChunkMonitorDisplay extends JPanel {
 
-    public static final Color COLOR_COMPLETE = new Color(0, 38, 28);
-    public static final Color COLOR_INTERNAL_LIGHT_GENERATION_PENDING = new Color(4, 76, 41);
-    public static final Color COLOR_ADJACENCY_GENERATION_PENDING = new Color(150, 237, 137);
-
-    public static final Color COLOR_HIGHLIGHT_TESSELLATION = Color.blue.brighter().brighter();
-
-    public static final Color COLOR_SELECTED_CHUNK = new Color(255, 102, 0);
-
-    public static final Color COLOR_DEAD = Color.lightGray;
-    public static final Color COLOR_INVALID = Color.red;
+    private static final Color COLOR_COMPLETE = new Color(0, 38, 28);
+    private static final Color COLOR_INTERNAL_LIGHT_GENERATION_PENDING = new Color(4, 76, 41);
+    private static final Color COLOR_HIGHLIGHT_TESSELLATION = Color.blue.brighter().brighter();
+    private static final Color COLOR_SELECTED_CHUNK = new Color(255, 102, 0);
+    private static final Color COLOR_DEAD = Color.LIGHT_GRAY;
 
     private static final Logger logger = LoggerFactory.getLogger(ChunkMonitorDisplay.class);
 
@@ -89,13 +84,15 @@ public class ChunkMonitorDisplay extends JPanel {
     private int renderY;
     private int minRenderY;
     private int maxRenderY;
-    private boolean followPlayer = true;
+    private boolean followPlayer;
 
     private Vector3i selectedChunk;
 
-    private final BlockingQueue<Request> queue = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Request> queue;
     private final transient ExecutorService executor;
-    private final transient Runnable renderTask;
+
+    // If true, the active monitoring thread in executor service should stop.
+    private boolean stopThread;
 
     public ChunkMonitorDisplay(int refreshInterval, int chunkSize) {
         Preconditions.checkArgument(refreshInterval >= 500, "Parameter 'refreshInterval' has to be greater or equal 500 (" + refreshInterval + ")");
@@ -105,13 +102,18 @@ public ChunkMonitorDisplay(int refreshInterval, int chunkSize) {
         addMouseListener(ml);
         addMouseMotionListener(ml);
         addMouseWheelListener(ml);
+        this.followPlayer = true;
         this.refreshInterval = refreshInterval;
         this.chunkSize = chunkSize;
         this.executor = Executors.newSingleThreadExecutor();
-        this.renderTask = new RenderTask();
         ChunkMonitor.registerForEvents(this);
-        queue.offer(new InitialRequest());
-        executor.execute(renderTask);
+        this.queue = new LinkedBlockingQueue<>();
+        this.queue.offer(new InitialRequest());
+        this.executor.execute(new RenderTask());
+    }
+
+    public void stopThread() {
+        stopThread = true;
     }
 
     private void fireChunkSelectedEvent(Vector3i pos) {
@@ -165,26 +167,6 @@ private Vector3i calcPlayerChunkPos() {
         return null;
     }
 
-    public int getChunkSize() {
-        return chunkSize;
-    }
-
-    public ChunkMonitorDisplay setChunkSize(int value) {
-        if (value != chunkSize) {
-            Preconditions.checkArgument(value >= 6, "Parameter 'value' has to be greater or equal 6 (" + value + ")");
-            chunkSize = value;
-            updateDisplay(true);
-        }
-        return this;
-    }
-
-    public Vector3i getSelectedChunk() {
-        if (selectedChunk == null) {
-            return null;
-        }
-        return new Vector3i(selectedChunk);
-    }
-
     public ChunkMonitorDisplay setSelectedChunk(Vector3i chunk) {
         if (selectedChunk == null) {
             if (chunk != null) {
@@ -202,18 +184,6 @@ public ChunkMonitorDisplay setSelectedChunk(Vector3i chunk) {
         return this;
     }
 
-    public int getRenderY() {
-        return renderY;
-    }
-
-    public int getMinRenderY() {
-        return minRenderY;
-    }
-
-    public int getMaxRenderY() {
-        return maxRenderY;
-    }
-
     public ChunkMonitorDisplay setRenderY(int value) {
         int clampedValue = value;
         if (value < minRenderY) {
@@ -237,6 +207,7 @@ public boolean getFollowPlayer() {
         return followPlayer;
     }
 
+    // TODO: Add a checkbox to toggle whether the chunk display will follow where the player goes or not.
     public ChunkMonitorDisplay setFollowPlayer(boolean value) {
         if (followPlayer != value) {
             followPlayer = value;
@@ -259,24 +230,10 @@ public ChunkMonitorDisplay setOffset(int x, int y) {
             this.offsetY = y;
             updateDisplay(true);
         }
-        return this;
-    }
-
-    public int getRefreshInterval() {
-        return refreshInterval;
-    }
 
-    public ChunkMonitorDisplay setRefreshInterval(int value) {
-        Preconditions.checkArgument(value >= 500, "Parameter 'value' has to be greater or equal 500 (" + value + ")");
-        this.refreshInterval = value;
         return this;
     }
 
-    public void registerForEvents(Object object) {
-        Preconditions.checkNotNull(object, "The parameter 'object' must not be null");
-        eventbus.register(object);
-    }
-
     @Subscribe
     public void receiveChunkEvent(ChunkMonitorEvent event) {
         if (event != null) {
@@ -299,10 +256,6 @@ private static class ImageBuffer {
         private BufferedImage imageA;
         private BufferedImage imageB;
 
-        ImageBuffer(int width, int height) {
-            resize(width, height);
-        }
-
         ImageBuffer() {
         }
 
@@ -694,12 +647,12 @@ private void renderSelectedChunk(Graphics2D g, int offsetx, int offsety, Vector3
         }
 
         private void renderBox(Graphics2D g, int offsetx, int offsety, Rectangle box) {
-            g.setColor(Color.white);
+            g.setColor(Color.WHITE);
             g.drawRect(box.x * chunkSize + offsetx, box.y * chunkSize + offsety, box.width * chunkSize - 1, box.height * chunkSize - 1);
         }
 
         private void renderBackground(Graphics2D g, int width, int height) {
-            g.setColor(Color.black);
+            g.setColor(Color.BLACK);
             g.fillRect(0, 0, width, height);
         }
 
@@ -761,7 +714,7 @@ public void run() {
             final List<Request> requests = new LinkedList<>();
 
             try {
-                while (true) {
+                while (!stopThread) {
 
                     final long slept = poll(requests);
                     boolean needsRendering = false;
@@ -772,7 +725,7 @@ public void run() {
                             r.execute();
                         } catch (Exception e) {
                             ThreadMonitor.addError(e);
-                            logger.error("Thread error", e);
+                            logger.error("Error executing chunk monitor update", e);
                         } finally {
                             needsRendering |= r.needsRendering();
                             fastResume |= r.fastResume();
@@ -790,13 +743,15 @@ public void run() {
                     }
 
                     if (!fastResume && (slept <= 400)) {
-                        Thread.sleep(500 - slept);
+                        Thread.sleep(refreshInterval - slept);
                     }
                 }
             } catch (Exception e) {
                 ThreadMonitor.addError(e);
-                logger.error("Thread error", e);
+                logger.error("Error executing chunk monitor update", e);
             }
+
+            executor.shutdownNow();
         }
     }
 }
diff --git a/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorPanel.java b/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorPanel.java
index 9f88215c2..c332fcc0f 100644
--- a/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorPanel.java
+++ b/engine/src/main/java/org/terasology/monitoring/gui/ChunkMonitorPanel.java
@@ -15,8 +15,8 @@
  */
 package org.terasology.monitoring.gui;
 
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
 
 @SuppressWarnings("serial")
 public class ChunkMonitorPanel extends JPanel {
@@ -32,4 +32,7 @@ public ChunkMonitorPanel() {
         add(display, BorderLayout.CENTER);
     }
 
+    public void stopThread() {
+        display.stopThread();
+    }
 }
diff --git a/engine/src/main/java/org/terasology/monitoring/gui/PerformanceMonitorPanel.java b/engine/src/main/java/org/terasology/monitoring/gui/PerformanceMonitorPanel.java
index 591a037e0..50dfc365b 100644
--- a/engine/src/main/java/org/terasology/monitoring/gui/PerformanceMonitorPanel.java
+++ b/engine/src/main/java/org/terasology/monitoring/gui/PerformanceMonitorPanel.java
@@ -23,8 +23,18 @@
 import org.terasology.monitoring.ThreadActivity;
 import org.terasology.monitoring.ThreadMonitor;
 
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JPanel;
+import javax.swing.JList;
+import javax.swing.JLabel;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+import javax.swing.AbstractListModel;
+import javax.swing.SwingConstants;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Dimension;
+import java.awt.Component;
+import java.awt.Color;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -37,22 +47,25 @@
 
 @SuppressWarnings("serial")
 public class PerformanceMonitorPanel extends JPanel {
-    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorPanel.class);
-
-    private final HeaderPanel header;
-    private final JList list;
+    private static final Logger LOGGER = LoggerFactory.getLogger(PerformanceMonitorPanel.class);
 
+    // If true, the active monitoring thread in executor service should stop.
+    private boolean stopThread;
 
     public PerformanceMonitorPanel() {
         setLayout(new BorderLayout());
-        header = new HeaderPanel();
-        list = new JList(new PerformanceListModel());
+        HeaderPanel header = new HeaderPanel();
+        JList list = new JList(new PerformanceListModel());
         list.setCellRenderer(new PerformanceListRenderer(header));
         list.setVisible(true);
         add(header, BorderLayout.PAGE_START);
         add(list, BorderLayout.CENTER);
     }
 
+    public void stopThread() {
+        stopThread = true;
+    }
+
     private static class HeaderPanel extends JPanel {
 
         private final JLabel lName = new JLabel("Title");
@@ -137,15 +150,15 @@ private static class MyRenderer extends JPanel {
             MyRenderer(HeaderPanel header) {
                 this.header = Preconditions.checkNotNull(header, "The parameter 'header' must not be null");
 
-                setBackground(Color.white);
+                setBackground(Color.WHITE);
                 setLayout(new FlowLayout(FlowLayout.LEFT, 4, 2));
 
                 lMean.setHorizontalAlignment(SwingConstants.RIGHT);
-                lMean.setForeground(Color.gray);
+                lMean.setForeground(Color.GRAY);
                 lMean.setPreferredSize(header.lMean.getPreferredSize());
 
                 lSpike.setHorizontalAlignment(SwingConstants.RIGHT);
-                lSpike.setForeground(Color.gray);
+                lSpike.setForeground(Color.GRAY);
                 lSpike.setPreferredSize(header.lSpike.getPreferredSize());
 
                 add(lName);
@@ -156,7 +169,7 @@ private static class MyRenderer extends JPanel {
             public void setEntry(Entry entry) {
                 if (entry != null) {
                     lName.setPreferredSize(null);
-                    lName.setForeground(entry.active ? Color.blue : Color.gray);
+                    lName.setForeground(entry.active ? Color.BLUE : Color.GRAY);
                     lName.setText(entry.name);
                     Dimension tmp = lName.getPreferredSize();
                     if (tmp.width > dName.width || tmp.height > dName.height) {
@@ -176,7 +189,7 @@ public void setEntry(Entry entry) {
         }
     }
 
-    private static final class PerformanceListModel extends AbstractListModel {
+    private final class PerformanceListModel extends AbstractListModel {
 
         private final List<Entry> list = new ArrayList<>();
         private final Map<String, Entry> map = new HashMap<>();
@@ -186,7 +199,7 @@ private PerformanceListModel() {
             executor.execute(() -> {
                 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
                 try {
-                    while (true) {
+                    while (!stopThread) {
                         Thread.sleep(1000);
                         try (ThreadActivity ignored = ThreadMonitor.startThreadActivity("Poll")) {
                             updateEntries(PerformanceMonitor.getRunningMean(), PerformanceMonitor.getDecayingSpikes());
@@ -194,8 +207,10 @@ private PerformanceListModel() {
                     }
                 } catch (Exception e) {
                     ThreadMonitor.addError(e);
-                    logger.error("Error executing performance monitor update", e);
+                    LOGGER.error("Error executing performance monitor update", e);
                 }
+
+                executor.shutdownNow();
             });
         }
 
@@ -204,11 +219,6 @@ private void invokeIntervalAdded(final int a, final int b) {
             SwingUtilities.invokeLater(() -> fireIntervalAdded(source, a, b));
         }
 
-        private void invokeIntervalRemoved(final int a, final int b) {
-            final Object source = this;
-            SwingUtilities.invokeLater(() -> fireIntervalRemoved(source, a, b));
-        }
-
         private void invokeContentsChanged(final int a, final int b) {
             final Object source = this;
             SwingUtilities.invokeLater(() -> fireContentsChanged(source, a, b));
@@ -238,6 +248,7 @@ private void updateEntries(TObjectDoubleMap<String> means, TObjectDoubleMap<Stri
                     }
                     return true;
                 });
+
                 Collections.sort(list);
                 invokeContentsChanged(0, list.size() - 1);
             }
diff --git a/engine/src/main/java/org/terasology/monitoring/gui/ThreadMonitorPanel.java b/engine/src/main/java/org/terasology/monitoring/gui/ThreadMonitorPanel.java
index 78b20fed5..49b7eff4e 100644
--- a/engine/src/main/java/org/terasology/monitoring/gui/ThreadMonitorPanel.java
+++ b/engine/src/main/java/org/terasology/monitoring/gui/ThreadMonitorPanel.java
@@ -23,8 +23,18 @@
 import org.terasology.monitoring.impl.SingleThreadMonitor;
 import org.terasology.monitoring.impl.ThreadMonitorEvent;
 
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JPanel;
+import javax.swing.JList;
+import javax.swing.JLabel;
+import javax.swing.ListCellRenderer;
+import javax.swing.SwingUtilities;
+import javax.swing.AbstractListModel;
+import javax.swing.SwingConstants;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Dimension;
+import java.awt.Component;
+import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.concurrent.BlockingQueue;
@@ -36,24 +46,31 @@
 @SuppressWarnings("serial")
 public class ThreadMonitorPanel extends JPanel {
 
-    private static final Color BACKGROUND = Color.white;
-    private static final Logger logger = LoggerFactory.getLogger(ThreadMonitorPanel.class);
+    private static final Color BACKGROUND = Color.WHITE;
+    private static final Logger LOGGER = LoggerFactory.getLogger(ThreadMonitorPanel.class);
 
-    private final JList list;
+    /**
+     * If true, the active monitoring thread in executor service should stop.
+     */
+    private boolean stopThread;
 
     public ThreadMonitorPanel() {
         setLayout(new BorderLayout());
-        list = new JList(new ThreadListModel());
+        JList list = new JList(new ThreadListModel());
         list.setCellRenderer(new ThreadListRenderer());
         list.setVisible(true);
         add(list, BorderLayout.CENTER);
     }
 
+    public void stopThread() {
+        this.stopThread = true;
+    }
+
     private abstract static class Task {
 
         private String name;
 
-         Task(String name) {
+        Task(String name) {
             this.name = name;
         }
 
@@ -94,7 +111,7 @@ private static class MyRenderer extends JPanel {
             private Dimension dId = new Dimension(0, 0);
             private Dimension dName = new Dimension(0, 0);
 
-             MyRenderer() {
+            MyRenderer() {
                 setBackground(BACKGROUND);
                 setLayout(new BorderLayout());
 
@@ -105,8 +122,8 @@ private static class MyRenderer extends JPanel {
                 pHead.add(pError, BorderLayout.PAGE_END);
 
                 lId.setHorizontalAlignment(SwingConstants.RIGHT);
-                lName.setForeground(Color.blue);
-                lCounters.setForeground(Color.gray);
+                lName.setForeground(Color.BLUE);
+                lCounters.setForeground(Color.GRAY);
 
                 pList.setLayout(new FlowLayout(FlowLayout.LEFT, 4, 2));
                 pList.setBackground(BACKGROUND);
@@ -120,7 +137,7 @@ private static class MyRenderer extends JPanel {
                 pError.add(lErrorSpacer);
                 pError.add(lError);
 
-                lError.setForeground(Color.red);
+                lError.setForeground(Color.RED);
 
                 add(pHead, BorderLayout.PAGE_START);
             }
@@ -148,14 +165,14 @@ public void setMonitor(SingleThreadMonitor monitor) {
 
                     if (monitor.isAlive()) {
                         if (monitor.isActive()) {
-                            lActive.setForeground(Color.green);
+                            lActive.setForeground(Color.GREEN);
                             lActive.setText("Active");
                         } else {
-                            lActive.setForeground(Color.gray);
+                            lActive.setForeground(Color.GRAY);
                             lActive.setText("Inactive");
                         }
                     } else {
-                        lActive.setForeground(Color.red);
+                        lActive.setForeground(Color.RED);
                         lActive.setText("Disposed");
                     }
 
@@ -175,7 +192,7 @@ public void setMonitor(SingleThreadMonitor monitor) {
         }
     }
 
-    private static final class ThreadListModel extends AbstractListModel {
+    private final class ThreadListModel extends AbstractListModel {
 
         private final java.util.List<SingleThreadMonitor> monitors = new ArrayList<>();
         private final ExecutorService executor = Executors.newSingleThreadExecutor();
@@ -193,30 +210,29 @@ public void execute() {
                     }
                 }
             });
-            executor.execute(new Runnable() {
-                @Override
-                public void run() {
-                    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
-                    try {
-                        while (true) {
-                            final Task task = queue.poll(500, TimeUnit.MILLISECONDS);
-                            if (task != null) {
-                                try (ThreadActivity ignored = ThreadMonitor.startThreadActivity(task.getName())) {
-                                    task.execute();
-                                }
-                            } else {
-                                try (ThreadActivity ignored = ThreadMonitor.startThreadActivity("Sort Monitors")) {
-                                    Collections.sort(monitors);
-                                    invokeContentsChanged(0, monitors.size() - 1);
-                                }
+
+            executor.execute(() -> {
+                Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+                try {
+                    while (!stopThread) {
+                        final Task task = queue.poll(500, TimeUnit.MILLISECONDS);
+                        if (task != null) {
+                            try (ThreadActivity ignored = ThreadMonitor.startThreadActivity(task.getName())) {
+                                task.execute();
+                            }
+                        } else {
+                            try (ThreadActivity ignored = ThreadMonitor.startThreadActivity("Sort Monitors")) {
+                                Collections.sort(monitors);
+                                invokeContentsChanged(0, monitors.size() - 1);
                             }
                         }
-                    } catch (Exception e) {
-                        ThreadMonitor.addError(e);
-                        logger.error("Error executing thread monitor update", e);
                     }
-                    invokeContentsChanged(0, monitors.size() - 1);
+                } catch (Exception e) {
+                    ThreadMonitor.addError(e);
+                    LOGGER.error("Error executing thread monitor update", e);
                 }
+
+                executor.shutdownNow();
             });
         }
 
@@ -225,11 +241,6 @@ private void invokeIntervalAdded(final int a, final int b) {
             SwingUtilities.invokeLater(() -> fireIntervalAdded(source, a, b));
         }
 
-        private void invokeIntervalRemoved(final int a, final int b) {
-            final Object source = this;
-            SwingUtilities.invokeLater(() -> fireIntervalRemoved(source, a, b));
-        }
-
         private void invokeContentsChanged(final int a, final int b) {
             final Object source = this;
             SwingUtilities.invokeLater(() -> fireContentsChanged(source, a, b));