From 35848e09a83a0bd74e7dae1d2ddeee3b6b2d9ddf Mon Sep 17 00:00:00 2001 From: Cristian Maglie Date: Wed, 10 Dec 2014 10:26:02 +0100 Subject: [PATCH] Mitigated Serial Monitor resource exhaustion when the connected device sends a lot of data Fixes #2233 --- app/src/processing/app/SerialMonitor.java | 52 +++++++++---- .../processing/app/debug/TextAreaFIFO.java | 78 +++++++++++++++++++ build/shared/revisions.txt | 3 + 3 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 app/src/processing/app/debug/TextAreaFIFO.java diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java index 1f34e8f7e..10a23331e 100644 --- a/app/src/processing/app/SerialMonitor.java +++ b/app/src/processing/app/SerialMonitor.java @@ -19,6 +19,7 @@ package processing.app; import processing.app.debug.MessageConsumer; +import processing.app.debug.TextAreaFIFO; import processing.core.*; import static processing.app.I18n._; @@ -26,13 +27,12 @@ import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; -import javax.swing.event.*; import javax.swing.text.*; -public class SerialMonitor extends JFrame implements MessageConsumer { +public class SerialMonitor extends JFrame implements MessageConsumer,ActionListener { private Serial serial; private String port; - private JTextArea textArea; + private TextAreaFIFO textArea; private JScrollPane scrollPane; private JTextField textField; private JButton sendButton; @@ -40,6 +40,8 @@ public class SerialMonitor extends JFrame implements MessageConsumer { private JComboBox lineEndings; private JComboBox serialRates; private int serialRate; + private javax.swing.Timer updateTimer; + private StringBuffer updateBuffer; public SerialMonitor(String port) { super(port); @@ -67,7 +69,9 @@ public class SerialMonitor extends JFrame implements MessageConsumer { Font editorFont = Preferences.getFont("editor.font"); Font font = new Font(consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize()); - textArea = new JTextArea(16, 40); + textArea = new TextAreaFIFO(4000000); + textArea.setRows(16); + textArea.setColumns(40); textArea.setEditable(false); textArea.setFont(font); @@ -171,6 +175,9 @@ public class SerialMonitor extends JFrame implements MessageConsumer { } } } + + updateBuffer = new StringBuffer(1048576); + updateTimer = new javax.swing.Timer(33, this); // redraw serial monitor at 30 Hz } protected void setPlacement(int[] location) { @@ -203,9 +210,9 @@ public class SerialMonitor extends JFrame implements MessageConsumer { public void openSerialPort() throws SerialException { if (serial != null) return; - serial = new Serial(port, serialRate); serial.addListener(this); + updateTimer.start(); } public void closeSerialPort() { @@ -219,13 +226,32 @@ public class SerialMonitor extends JFrame implements MessageConsumer { } } - public void message(final String s) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - textArea.append(s); - if (autoscrollBox.isSelected()) { - textArea.setCaretPosition(textArea.getDocument().getLength()); - } - }}); + public void message(String s) { + // TODO: can we pass a byte array, to avoid overhead of String + addToUpdateBuffer(s); } + + private synchronized void addToUpdateBuffer(String s) { + updateBuffer.append(s); + } + + private synchronized String consumeUpdateBuffer() { + String s = updateBuffer.toString(); + updateBuffer.setLength(0); + return s; + } + + public void actionPerformed(ActionEvent e) { + final String s = consumeUpdateBuffer(); + if (s.length() > 0) { + //System.out.println("gui append " + s.length()); + boolean scroll = autoscrollBox.isSelected(); + textArea.allowTrim(scroll); + textArea.append(s); + if (scroll) { + textArea.setCaretPosition(textArea.getDocument().getLength()); + } + } + } + } diff --git a/app/src/processing/app/debug/TextAreaFIFO.java b/app/src/processing/app/debug/TextAreaFIFO.java new file mode 100644 index 000000000..9c5dc8612 --- /dev/null +++ b/app/src/processing/app/debug/TextAreaFIFO.java @@ -0,0 +1,78 @@ +/* + Copyright (c) 2014 Paul Stoffregen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + $Id$ +*/ + +// adapted from https://community.oracle.com/thread/1479784 + +package processing.app.debug; + +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; + +public class TextAreaFIFO extends JTextArea implements DocumentListener { + private int maxChars; + + private int updateCount; // limit how often we trim the document + + private boolean doTrim; + + public TextAreaFIFO(int max) { + maxChars = max; + updateCount = 0; + doTrim = true; + getDocument().addDocumentListener(this); + } + + public void allowTrim(boolean trim) { + doTrim = trim; + } + + public void insertUpdate(DocumentEvent e) { + if (++updateCount > 150 && doTrim) { + updateCount = 0; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + trimDocument(); + } + }); + } + } + + public void removeUpdate(DocumentEvent e) { + } + + public void changedUpdate(DocumentEvent e) { + } + + public void trimDocument() { + int len = 0; + len = getDocument().getLength(); + if (len > maxChars) { + int n = len - maxChars; + System.out.println("trimDocument: remove " + n + " chars"); + try { + getDocument().remove(0, n); + } catch (BadLocationException ble) { + } + } + } +} diff --git a/build/shared/revisions.txt b/build/shared/revisions.txt index e960fd372..c55aec50a 100644 --- a/build/shared/revisions.txt +++ b/build/shared/revisions.txt @@ -13,6 +13,9 @@ ARDUINO 1.0.7 * Fixed missing NOT_AN_INTERRUPT constant in digitalPinToInterrupt() macro * Fixed performance regression in HardwareSerial::available() introduced with https://github.com/arduino/Arduino/pull/2057 +[ide] +* Mitigated Serial Monitor resource exhaustion when the connected device sends a lot of data (Paul Stoffregen) + ARDUINO 1.0.6 - 2014.09.16 [core]