2929import hudson .remoting .Channel ;
3030import hudson .remoting .ChannelClosedException ;
3131import hudson .remoting .RemoteOutputStream ;
32+ import hudson .util .DaemonThreadFactory ;
33+ import hudson .util .NamingThreadFactory ;
3234import hudson .util .StreamTaskListener ;
3335import java .io .Closeable ;
3436import java .io .FilterOutputStream ;
3537import java .io .IOException ;
3638import java .io .OutputStream ;
3739import java .io .PrintStream ;
40+ import java .lang .ref .Cleaner ;
3841import java .util .logging .Logger ;
3942import org .jenkinsci .remoting .SerializableOnlyOverRemoting ;
4043
4447 */
4548final class BufferedBuildListener extends OutputStreamTaskListener .Default implements BuildListener , Closeable , SerializableOnlyOverRemoting {
4649
50+ private static final long serialVersionUID = 1 ;
51+
4752 private static final Logger LOGGER = Logger .getLogger (BufferedBuildListener .class .getName ());
4853
54+ private static final Cleaner cleaner = Cleaner .create (new NamingThreadFactory (new DaemonThreadFactory (), BufferedBuildListener .class .getName () + ".cleaner" ));
55+
4956 private final OutputStream out ;
57+ private transient final Channel channel ;
58+ private transient final Listener listener ;
5059
5160 BufferedBuildListener (OutputStream out ) {
5261 this .out = out ;
62+ if (out instanceof CloseableOutputStream ) {
63+ channel = Channel .currentOrFail ();
64+ listener = new Listener ((CloseableOutputStream ) out , channel );
65+ channel .addListener (listener );
66+ cleaner .register (this , listener );
67+ } else {
68+ channel = null ;
69+ listener = null ;
70+ }
5371 }
5472
5573 @ Override public OutputStream getOutputStream () {
@@ -58,12 +76,32 @@ final class BufferedBuildListener extends OutputStreamTaskListener.Default imple
5876
5977 @ Override public void close () throws IOException {
6078 getLogger ().close ();
79+ if (listener != null ) {
80+ channel .removeListener (listener );
81+ }
6182 }
6283
6384 private Object writeReplace () {
6485 return new Replacement (this );
6586 }
6687
88+ private static final class Listener extends Channel .Listener implements Runnable {
89+ private final CloseableOutputStream cos ;
90+ private final Channel channel ;
91+ Listener (CloseableOutputStream cos , Channel channel ) {
92+ this .cos = cos ;
93+ this .channel = channel ;
94+ }
95+ @ Override public void onClosed (Channel channel , IOException cause ) {
96+ LOGGER .fine (() -> "closing " + channel .getName ());
97+ cos .close (channel , cause );
98+ channel .removeListener (this );
99+ }
100+ @ Override public void run () {
101+ channel .removeListener (this );
102+ }
103+ }
104+
67105 private static final class Replacement implements SerializableOnlyOverRemoting {
68106
69107 private static final long serialVersionUID = 1 ;
@@ -76,15 +114,7 @@ private static final class Replacement implements SerializableOnlyOverRemoting {
76114 }
77115
78116 private Object readResolve () {
79- var cos = new CloseableOutputStream (new GCFlushedOutputStream (new DelayBufferedOutputStream (ros , tuning )));
80- Channel .currentOrFail ().addListener (new Channel .Listener () {
81- @ Override public void onClosed (Channel channel , IOException cause ) {
82- LOGGER .fine (() -> "closing " + channel .getName ());
83- cos .close (channel , cause );
84- channel .removeListener (this );
85- }
86- });
87- return new BufferedBuildListener (cos );
117+ return new BufferedBuildListener (new CloseableOutputStream (new GCFlushedOutputStream (new DelayBufferedOutputStream (ros , tuning ))));
88118 }
89119
90120 }
0 commit comments