diff --git a/app/src/processing/app/AbstractMonitor.java b/app/src/processing/app/AbstractMonitor.java index 027601c57..e15791d9e 100644 --- a/app/src/processing/app/AbstractMonitor.java +++ b/app/src/processing/app/AbstractMonitor.java @@ -1,24 +1,43 @@ package processing.app; -import processing.app.debug.MessageConsumer; -import processing.app.legacy.PApplet; +import static processing.app.I18n._; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.text.DefaultCaret; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import static processing.app.I18n._; +import javax.swing.AbstractAction; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.border.EmptyBorder; +import javax.swing.text.DefaultCaret; + +import processing.app.debug.TextAreaFIFO; +import processing.app.legacy.PApplet; @SuppressWarnings("serial") -public abstract class AbstractMonitor extends JFrame implements MessageConsumer { +public abstract class AbstractMonitor extends JFrame implements ActionListener { protected final JLabel noLineEndingAlert; - protected JTextArea textArea; + protected TextAreaFIFO textArea; protected JScrollPane scrollPane; protected JTextField textField; protected JButton sendButton; @@ -26,6 +45,9 @@ public abstract class AbstractMonitor extends JFrame implements MessageConsumer protected JComboBox lineEndings; protected JComboBox serialRates; + private Timer updateTimer; + private StringBuffer updateBuffer; + public AbstractMonitor(String title) { super(title); @@ -59,7 +81,9 @@ public abstract class AbstractMonitor 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(8000000); + textArea.setRows(16); + textArea.setColumns(40); textArea.setEditable(false); textArea.setFont(font); @@ -149,6 +173,10 @@ public abstract class AbstractMonitor extends JFrame implements MessageConsumer } } } + + updateBuffer = new StringBuffer(1048576); + updateTimer = new Timer(33, this); // redraw serial monitor at 30 Hz + updateTimer.start(); } public void onSerialRateChange(ActionListener listener) { @@ -199,4 +227,28 @@ public abstract class AbstractMonitor extends JFrame implements MessageConsumer public abstract void open() throws Exception; public abstract void close() throws Exception; + + public synchronized void addToUpdateBuffer(char buff[], int n) { + updateBuffer.append(buff, 0, n); + } + + 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()); + if (autoscrollBox.isSelected()) { + textArea.appendTrim(s); + textArea.setCaretPosition(textArea.getDocument().getLength()); + } else { + textArea.appendNoTrim(s); + } + } + } + } diff --git a/app/src/processing/app/NetworkMonitor.java b/app/src/processing/app/NetworkMonitor.java index 9b82f1898..850481ef7 100644 --- a/app/src/processing/app/NetworkMonitor.java +++ b/app/src/processing/app/NetworkMonitor.java @@ -5,10 +5,14 @@ import cc.arduino.packages.ssh.NoInteractionUserInfo; import cc.arduino.packages.ssh.SSHClientSetupChainRing; import cc.arduino.packages.ssh.SSHConfigFileSetup; import cc.arduino.packages.ssh.SSHPwdSetup; + import com.jcraft.jsch.*; + +import processing.app.debug.MessageConsumer; import processing.app.debug.MessageSiphon; import javax.swing.*; + import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; @@ -18,7 +22,7 @@ import java.io.OutputStream; import static processing.app.I18n._; @SuppressWarnings("serial") -public class NetworkMonitor extends AbstractMonitor { +public class NetworkMonitor extends AbstractMonitor implements MessageConsumer { private static final int MAX_CONNECTION_ATTEMPTS = 5; diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java index 67e90d5bb..122e3323d 100644 --- a/app/src/processing/app/SerialMonitor.java +++ b/app/src/processing/app/SerialMonitor.java @@ -91,8 +91,12 @@ public class SerialMonitor extends AbstractMonitor { public void open() throws Exception { if (serial != null) return; - serial = new Serial(port, serialRate); - serial.addListener(this); + serial = new Serial(port, serialRate) { + @Override + protected void message(char buff[], int n) { + addToUpdateBuffer(buff, n); + } + }; } public void close() throws Exception { @@ -105,4 +109,5 @@ public class SerialMonitor extends AbstractMonitor { serial = null; } } + } diff --git a/app/src/processing/app/debug/TextAreaFIFO.java b/app/src/processing/app/debug/TextAreaFIFO.java new file mode 100644 index 000000000..9783cd42c --- /dev/null +++ b/app/src/processing/app/debug/TextAreaFIFO.java @@ -0,0 +1,92 @@ +/* + 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 trimMaxChars; + + private int updateCount; // limit how often we trim the document + + private boolean doTrim; + + public TextAreaFIFO(int max) { + maxChars = max; + trimMaxChars = max / 2; + updateCount = 0; + doTrim = true; + getDocument().addDocumentListener(this); + } + + 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 > trimMaxChars) { + int n = len - trimMaxChars; + //System.out.println("trimDocument: remove " + n + " chars"); + try { + getDocument().remove(0, n); + } catch (BadLocationException ble) { + } + } + } + + public void appendNoTrim(String s) { + int free = maxChars - getDocument().getLength(); + if (free <= 0) + return; + if (s.length() > free) + append(s.substring(0, free)); + else + append(s); + doTrim = false; + } + + public void appendTrim(String str) { + append(str); + doTrim = true; + } +} diff --git a/arduino-core/src/processing/app/Serial.java b/arduino-core/src/processing/app/Serial.java index fdff1d575..9860d253c 100644 --- a/arduino-core/src/processing/app/Serial.java +++ b/arduino-core/src/processing/app/Serial.java @@ -34,7 +34,6 @@ import jssc.SerialPort; import jssc.SerialPortEvent; import jssc.SerialPortEventListener; import jssc.SerialPortException; -import processing.app.debug.MessageConsumer; public class Serial implements SerialPortEventListener { @@ -60,8 +59,6 @@ public class Serial implements SerialPortEventListener { int bufferIndex; int bufferLast; - MessageConsumer consumer; - public Serial(boolean monitor) throws SerialException { this(PreferencesData.get("serial.port"), PreferencesData.getInteger("serial.debug_rate"), @@ -168,10 +165,6 @@ public class Serial implements SerialPortEventListener { } } - public void addListener(MessageConsumer consumer) { - this.consumer = consumer; - } - public synchronized void serialEvent(SerialPortEvent serialEvent) { if (serialEvent.isRXCHAR()) { try { @@ -182,12 +175,12 @@ public class Serial implements SerialPortEventListener { System.arraycopy(buffer, 0, temp, 0, bufferLast); buffer = temp; } + String msg = new String(buf); if (monitor) { - System.out.print(new String(buf)); - } - if (this.consumer != null) { - this.consumer.message(new String(buf)); + System.out.print(msg); } + char[] chars = msg.toCharArray(); + message(chars, chars.length); } } catch (SerialPortException e) { errorMessage("serialEvent", e); @@ -195,6 +188,16 @@ public class Serial implements SerialPortEventListener { } } + /** + * This method is intented to be extended to receive messages + * coming from serial port. + * + * @param chars + * @param length + */ + protected void message(char[] chars, int length) { + // Empty + } /** * Returns the number of bytes that have been read from serial diff --git a/build/shared/revisions.txt b/build/shared/revisions.txt index bfbe96e52..5944bfa18 100644 --- a/build/shared/revisions.txt +++ b/build/shared/revisions.txt @@ -1,4 +1,11 @@ +ARDUINO 1.6.0rc2 + +The following changes are included also in the Arduino IDE 1.0.7: + +[ide] +* Mitigated Serial Monitor resource exhaustion when the connected device sends a lot of data (Paul Stoffregen) + ARDUINO 1.6.0rc1 * IDE internals have been refactored and sorted out. (Claudio Indellicati) @@ -379,6 +386,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]