Skip to content

Mitigated Serial Monitor resource exhaustion #2491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 29, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 16 additions & 41 deletions app/src/processing/app/Serial.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
package processing.app;
//import processing.core.*;

import processing.app.debug.MessageConsumer;
import static processing.app.I18n._;

import gnu.io.*;

import java.io.*;
Expand Down Expand Up @@ -55,15 +53,13 @@ public class Serial implements SerialPortEventListener {

// read buffer and streams

InputStream input;
InputStreamReader input;
OutputStream output;

byte buffer[] = new byte[32768];
int bufferIndex;
int bufferLast;

MessageConsumer consumer;

public Serial(boolean monitor) throws SerialException {
this(Preferences.get("serial.port"),
Preferences.getInteger("serial.debug_rate"),
Expand Down Expand Up @@ -158,7 +154,7 @@ public Serial(String iname, int irate,
if (portId.getName().equals(iname)) {
//System.out.println("looking for "+iname);
port = (SerialPort)portId.open("serial madness", 2000);
input = port.getInputStream();
input = new InputStreamReader(port.getInputStream());
output = port.getOutputStream();
port.setSerialPortParams(rate, databits, stopbits, parity);
port.addEventListener(this);
Expand Down Expand Up @@ -237,62 +233,41 @@ public void dispose() {
port = null;
}

char serialBuffer[] = new char[4096];

public void addListener(MessageConsumer consumer) {
this.consumer = consumer;
}


synchronized public void serialEvent(SerialPortEvent serialEvent) {
//System.out.println("serial port event"); // " + serialEvent);
//System.out.flush();
//System.out.println("into");
//System.out.flush();
//System.err.println("type " + serialEvent.getEventType());
//System.err.println("ahoooyey");
//System.err.println("ahoooyeysdfsdfsdf");
if (serialEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
//System.out.println("data available");
//System.err.flush();
try {
while (input.available() > 0) {
//if (input.available() > 0) {
//serial = input.read();
//serialEvent();
//buffer[bufferCount++] = (byte) serial;
while (input.ready()) {
synchronized (buffer) {
if (bufferLast == buffer.length) {
byte temp[] = new byte[bufferLast << 1];
System.arraycopy(buffer, 0, temp, 0, bufferLast);
buffer = temp;
}
//buffer[bufferLast++] = (byte) input.read();
if(monitor == true)
System.out.print((char) input.read());
if (this.consumer != null)
this.consumer.message("" + (char) input.read());

/*
System.err.println(input.available() + " " +
((char) buffer[bufferLast-1]));
*/ //}
int n = input.read(serialBuffer);
message(serialBuffer, n);
}
}
//System.out.println("no more");

} catch (IOException e) {
errorMessage("serialEvent", e);
//e.printStackTrace();
//System.out.println("angry");
}
catch (Exception e) {
}
}
//System.out.println("out of");
//System.err.println("out of event " + serialEvent.getEventType());
}


/**
* This method is intended to be redefined by users of Serial class
*
* @param buff
* @param n
*/
protected void message(char buff[], int n) {
// Empty
}

/**
* Returns the number of bytes that have been read from serial
* and are waiting to be dealt with by the user.
Expand Down
57 changes: 41 additions & 16 deletions app/src/processing/app/SerialMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,30 @@

package processing.app;

import processing.app.debug.MessageConsumer;
import processing.app.debug.TextAreaFIFO;
import processing.core.*;
import static processing.app.I18n._;

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 ActionListener {
private Serial serial;
private String port;
private JTextArea textArea;
private TextAreaFIFO textArea;
private JScrollPane scrollPane;
private JTextField textField;
private JButton sendButton;
private JCheckBox autoscrollBox;
private JComboBox lineEndings;
private JComboBox serialRates;
private int serialRate;
private javax.swing.Timer updateTimer;
private StringBuffer updateBuffer;

public SerialMonitor(String port) {
super(port);
Expand Down Expand Up @@ -67,7 +69,9 @@ public void actionPerformed(ActionEvent e) {
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);

Expand Down Expand Up @@ -171,6 +175,9 @@ public void actionPerformed(ActionEvent event) {
}
}
}

updateBuffer = new StringBuffer(1048576);
updateTimer = new javax.swing.Timer(33, this); // redraw serial monitor at 30 Hz
}

protected void setPlacement(int[] location) {
Expand Down Expand Up @@ -203,9 +210,13 @@ private void send(String s) {

public void openSerialPort() throws SerialException {
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);
}
};
updateTimer.start();
}

public void closeSerialPort() {
Expand All @@ -219,13 +230,27 @@ public void closeSerialPort() {
}
}

public void message(final String s) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
textArea.append(s);
if (autoscrollBox.isSelected()) {
textArea.setCaretPosition(textArea.getDocument().getLength());
}
}});
private 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);
}
}
}

}
92 changes: 92 additions & 0 deletions app/src/processing/app/debug/TextAreaFIFO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright (c) 2014 Paul Stoffregen <[email protected]>

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;
}
}
3 changes: 3 additions & 0 deletions build/shared/revisions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down