From a298c600bc8210c05f671e76ade2e7f356793320 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 16 Jul 2025 14:28:00 +0200 Subject: [PATCH 1/5] Make rum injector stream/writer more resilient to errors --- .../buffer/InjectingPipeOutputStream.java | 75 ++++++++++------ .../buffer/InjectingPipeWriter.java | 85 ++++++++++++------- .../InjectingPipeOutputStreamTest.groovy | 62 ++++++++++++++ .../buffer/InjectingPipeWriterTest.groovy | 62 ++++++++++++++ 4 files changed, 231 insertions(+), 53 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java index 633db0c8d4c..a7c7fe2dddf 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java @@ -5,16 +5,19 @@ /** * An OutputStream containing a circular buffer with a lookbehind buffer of n bytes. The first time - * that the latest n bytes matches the marker, a content is injected before. + * that the latest n bytes matches the marker, a content is injected before. In case of IOException + * thrown by the downstream, the buffer will be lost unless the error occurred when draining it. In + * this case the draining will be resumed. */ public class InjectingPipeOutputStream extends OutputStream { private final byte[] lookbehind; private int pos; - private boolean bufferFilled; + private int count; private final byte[] marker; private final byte[] contentToInject; - private boolean found = false; - private int matchingPos = 0; + private boolean filter; + private boolean wasDraining; + private int matchingPos; private final Runnable onContentInjected; private final int bulkWriteThreshold; private final OutputStream downstream; @@ -34,6 +37,11 @@ public InjectingPipeOutputStream( this.marker = marker; this.lookbehind = new byte[marker.length]; this.pos = 0; + this.count = 0; + this.matchingPos = 0; + this.wasDraining = false; + // should filter the stream to potentially inject into it. + this.filter = true; this.contentToInject = contentToInject; this.onContentInjected = onContentInjected; this.bulkWriteThreshold = marker.length * 2 - 2; @@ -41,25 +49,27 @@ public InjectingPipeOutputStream( @Override public void write(int b) throws IOException { - if (found) { + if (!filter) { + if (wasDraining) { + // continue draining + drain(); + } downstream.write(b); return; } - if (bufferFilled) { + if (count == lookbehind.length) { downstream.write(lookbehind[pos]); + } else { + count++; } lookbehind[pos] = (byte) b; pos = (pos + 1) % lookbehind.length; - if (!bufferFilled) { - bufferFilled = pos == 0; - } - if (marker[matchingPos++] == b) { if (matchingPos == marker.length) { - found = true; + filter = false; downstream.write(contentToInject); if (onContentInjected != null) { onContentInjected.run(); @@ -73,10 +83,15 @@ public void write(int b) throws IOException { @Override public void write(byte[] array, int off, int len) throws IOException { - if (found) { + if (!filter) { + if (wasDraining) { + // needs drain + drain(); + } downstream.write(array, off, len); return; } + if (len > bulkWriteThreshold) { // if the content is large enough, we can bulk write everything but the N trail and tail. // This because the buffer can already contain some byte from a previous single write. @@ -84,7 +99,7 @@ public void write(byte[] array, int off, int len) throws IOException { int idx = arrayContains(array, off, len, marker); if (idx >= 0) { // we have a full match. just write everything - found = true; + filter = false; drain(); downstream.write(array, off, idx); downstream.write(contentToInject); @@ -99,7 +114,13 @@ public void write(byte[] array, int off, int len) throws IOException { write(array[i]); } drain(); + boolean tmpFilter = filter; + + // will be reset if no errors after the following write + filter = false; downstream.write(array, off + marker.length - 1, len - bulkWriteThreshold); + filter = tmpFilter; + for (int i = len - marker.length + 1; i < len; i++) { write(array[i]); } @@ -133,16 +154,19 @@ private int arrayContains(byte[] array, int off, int len, byte[] search) { } private void drain() throws IOException { - if (bufferFilled) { - for (int i = 0; i < lookbehind.length; i++) { - downstream.write(lookbehind[(pos + i) % lookbehind.length]); + if (count > 0) { + boolean tmpFilter = filter; + filter = false; + wasDraining = true; + int start = (pos - count + lookbehind.length) % lookbehind.length; + int cnt = count; + for (int i = 0; i < cnt; i++) { + downstream.write(lookbehind[(start + i) % lookbehind.length]); + count--; } - } else { - downstream.write(this.lookbehind, 0, pos); + filter = tmpFilter; + wasDraining = false; } - pos = 0; - matchingPos = 0; - bufferFilled = false; } @Override @@ -152,9 +176,12 @@ public void flush() throws IOException { @Override public void close() throws IOException { - if (!found) { - drain(); + try { + if (filter || wasDraining) { + drain(); + } + } finally { + downstream.close(); } - downstream.close(); } } diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java index f012c04cae4..aaf70a1c5c2 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java @@ -5,16 +5,19 @@ /** * A Writer containing a circular buffer with a lookbehind buffer of n bytes. The first time that - * the latest n bytes matches the marker, a content is injected before. + * the latest n bytes matches the marker, a content is injected before. In case of IOException + * thrown by the downstream, the buffer will be lost unless the error occurred when draining it. In + * this case the draining will be resumed. */ public class InjectingPipeWriter extends Writer { private final char[] lookbehind; private int pos; - private boolean bufferFilled; + private int count; private final char[] marker; private final char[] contentToInject; - private boolean found = false; - private int matchingPos = 0; + private boolean filter; + private boolean wasDraining; + private int matchingPos; private final Runnable onContentInjected; private final int bulkWriteThreshold; private final Writer downstream; @@ -34,6 +37,11 @@ public InjectingPipeWriter( this.marker = marker; this.lookbehind = new char[marker.length]; this.pos = 0; + this.count = 0; + this.matchingPos = 0; + this.wasDraining = false; + // should filter the stream to potentially inject into it. + this.filter = true; this.contentToInject = contentToInject; this.onContentInjected = onContentInjected; this.bulkWriteThreshold = marker.length * 2 - 2; @@ -41,25 +49,27 @@ public InjectingPipeWriter( @Override public void write(int c) throws IOException { - if (found) { + if (!filter) { + if (wasDraining) { + // continue draining + drain(); + } downstream.write(c); return; } - if (bufferFilled) { + if (count == lookbehind.length) { downstream.write(lookbehind[pos]); + } else { + count++; } lookbehind[pos] = (char) c; pos = (pos + 1) % lookbehind.length; - if (!bufferFilled) { - bufferFilled = pos == 0; - } - if (marker[matchingPos++] == c) { if (matchingPos == marker.length) { - found = true; + filter = false; downstream.write(contentToInject); if (onContentInjected != null) { onContentInjected.run(); @@ -71,17 +81,17 @@ public void write(int c) throws IOException { } } - @Override - public void flush() throws IOException { - downstream.flush(); - } - @Override public void write(char[] array, int off, int len) throws IOException { - if (found) { + if (!filter) { + if (wasDraining) { + // needs drain + drain(); + } downstream.write(array, off, len); return; } + if (len > bulkWriteThreshold) { // if the content is large enough, we can bulk write everything but the N trail and tail. // This because the buffer can already contain some byte from a previous single write. @@ -89,7 +99,7 @@ public void write(char[] array, int off, int len) throws IOException { int idx = arrayContains(array, off, len, marker); if (idx >= 0) { // we have a full match. just write everything - found = true; + filter = false; drain(); downstream.write(array, off, idx); downstream.write(contentToInject); @@ -104,7 +114,13 @@ public void write(char[] array, int off, int len) throws IOException { write(array[i]); } drain(); + boolean tmpFilter = filter; + + // will be reset if no errors after the following write + filter = false; downstream.write(array, off + marker.length - 1, len - bulkWriteThreshold); + filter = tmpFilter; + for (int i = len - marker.length + 1; i < len; i++) { write(array[i]); } @@ -138,23 +154,34 @@ private int arrayContains(char[] array, int off, int len, char[] search) { } private void drain() throws IOException { - if (bufferFilled) { - for (int i = 0; i < lookbehind.length; i++) { - downstream.write(lookbehind[(pos + i) % lookbehind.length]); + if (count > 0) { + boolean tmpFilter = filter; + filter = false; + wasDraining = true; + int start = (pos - count + lookbehind.length) % lookbehind.length; + int cnt = count; + for (int i = 0; i < cnt; i++) { + downstream.write(lookbehind[(start + i) % lookbehind.length]); + count--; } - } else { - downstream.write(this.lookbehind, 0, pos); + filter = tmpFilter; + wasDraining = false; } - pos = 0; - matchingPos = 0; - bufferFilled = false; + } + + @Override + public void flush() throws IOException { + downstream.flush(); } @Override public void close() throws IOException { - if (!found) { - drain(); + try { + if (filter || wasDraining) { + drain(); + } + } finally { + downstream.close(); } - downstream.close(); } } diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy index 457b26577ba..755d698ad15 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy @@ -1,8 +1,37 @@ package datadog.trace.bootstrap.instrumentation.buffer import datadog.trace.test.util.DDSpecification +import org.apache.commons.io.IOUtils class InjectingPipeOutputStreamTest extends DDSpecification { + static class GlitchedOutputStream extends FilterOutputStream { + int glitchesPos + int count + + GlitchedOutputStream(OutputStream out, int glitchesPos) { + super(out) + this.glitchesPos = glitchesPos + } + + @Override + void write(byte[] b, int off, int len) throws IOException { + count += len + if (count >= glitchesPos) { + glitchesPos = Integer.MAX_VALUE + throw new IOException("Glitched after $count bytes") + } + out.write(b, off, len) + } + + @Override + void write(int b) throws IOException { + if (++count == glitchesPos) { + throw new IOException("Glitched after $glitchesPos bytes") + } + out.write(b) + } + } + def 'should filter a buffer and inject if found #found'() { setup: def downstream = new ByteArrayOutputStream() @@ -20,4 +49,37 @@ class InjectingPipeOutputStreamTest extends DDSpecification { "" | "" | "" | false | "" "" | "" | "" | false | "" } + + def 'should be resilient to exceptions when writing #body'() { + setup: + def baos = new ByteArrayOutputStream() + def downstream = new GlitchedOutputStream(baos, glichesAt) + def piped = new InjectingPipeOutputStream(downstream, marker.getBytes("UTF-8"), contentToInject.getBytes("UTF-8"), null) + when: + try { + for (String line : body) { + final bytes = line.getBytes("UTF-8") + try { + piped.write(bytes) + } catch (IOException ioe) { + ioe.printStackTrace() + piped.write(bytes) + } + } + } finally { + IOUtils.closeQuietly(piped) // it can throw when draining at close + } + then: + assert baos.toByteArray() == expected.getBytes("UTF-8") + where: + body | marker | contentToInject | glichesAt | expected + // write fails after the content has been injected + ["", "", "", "", "", ""] | "" | "" | 60 | "" + // write fails before the content has been injected + ["", "", "", "", "", ""] | "" | "" | 20 | "" + // write fails after having filled the buffer. The last line is written twice + ["", "", ""] | "" | "" | 10 | "" + // expected broken since the real write happens at close (drain) being the content smaller than the buffer. And retry on close is not a common practice. Hence, we suppose loosing content + [""] | "" | "" | 3 | "= glitchesPos) { + glitchesPos = Integer.MAX_VALUE + throw new IOException("Glitched after $count bytes") + } + out.write(c, off, len) + } + + @Override + void write(int c) throws IOException { + if (++count == glitchesPos) { + throw new IOException("Glitched after $glitchesPos bytes") + } + out.write(c) + } + } + def 'should filter a buffer and inject if found #found using write'() { setup: def downstream = new StringWriter() @@ -36,4 +65,37 @@ class InjectingPipeWriterTest extends DDSpecification { "" | "" | "" | false | "" "" | "" | "" | false | "" } + + def 'should be resilient to exceptions when writing #body'() { + setup: + def writer = new StringWriter() + def downstream = new GlitchedWriter(writer, glichesAt) + def piped = new InjectingPipeWriter(downstream, marker.toCharArray(), contentToInject.toCharArray(), null) + when: + try { + for (String line : body) { + final chars = line.toCharArray() + try { + piped.write(chars) + } catch (IOException ioe) { + ioe.printStackTrace() + piped.write(chars) + } + } + } finally { + IOUtils.closeQuietly(piped) // it can throw when draining at close + } + then: + assert writer.toString() == expected + where: + body | marker | contentToInject | glichesAt | expected + // write fails after the content has been injected + ["", "", "", "", "", ""] | "" | "" | 60 | "" + // write fails before the content has been injected + ["", "", "", "", "", ""] | "" | "" | 20 | "" + // write fails after having filled the buffer. The last line is written twice + ["", "", ""] | "" | "" | 10 | "" + // expected broken since the real write happens at close (drain) being the content smaller than the buffer. And retry on close is not a common practice. Hence, we suppose loosing content + [""] | "" | "" | 3 | " Date: Wed, 16 Jul 2025 15:04:22 +0200 Subject: [PATCH 2/5] fix tests --- .../buffer/InjectingPipeOutputStreamTest.groovy | 7 +++++-- .../instrumentation/buffer/InjectingPipeWriterTest.groovy | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy index 755d698ad15..06ad3f79619 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy @@ -1,7 +1,6 @@ package datadog.trace.bootstrap.instrumentation.buffer import datadog.trace.test.util.DDSpecification -import org.apache.commons.io.IOUtils class InjectingPipeOutputStreamTest extends DDSpecification { static class GlitchedOutputStream extends FilterOutputStream { @@ -67,7 +66,11 @@ class InjectingPipeOutputStreamTest extends DDSpecification { } } } finally { - IOUtils.closeQuietly(piped) // it can throw when draining at close + // it can throw when draining at close + try { + piped.close() + } catch (IOException ignored) { + } } then: assert baos.toByteArray() == expected.getBytes("UTF-8") diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy index fe1523ed8d6..100ea7bbaf2 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy @@ -1,7 +1,6 @@ package datadog.trace.bootstrap.instrumentation.buffer import datadog.trace.test.util.DDSpecification -import org.apache.commons.io.IOUtils class InjectingPipeWriterTest extends DDSpecification { static class GlitchedWriter extends FilterWriter { @@ -83,7 +82,11 @@ class InjectingPipeWriterTest extends DDSpecification { } } } finally { - IOUtils.closeQuietly(piped) // it can throw when draining at close + // it can throw when draining at close + try { + piped.close() + } catch (IOException ignored) { + } } then: assert writer.toString() == expected From 5a32d5116a442039465ac72fb5d64684247116df Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 16 Jul 2025 16:08:54 +0200 Subject: [PATCH 3/5] fix tests --- .../instrumentation/buffer/InjectingPipeOutputStreamTest.groovy | 2 ++ .../instrumentation/buffer/InjectingPipeWriterTest.groovy | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy index 06ad3f79619..9b04234ad3d 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy @@ -6,9 +6,11 @@ class InjectingPipeOutputStreamTest extends DDSpecification { static class GlitchedOutputStream extends FilterOutputStream { int glitchesPos int count + final OutputStream out GlitchedOutputStream(OutputStream out, int glitchesPos) { super(out) + this.out = out this.glitchesPos = glitchesPos } diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy index 100ea7bbaf2..d115f81a403 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriterTest.groovy @@ -6,9 +6,11 @@ class InjectingPipeWriterTest extends DDSpecification { static class GlitchedWriter extends FilterWriter { int glitchesPos int count + final Writer out GlitchedWriter(Writer out, int glitchesPos) { super(out) + this.out = out this.glitchesPos = glitchesPos } From 87d15fbde3d22f829859cee530903ff94c132363 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 6 Aug 2025 15:06:04 +0200 Subject: [PATCH 4/5] Update dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java Co-authored-by: Stuart McCulloch --- .../instrumentation/buffer/InjectingPipeOutputStream.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java index a7c7fe2dddf..c70290ed10b 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java @@ -114,13 +114,12 @@ public void write(byte[] array, int off, int len) throws IOException { write(array[i]); } drain(); - boolean tmpFilter = filter; + boolean wasFiltering = filter; // will be reset if no errors after the following write filter = false; downstream.write(array, off + marker.length - 1, len - bulkWriteThreshold); - filter = tmpFilter; - + filter = wasFiltering; for (int i = len - marker.length + 1; i < len; i++) { write(array[i]); } From 21afeb2cac550b9fcfeca804b785ecd362c270b4 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 6 Aug 2025 15:09:02 +0200 Subject: [PATCH 5/5] apply suggestions --- .../instrumentation/buffer/InjectingPipeOutputStream.java | 4 ++-- .../instrumentation/buffer/InjectingPipeWriter.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java index c70290ed10b..8fa6e115c4d 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java @@ -154,7 +154,7 @@ private int arrayContains(byte[] array, int off, int len, byte[] search) { private void drain() throws IOException { if (count > 0) { - boolean tmpFilter = filter; + boolean wasFiltering = filter; filter = false; wasDraining = true; int start = (pos - count + lookbehind.length) % lookbehind.length; @@ -163,7 +163,7 @@ private void drain() throws IOException { downstream.write(lookbehind[(start + i) % lookbehind.length]); count--; } - filter = tmpFilter; + filter = wasFiltering; wasDraining = false; } } diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java index aaf70a1c5c2..d7128e9b385 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeWriter.java @@ -114,12 +114,12 @@ public void write(char[] array, int off, int len) throws IOException { write(array[i]); } drain(); - boolean tmpFilter = filter; + boolean wasFiltering = filter; // will be reset if no errors after the following write filter = false; downstream.write(array, off + marker.length - 1, len - bulkWriteThreshold); - filter = tmpFilter; + filter = wasFiltering; for (int i = len - marker.length + 1; i < len; i++) { write(array[i]); @@ -155,7 +155,7 @@ private int arrayContains(char[] array, int off, int len, char[] search) { private void drain() throws IOException { if (count > 0) { - boolean tmpFilter = filter; + boolean wasFiltering = filter; filter = false; wasDraining = true; int start = (pos - count + lookbehind.length) % lookbehind.length; @@ -164,7 +164,7 @@ private void drain() throws IOException { downstream.write(lookbehind[(start + i) % lookbehind.length]); count--; } - filter = tmpFilter; + filter = wasFiltering; wasDraining = false; } }