From a9de2e3d5310d7ae6982a2e52fbc174aec303715 Mon Sep 17 00:00:00 2001 From: Ben Fry Date: Wed, 3 May 2017 20:40:36 -0400 Subject: [PATCH] lots of work to clean up console lockup issues (fixes #4825) --- app/src/processing/app/Console.java | 32 +++-- .../app/exec/StreamRedirectThread.java | 21 ++-- app/src/processing/app/ui/EditorConsole.java | 119 +++++++++++++----- build/shared/lib/defaults.txt | 3 +- todo.txt | 9 +- 5 files changed, 120 insertions(+), 64 deletions(-) diff --git a/app/src/processing/app/Console.java b/app/src/processing/app/Console.java index 178fce716..b65aa1c89 100644 --- a/app/src/processing/app/Console.java +++ b/app/src/processing/app/Console.java @@ -30,7 +30,7 @@ import java.util.Date; /** - * Message console that sits below the editing area. + * Non-GUI handling of System.out and System.err redirection. *

* Be careful when debugging this class, because if it's throwing exceptions, * don't take over System.err, and debug while watching just System.out @@ -43,9 +43,6 @@ import java.util.Date; * get along with one another. Use 'ant run' to work on encoding-related issues. */ public class Console { -// PrintStream sketchOut; -// PrintStream sketchErr; - // Single static instance shared because there's only one real System.out. // Within the input handlers, the currentConsole variable will be used to // echo things to the correct location. @@ -72,6 +69,13 @@ public class Console { static public void startup() { + if (systemOut != null) { + // TODO fix this dreadful style choice in how the Console is initialized + // (This is not good code.. startup() should gracefully deal with this. + // It's just a low priority relative to the likelihood of trouble.) + new Exception("startup() called more than once").printStackTrace(systemErr); + return; + } systemOut = System.out; systemErr = System.err; @@ -115,8 +119,6 @@ public class Console { File errFile = new File(consoleDir, stamp + ".err"); stderrFile = new FileOutputStream(errFile); -// consoleOut = new PrintStream(new EditorConsoleStream(false, null)); -// consoleErr = new PrintStream(new EditorConsoleStream(true, null)); consoleOut = new PrintStream(new ConsoleStream(false)); consoleErr = new PrintStream(new ConsoleStream(true)); @@ -144,20 +146,14 @@ public class Console { } -// public Console() { -// sketchOut = new PrintStream(new EditorConsoleStream(false, this)); -// sketchErr = new PrintStream(new EditorConsoleStream(true, this)); -// } + static public void systemOut(String what) { + systemOut.println(what); + } -// public PrintStream getOut() { -// return sketchOut; -// } - - -// public PrintStream getErr() { -// return sketchErr; -// } + static public void systemErr(String what) { + systemErr.println(what); + } /** diff --git a/app/src/processing/app/exec/StreamRedirectThread.java b/app/src/processing/app/exec/StreamRedirectThread.java index c3140d872..ee6d7ed00 100644 --- a/app/src/processing/app/exec/StreamRedirectThread.java +++ b/app/src/processing/app/exec/StreamRedirectThread.java @@ -7,13 +7,13 @@ */ /* * Copyright (c) 1997-2001 by Sun Microsystems, Inc. All Rights Reserved. - * + * * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, * modify and redistribute this software in source and binary code form, * provided that i) this copyright notice and license appear on all copies of * the software; and ii) Licensee does not utilize the software in a manner * which is disparaging to Sun. - * + * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR @@ -25,7 +25,7 @@ * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. - * + * * This software is not designed or intended for use in on-line control of * aircraft, air traffic, aircraft navigation or aircraft communications; or in * the design, construction, operation or maintenance of any nuclear @@ -50,7 +50,7 @@ public class StreamRedirectThread extends Thread { private static final int BUFFER_SIZE = 2048; - + /** * Set up for copy. * @param name Name of the thread @@ -63,15 +63,15 @@ public class StreamRedirectThread extends Thread { this.out = new OutputStreamWriter(out); setPriority(Thread.MAX_PRIORITY-1); } - - + + public StreamRedirectThread(String name, Reader in, Writer out) { super(name); this.in = in; this.out = out; setPriority(Thread.MAX_PRIORITY-1); } - + /** * Copy. @@ -80,16 +80,15 @@ public class StreamRedirectThread extends Thread { try { char[] cbuf = new char[BUFFER_SIZE]; int count; - //System.out.println("opening streamredirectthread"); while ((count = in.read(cbuf, 0, BUFFER_SIZE)) >= 0) { out.write(cbuf, 0, count); // had to add the flush() here.. maybe shouldn't be using writer? [fry] out.flush(); } - //System.out.println("exiting streamredirectthread"); out.flush(); - } catch(IOException exc) { - System.err.println("Child I/O Transfer - " + exc); + + } catch (IOException exc) { + processing.app.Console.systemErr("Child I/O Transfer - " + exc); } } } diff --git a/app/src/processing/app/ui/EditorConsole.java b/app/src/processing/app/ui/EditorConsole.java index 005f12405..d27d17d05 100644 --- a/app/src/processing/app/ui/EditorConsole.java +++ b/app/src/processing/app/ui/EditorConsole.java @@ -32,8 +32,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.OutputStream; import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; import javax.swing.*; import javax.swing.border.MatteBorder; @@ -46,16 +45,6 @@ import processing.app.Preferences; /** * Message console that sits below the editing area. - *

- * Be careful when debugging this class, because if it's throwing exceptions, - * don't take over System.err, and debug while watching just System.out - * or just call println() or whatever directly to systemOut or systemErr. - *

- * Also note that encodings will not work properly when run from Eclipse. This - * means that if you use non-ASCII characters in a println() or some such, - * the characters won't print properly in the Processing and/or Eclipse console. - * It seems that Eclipse's console-grabbing and that of Processing don't - * get along with one another. Use 'ant run' to work on encoding-related issues. */ public class EditorConsole extends JScrollPane { Editor editor; @@ -69,6 +58,7 @@ public class EditorConsole extends JScrollPane { MutableAttributeSet errStyle; int maxLineCount; + int maxCharCount; PrintStream sketchOut; PrintStream sketchErr; @@ -79,9 +69,10 @@ public class EditorConsole extends JScrollPane { public EditorConsole(Editor editor) { this.editor = editor; - maxLineCount = Preferences.getInteger("console.length"); + maxLineCount = Preferences.getInteger("console.lines"); + maxCharCount = Preferences.getInteger("console.chars"); - consoleDoc = new BufferedStyledDocument(10000, maxLineCount); + consoleDoc = new BufferedStyledDocument(10000, maxLineCount, maxCharCount); consoleTextPane = new JTextPane(consoleDoc); consoleTextPane.setEditable(false); @@ -98,7 +89,7 @@ public class EditorConsole extends JScrollPane { protected void flush() { // only if new text has been added - if (consoleDoc.hasAppendage) { + if (consoleDoc.hasAppendage()) { // insert the text that's been added in the meantime consoleDoc.insertAll(); // always move to the end of the text as it's added @@ -150,8 +141,7 @@ public class EditorConsole extends JScrollPane { */ protected void updateAppearance() { String fontFamily = Preferences.get("editor.font.family"); - int fontSize = - Toolkit.zoom(Preferences.getInteger("console.font.size")); + int fontSize = Toolkit.zoom(Preferences.getInteger("console.font.size")); StyleConstants.setFontFamily(stdStyle, fontFamily); StyleConstants.setFontSize(stdStyle, fontSize); StyleConstants.setFontFamily(errStyle, fontFamily); @@ -232,7 +222,7 @@ public class EditorConsole extends JScrollPane { } - synchronized public void message(String what, boolean err) { + public void message(String what, boolean err) { if (err && (what.contains("invalid context 0x0") || (what.contains("invalid drawable")))) { // Respectfully declining... This is a quirk of more recent releases of // Java on Mac OS X, but is widely reported as the source of any other @@ -302,22 +292,32 @@ public class EditorConsole extends JScrollPane { * swing event thread, so they need to be synchronized */ class BufferedStyledDocument extends DefaultStyledDocument { - List elements = new ArrayList<>(); - int maxLineLength, maxLineCount; + //List elements = new ArrayList<>(); + LinkedBlockingQueue elements; +// AtomicInteger queuedLineCount = new AtomicInteger(); + int maxLineLength, maxLineCount, maxCharCount; int currentLineLength = 0; boolean needLineBreak = false; - boolean hasAppendage = false; +// boolean hasAppendage = false; + final Object insertLock = new Object(); - public BufferedStyledDocument(int maxLineLength, int maxLineCount) { + public BufferedStyledDocument(int maxLineLength, int maxLineCount, + int maxCharCount) { this.maxLineLength = maxLineLength; this.maxLineCount = maxLineCount; + this.maxCharCount = maxCharCount; + elements = new LinkedBlockingQueue<>(); + } + + // monitor this so that it's only updated when needed (otherwise console + // updates every 250 ms when an app isn't even running.. see bug 180) + public boolean hasAppendage() { + return elements.size() > 0; } /** buffer a string for insertion at the end of the DefaultStyledDocument */ - public synchronized void appendString(String str, AttributeSet a) { - // do this so that it's only updated when needed (otherwise console - // updates every 250 ms when an app isn't even running.. see bug 180) - hasAppendage = true; + public void appendString(String str, AttributeSet a) { +// hasAppendage = true; // process each line of the string while (str.length() > 0) { @@ -326,6 +326,7 @@ class BufferedStyledDocument extends DefaultStyledDocument { if (needLineBreak || currentLineLength > maxLineLength) { elements.add(new ElementSpec(a, ElementSpec.EndTagType)); elements.add(new ElementSpec(a, ElementSpec.StartTagType)); +// queuedLineCount.incrementAndGet(); currentLineLength = 0; } @@ -341,15 +342,42 @@ class BufferedStyledDocument extends DefaultStyledDocument { needLineBreak = true; str = str.substring(str.indexOf('\n') + 1); // eat the line } + /* + while (queuedLineCount.get() > maxLineCount) { + Console.systemOut("too many: " + queuedLineCount); + ElementSpec elem = elements.remove(); + if (elem.getType() == ElementSpec.EndTagType) { + queuedLineCount.decrementAndGet(); + } + } + */ + } + if (elements.size() > 1000) { + insertAll(); } } /** insert the buffered strings */ - public synchronized void insertAll() { - ElementSpec[] elementArray = new ElementSpec[elements.size()]; - elements.toArray(elementArray); + public void insertAll() { + /* + // each line is ~3 elements + int tooMany = elements.size() - maxLineCount*3; + if (tooMany > 0) { + try { + remove(0, getLength()); // clear the document first + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + Console.systemOut("skipping " + elements.size()); + for (int i = 0; i < tooMany; i++) { + elements.remove(); + } + } + */ + ElementSpec[] elementArray = elements.toArray(new ElementSpec[0]); try { + /* // check how many lines have been used so far // if too many, shave off a few lines from the beginning Element element = super.getDefaultRootElement(); @@ -366,13 +394,42 @@ class BufferedStyledDocument extends DefaultStyledDocument { // remove to the end of the 200th line super.remove(0, endOffset); } - super.insert(super.getLength(), elementArray); + */ + synchronized (insertLock) { + checkLength(); + insert(getLength(), elementArray); + checkLength(); + } } catch (BadLocationException e) { // ignore the error otherwise this will cause an infinite loop // maybe not a good idea in the long run? } elements.clear(); - hasAppendage = false; +// hasAppendage = false; + } + + private void checkLength() throws BadLocationException { + // set a limit on the number of characters in the console + int docLength = getLength(); + if (docLength > maxCharCount) { + remove(0, docLength - maxCharCount); + } + // check how many lines have been used so far + // if too many, shave off a few lines from the beginning + Element element = super.getDefaultRootElement(); + int lineCount = element.getElementCount(); + int overage = lineCount - maxLineCount; + if (overage > 0) { + // if 1200 lines, and 1000 lines is max, + // find the position of the end of the 200th line + //systemOut.println("overage is " + overage); + Element lineElement = element.getElement(overage); + if (lineElement != null) { + int endOffset = lineElement.getEndOffset(); + // remove to the end of the 200th line + super.remove(0, endOffset); + } + } } } diff --git a/build/shared/lib/defaults.txt b/build/shared/lib/defaults.txt index 3beaf068b..55dd53e9b 100644 --- a/build/shared/lib/defaults.txt +++ b/build/shared/lib/defaults.txt @@ -184,7 +184,8 @@ console.auto_clear = true # set the maximum number of lines remembered by the console # the default is 500, lengthen at your own peril -console.length = 500 +console.lines = 500 +console.chars = 40000 # Any additional Java options when running. # If you change this and can't run things, it's your own durn fault. diff --git a/todo.txt b/todo.txt index 610efce82..ecf5e803d 100755 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,10 @@ 0261 (3.3.4 or 3.4) +X redo console handling to not use Timer, fixing freeze-up problems +o https://github.com/processing/processing/pull/4935 +X https://github.com/processing/processing/issues/4825 + + +_ should ant run launch the .app so that launchsvcs stuff works properly? _ sketch.properties not being written if initial mode is p5.js? _ what to double-click when opening p5 projects @@ -39,9 +45,6 @@ _ super easy given current code implementation, might help usability contrib -_ redo console handling to not use Timer, fixing freeze-up problems -_ https://github.com/processing/processing/pull/4935 -_ https://github.com/processing/processing/issues/4825 _ Make the change detector not reload the sketch _ https://github.com/processing/processing/pull/5021 _ https://github.com/processing/processing/issues/4713