3434import java .util .logging .Level ;
3535import java .util .logging .Logger ;
3636import jenkins .util .Timer ;
37+ import org .jenkinsci .remoting .SerializableOnlyOverRemoting ;
3738
3839/**
3940 * Buffered output stream which is guaranteed to deliver content after some time even if idle and the buffer does not fill up.
@@ -43,21 +44,33 @@ final class DelayBufferedOutputStream extends BufferedOutputStream {
4344
4445 private static final Logger LOGGER = Logger .getLogger (DelayBufferedOutputStream .class .getName ());
4546
46- // TODO make these customizable (not trivial since this system properties would need to be loaded on the master side and then remoted)
47- private static final long MIN_RECURRENCE_PERIOD = 250 ; // ¼s
48- private static final long MAX_RECURRENCE_PERIOD = 10_000 ; // 10s
49- private static final float RECURRENCE_PERIOD_BACKOFF = 1.05f ;
47+ static final class Tuning implements SerializableOnlyOverRemoting {
48+ private Tuning () {}
49+ // nonfinal for Groovy scripting:
50+ long minRecurrencePeriod = Long .getLong (DelayBufferedOutputStream .class .getName () + ".minRecurrencePeriod" , 250 ); // ¼s
51+ long maxRecurrencePeriod = Long .getLong (DelayBufferedOutputStream .class .getName () + ".maxRecurrencePeriod" , 10_000 ); // 10s
52+ float recurrencePeriodBackoff = Float .parseFloat (System .getProperty (DelayBufferedOutputStream .class .getName () + ".recurrencePeriodBackoff" , "1.05" ));
53+ int bufferSize = Integer .getInteger (DelayBufferedOutputStream .class .getName () + ".bufferSize" , 1 << 16 ); // 64Kib
54+ static final Tuning DEFAULT = new Tuning ();
55+ }
5056
51- private long recurrencePeriod = MIN_RECURRENCE_PERIOD ;
57+ private final Tuning tuning ;
58+ private long recurrencePeriod ;
5259
5360 DelayBufferedOutputStream (OutputStream out ) {
54- super (new FlushControlledOutputStream (out )); // default buffer size: 8Kib
61+ this (out , Tuning .DEFAULT );
62+ }
63+
64+ DelayBufferedOutputStream (OutputStream out , Tuning tuning ) {
65+ super (new FlushControlledOutputStream (out ), tuning .bufferSize );
66+ this .tuning = tuning ;
67+ recurrencePeriod = tuning .minRecurrencePeriod ;
5568 reschedule ();
5669 }
5770
5871 private void reschedule () {
5972 Timer .get ().schedule (new Flush (this ), recurrencePeriod , TimeUnit .MILLISECONDS );
60- recurrencePeriod = Math .min ((long ) (recurrencePeriod * RECURRENCE_PERIOD_BACKOFF ), MAX_RECURRENCE_PERIOD );
73+ recurrencePeriod = Math .min ((long ) (recurrencePeriod * tuning . recurrencePeriodBackoff ), tuning . maxRecurrencePeriod );
6174 }
6275
6376 /** We can only call {@link BufferedOutputStream#flushBuffer} via {@link #flush}, but we do not wish to flush the underlying stream, only write out the buffer. */
@@ -72,7 +85,8 @@ private void flushBuffer() throws IOException {
7285 }
7386 }
7487
75- void run () {
88+ void flushAndReschedule () {
89+ // TODO as an optimization, avoid flushing the buffer if it was recently flushed anyway due to filling up
7690 try {
7791 flushBuffer ();
7892 } catch (IOException x ) {
@@ -100,7 +114,7 @@ private static final class Flush implements Runnable {
100114 @ Override public void run () {
101115 DelayBufferedOutputStream os = osr .get ();
102116 if (os != null ) {
103- os .run ();
117+ os .flushAndReschedule ();
104118 }
105119 }
106120
0 commit comments