Skip to content

Commit 85bed93

Browse files
committed
fix race condition reported in LOGBACK-1362
Signed-off-by: Ceki Gulcu <[email protected]>
1 parent c8c46e3 commit 85bed93

File tree

5 files changed

+129
-15
lines changed

5 files changed

+129
-15
lines changed

logback-core/src/main/java/ch/qos/logback/core/AppenderBase.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import ch.qos.logback.core.status.WarnStatus;
2323

2424
/**
25-
* Sets a skeleton implementation for appenders.
25+
* <p>Sets a skeleton implementation for appenders.</p>
26+
*
27+
* <p>It is coarsely synchronized in its {@link #doAppend(E)} method.</p>
2628
*
2729
* <p>
2830
* For more information about this appender, please refer to the online manual

logback-core/src/main/java/ch/qos/logback/core/OutputStreamAppender.java

+12-6
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,17 @@ public void start() {
7777
addStatus(new ErrorStatus("No output stream set for the appender named \"" + name + "\".", this));
7878
errors++;
7979
}
80+
81+
if (encoder == null) {
82+
addWarn("Encoder has not been set. Cannot invoke its init method.");
83+
errors++;
84+
}
85+
86+
8087
// only error free appenders should be activated
8188
if (errors == 0) {
8289
super.start();
90+
encoderInit();
8391
}
8492
}
8593

@@ -164,12 +172,7 @@ public void setOutputStream(OutputStream outputStream) {
164172
// close any previously opened output stream
165173
closeOutputStream();
166174
this.outputStream = outputStream;
167-
if (encoder == null) {
168-
addWarn("Encoder has not been set. Cannot invoke its init method.");
169-
return;
170-
}
171175

172-
encoderInit();
173176
} finally {
174177
streamWriteLock.unlock();
175178
}
@@ -198,8 +201,11 @@ private void writeBytes(byte[] byteArray) throws IOException {
198201
return;
199202

200203
streamWriteLock.lock();
204+
201205
try {
202-
writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
206+
if(isStarted()) {
207+
writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
208+
}
203209
} finally {
204210
streamWriteLock.unlock();
205211
}

logback-core/src/main/java/ch/qos/logback/core/UnsynchronizedAppenderBase.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
import ch.qos.logback.core.status.WarnStatus;
2323

2424
/**
25-
* Similar to AppenderBase except that derived appenders need to handle thread
25+
* Similar to {@link AppenderBase} except that derived appenders need to handle thread
2626
* synchronization on their own.
2727
*
2828
* @author Ceki G&uuml;lc&uuml;
2929
* @author Ralph Goers
3030
*/
3131
abstract public class UnsynchronizedAppenderBase<E> extends ContextAwareBase implements Appender<E> {
3232

33-
protected boolean started = false;
33+
protected volatile boolean started = false;
3434

3535
// using a ThreadLocal instead of a boolean add 75 nanoseconds per
3636
// doAppend invocation. This is tolerable as doAppend takes at least a few

logback-core/src/test/java/ch/qos/logback/core/OutputStreamAppenderTest.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ public void nullFileFooter() {
8282

8383
public void headerFooterCheck(String fileHeader, String presentationHeader, String presentationFooter,
8484
String fileFooter) {
85-
OutputStreamAppender<Object> wa = new OutputStreamAppender<Object>();
86-
wa.setContext(context);
85+
OutputStreamAppender<Object> osa = new OutputStreamAppender<Object>();
86+
osa.setContext(context);
8787
ByteArrayOutputStream baos = new ByteArrayOutputStream();
8888

8989
SamplePatternLayout<Object> spl = new SamplePatternLayout<Object>();
@@ -99,16 +99,17 @@ public void headerFooterCheck(String fileHeader, String presentationHeader, Stri
9999
encoder.setLayout(spl);
100100
encoder.setContext(context);
101101

102-
wa.setEncoder(encoder);
103-
wa.setOutputStream(baos);
104-
wa.start();
102+
osa.setEncoder(encoder);
103+
osa.setOutputStream(baos);
104+
osa.start();
105105

106-
wa.stop();
106+
osa.stop();
107107
String result = baos.toString();
108108

109109
String expectedHeader = emtptyIfNull(fileHeader) + emtptyIfNull(presentationHeader);
110110

111111
System.out.println(result);
112+
112113
Assertions.assertTrue(result.startsWith(expectedHeader), result);
113114

114115
String expectedFooter = emtptyIfNull(presentationFooter) + emtptyIfNull(fileFooter);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2024, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
15+
package ch.qos.logback.core.issue.lbcore258;
16+
17+
18+
import java.io.IOException;
19+
import java.io.OutputStream;
20+
21+
22+
import ch.qos.logback.core.OutputStreamAppender;
23+
import ch.qos.logback.core.encoder.EncoderBase;
24+
import org.junit.jupiter.api.Test;
25+
26+
/**
27+
* Provided by Alexander Kudrevatykh in LOGBACK-1362
28+
*/
29+
public class Logback1362 {
30+
31+
long startNanos = System.nanoTime();
32+
long DELAY = 20;
33+
long getNanos() {
34+
return (System.nanoTime() - startNanos);
35+
}
36+
37+
@Test
38+
public void testAppender() throws InterruptedException {
39+
40+
OutputStreamAppender<Object> appender = new OutputStreamAppender<Object>() {
41+
@Override
42+
public void addError(String msg, Throwable ex) {
43+
throw new RuntimeException(getNanos()+" "+msg, ex);
44+
}
45+
};
46+
47+
appender.setEncoder(new EncoderBase<Object>() {
48+
49+
@Override
50+
public byte[] headerBytes() {
51+
return null;
52+
}
53+
54+
@Override
55+
public byte[] encode(Object event) {
56+
delay(DELAY*2);
57+
return new byte[]{'A'};
58+
}
59+
60+
@Override
61+
public byte[] footerBytes() {
62+
// TODO Auto-generated method stub
63+
return null;
64+
}
65+
});
66+
appender.setOutputStream(new OutputStream() {
67+
68+
@Override
69+
public void write(int b) throws IOException {
70+
throw new RuntimeException("not closed appender");
71+
}
72+
});
73+
74+
System.out.println(getNanos() + " About to call appender.start()");
75+
appender.start();
76+
System.out.println(getNanos() + " After call to appender.start()");
77+
78+
Thread t = new Thread(new Runnable() {
79+
@Override
80+
public void run() {
81+
delay(DELAY);
82+
System.out.println(getNanos() + " About to call appender.stop()");
83+
appender.stop();
84+
System.out.println(getNanos() + " After call to appender.stop()");
85+
}
86+
});
87+
t.start();
88+
System.out.println(getNanos() + " Calling appender.doAppend(new Object());");
89+
appender.doAppend(new Object());
90+
System.out.println("xxxxxxxxxxxxxxxxxxxxxx");
91+
System.out.println(getNanos()+ " After call to appender.doAppender(new Object())");
92+
t.join();
93+
}
94+
95+
private void delay(long delayInMillis) {
96+
try {
97+
Thread.sleep(delayInMillis);
98+
} catch (InterruptedException e) {
99+
// TODO Auto-generated catch block
100+
e.printStackTrace();
101+
}
102+
}
103+
104+
}
105+

0 commit comments

Comments
 (0)