Skip to content

Commit b418309

Browse files
Yaml reader is not thread safe (#3625)
* Yaml reader is not thread safe * Yaml reader is not thread safe
1 parent 140ec06 commit b418309

File tree

4 files changed

+78
-15
lines changed

4 files changed

+78
-15
lines changed

core/src/main/java/org/mapfish/print/config/ConfigurationFactory.java

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,25 @@
2222
public class ConfigurationFactory {
2323
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFactory.class);
2424
@Autowired private ConfigurableApplicationContext context;
25-
private Yaml yaml;
25+
private ThreadLocal<Yaml> yamlThreadLocal;
2626
private boolean doValidation = true;
2727

2828
/** initialize this factory. Called by spring after construction. */
2929
@PostConstruct
3030
public final void init() {
31-
MapfishPrintConstructor constructor = new MapfishPrintConstructor(this.context);
32-
3331
LoaderOptions loaderOptions = new LoaderOptions();
3432
String maxAliases = System.getenv("PRINT_YAML_MAX_ALIASES");
3533
if (maxAliases != null) {
3634
loaderOptions.setMaxAliasesForCollections(Integer.parseInt(maxAliases));
3735
}
3836
DumperOptions dumperOptions = new DumperOptions();
3937
Representer representer = new Representer(dumperOptions);
40-
this.yaml = new Yaml(constructor, representer, dumperOptions, loaderOptions);
38+
this.yamlThreadLocal =
39+
ThreadLocal.withInitial(
40+
() -> {
41+
MapfishPrintConstructor constructor = new MapfishPrintConstructor(this.context);
42+
return new Yaml(constructor, representer, dumperOptions, loaderOptions);
43+
});
4144
}
4245

4346
/**
@@ -64,20 +67,23 @@ public final Configuration getConfig(final File configFile, final InputStream co
6467
configuration.setConfigurationFile(configFile);
6568
MapfishPrintConstructor.setConfigurationUnderConstruction(configuration);
6669

67-
final Configuration config =
68-
this.yaml.load(new InputStreamReader(configData, StandardCharsets.UTF_8));
69-
if (this.doValidation) {
70-
final List<Throwable> validate = config.validate();
71-
if (!validate.isEmpty()) {
72-
StringBuilder errors = new StringBuilder();
73-
for (Throwable throwable : validate) {
74-
errors.append("\n\t* ").append(throwable.getMessage());
75-
LOGGER.error("Configuration Error found", throwable);
70+
try (InputStreamReader reader = new InputStreamReader(configData, StandardCharsets.UTF_8)) {
71+
final Configuration config = this.yamlThreadLocal.get().load(reader);
72+
if (this.doValidation) {
73+
final List<Throwable> validate = config.validate();
74+
if (!validate.isEmpty()) {
75+
StringBuilder errors = new StringBuilder();
76+
for (Throwable throwable : validate) {
77+
errors.append("\n\t* ").append(throwable.getMessage());
78+
LOGGER.error("Configuration Error found", throwable);
79+
}
80+
throw new Error(errors.toString(), validate.get(0));
7681
}
77-
throw new Error(errors.toString(), validate.get(0));
7882
}
83+
return config;
84+
} finally {
85+
this.yamlThreadLocal.remove();
7986
}
80-
return config;
8187
}
8288

8389
/**

core/src/test/java/org/mapfish/print/config/ConfigurationFactoryTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
import static org.junit.Assert.assertTrue;
66

77
import java.io.File;
8+
import java.util.concurrent.Callable;
9+
import java.util.concurrent.CountDownLatch;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.Executors;
12+
import java.util.concurrent.Future;
813
import org.junit.Before;
914
import org.junit.Test;
1015
import org.mapfish.print.AbstractMapfishSpringTest;
@@ -50,6 +55,48 @@ public void testSpringInjection() throws Exception {
5055
((ProcessorWithSpringInjection) processor).assertInjected();
5156
}
5257

58+
@Test
59+
public void testParallelConfigLoading() throws Exception {
60+
File configFileThread1 = getFile(ConfigurationFactoryTest.class, "configThread1.yaml");
61+
File configFileThread2 = getFile(ConfigurationFactoryTest.class, "configThread2.yaml");
62+
ExecutorService executor = Executors.newFixedThreadPool(2);
63+
CountDownLatch startLatch = new CountDownLatch(1);
64+
65+
Callable<Boolean> task1 =
66+
() -> {
67+
startLatch.await();
68+
Configuration config = configurationFactory.getConfig(configFileThread1);
69+
if (config.getTemplates() != null && config.getTemplates().containsKey("main")) {
70+
return config.getTemplate("main").getAttributes().get("thread1") != null;
71+
}
72+
return false;
73+
};
74+
75+
Callable<Boolean> task2 =
76+
() -> {
77+
startLatch.await();
78+
Configuration config = configurationFactory.getConfig(configFileThread2);
79+
if (config.getTemplates() != null && config.getTemplates().containsKey("main")) {
80+
return config.getTemplate("main").getAttributes().get("thread2") != null;
81+
}
82+
return false;
83+
};
84+
85+
Future<Boolean> thread1Futur = executor.submit(task1);
86+
Future<Boolean> thread2Futur = executor.submit(task2);
87+
88+
// Threads start at the same time
89+
startLatch.countDown();
90+
91+
boolean thread1Result = thread1Futur.get();
92+
boolean thread2Result = thread2Futur.get();
93+
94+
assertTrue("thread 1 configuration loading was not OK", thread1Result);
95+
assertTrue("thread 2 configuration loading was not OK", thread2Result);
96+
97+
executor.shutdown();
98+
}
99+
53100
@Test
54101
public void testConfigurationInjection() throws Exception {
55102
File configFile =
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
templates:
2+
main: !template
3+
attributes:
4+
thread1: !string
5+
default: 'thread1'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
templates:
2+
main: !template
3+
attributes:
4+
thread2: !string
5+
default: 'thread2'

0 commit comments

Comments
 (0)