Skip to content

Commit 033a921

Browse files
hinermctrueden
authored andcommitted
Avoid concurrency issues
When streams are added or removed, we do not want that to interfere with concurrent stream operations (i.e., writing, closing and flushing). But we also do not particularly want to incur the heavy performance penalty of synchronizing all the stream operations, either. So let's keep a copy of the streams, which is regenerated whenever stream is added or removed from the mix, and use that copy when iterating over the streams during the stream operations. Similarly, when listeners are added or removed from the ConsoleService, we use the same trick to avoid the need to synchronize listener notification every time an output event occurs. Signed-off-by: Curtis Rueden <ctrueden@wisc.edu>
1 parent 4c6fa30 commit 033a921

File tree

2 files changed

+31
-9
lines changed

2 files changed

+31
-9
lines changed

src/main/java/org/scijava/console/DefaultConsoleService.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public class DefaultConsoleService extends
6969
/** List of listeners for {@code stdout} and {@code stderr} output. */
7070
private ArrayList<OutputListener> listeners;
7171

72+
private OutputListener[] cachedListeners;
73+
7274
// -- ConsoleService methods --
7375

7476
@Override
@@ -97,6 +99,7 @@ public void addOutputListener(final OutputListener l) {
9799
if (listeners == null) initListeners();
98100
synchronized (listeners) {
99101
listeners.add(l);
102+
cacheListeners();
100103
}
101104
}
102105

@@ -105,17 +108,16 @@ public void removeOutputListener(final OutputListener l) {
105108
if (listeners == null) initListeners();
106109
synchronized (listeners) {
107110
listeners.remove(l);
111+
cacheListeners();
108112
}
109113
}
110114

111115
@Override
112116
public void notifyListeners(final OutputEvent event) {
113117
if (listeners == null) initListeners();
114-
synchronized (listeners) {
115-
for (final OutputListener l : listeners) {
116-
l.outputOccurred(event);
117-
}
118-
}
118+
final OutputListener[] toNotify = cachedListeners;
119+
for (final OutputListener l : toNotify)
120+
l.outputOccurred(event);
119121
}
120122

121123
// -- PTService methods --
@@ -158,10 +160,15 @@ private synchronized void initListeners() {
158160
syserr.getParent().addOutputStream(err);
159161

160162
listeners = new ArrayList<OutputListener>();
163+
cachedListeners = listeners.toArray(new OutputListener[0]);
161164
}
162165

163166
// -- Helper methods --
164167

168+
private void cacheListeners() {
169+
cachedListeners = listeners.toArray(new OutputListener[listeners.size()]);
170+
}
171+
165172
private MultiPrintStream multiPrintStream(final PrintStream ps) {
166173
if (ps instanceof MultiPrintStream) return (MultiPrintStream) ps;
167174
return new MultiPrintStream(ps);

src/main/java/org/scijava/console/MultiOutputStream.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public class MultiOutputStream extends OutputStream {
5050

5151
private final ArrayList<OutputStream> streams;
5252

53+
private OutputStream[] cachedStreams;
54+
5355
/**
5456
* Forwards output to a list of output streams.
5557
*
@@ -60,6 +62,7 @@ public MultiOutputStream(final OutputStream... os) {
6062
for (int i = 0; i < os.length; i++) {
6163
streams.add(os[i]);
6264
}
65+
cacheStreams();
6366
}
6467

6568
// -- MultiOutputStream methods --
@@ -68,46 +71,58 @@ public MultiOutputStream(final OutputStream... os) {
6871
public void addOutputStream(final OutputStream os) {
6972
synchronized (streams) {
7073
streams.add(os);
74+
cacheStreams();
7175
}
7276
}
7377

7478
/** Removes an output stream from those receiving this stream's output. */
7579
public void removeOutputStream(final OutputStream os) {
7680
synchronized (streams) {
7781
streams.remove(os);
82+
cacheStreams();
7883
}
7984
}
8085

8186
// -- OutputStream methods --
8287

8388
@Override
8489
public void write(final int b) throws IOException {
85-
for (final OutputStream stream : streams)
90+
final OutputStream[] toWrite = cachedStreams;
91+
for (final OutputStream stream : toWrite)
8692
stream.write(b);
8793
}
8894

8995
@Override
9096
public void write(final byte[] buf, final int off, final int len)
9197
throws IOException
9298
{
93-
for (final OutputStream stream : streams)
99+
final OutputStream[] toWrite = cachedStreams;
100+
for (final OutputStream stream : toWrite)
94101
stream.write(buf, off, len);
95102
}
96103

97104
// -- Closeable methods --
98105

99106
@Override
100107
public void close() throws IOException {
101-
for (final OutputStream stream : streams)
108+
final OutputStream[] toClose = cachedStreams;
109+
for (final OutputStream stream : toClose)
102110
stream.close();
103111
}
104112

105113
// -- Flushable methods --
106114

107115
@Override
108116
public void flush() throws IOException {
109-
for (final OutputStream stream : streams)
117+
final OutputStream[] toFlush = cachedStreams;
118+
for (final OutputStream stream : toFlush)
110119
stream.flush();
111120
}
112121

122+
// -- Helper methods --
123+
124+
private void cacheStreams() {
125+
cachedStreams = streams.toArray(new OutputStream[streams.size()]);
126+
}
127+
113128
}

0 commit comments

Comments
 (0)