Skip to content

Commit

Permalink
Merge pull request #155 from jstachio/partial_thread_safe
Browse files Browse the repository at this point in the history
Partial thread safe (merge #154 first)
  • Loading branch information
samskivert authored Nov 29, 2023
2 parents 09f7121 + ab4bcb7 commit fe6ef9c
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 4 deletions.
21 changes: 17 additions & 4 deletions src/main/java/com/samskivert/mustache/Mustache.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* Provides <a href="http://mustache.github.com/">Mustache</a> templating services.
Expand Down Expand Up @@ -1057,10 +1059,20 @@ private IncludedTemplateSegment (Compiler compiler, String name, String indent)
protected Template getTemplate () {
// we compile our template lazily to avoid infinie recursion if a template includes
// itself (see issue #13)
if (_template == null) {
_template = _comp.loadTemplate(_name).indent(_indent);
Template t = _template;
if (t == null) {
// We cannot use synchronized or a CAS operation here since loadTemplate might be an IO call
// and virtual threads prefer regular locks.
lock.lock();
try {
if ((t = _template) == null) {
_template = t = _comp.loadTemplate(_name).indent(_indent);
}
} finally {
lock.unlock();
}
}
return _template;
return t;
}
protected IncludedTemplateSegment indent(String indent, boolean first, boolean last) {
// Indent this partial based on the spacing provided.
Expand All @@ -1084,7 +1096,8 @@ public String toString() {
protected final Compiler _comp;
protected final String _name;
private final String _indent;
private Template _template;
private final Lock lock = new ReentrantLock();
private volatile Template _template;
protected boolean _standalone = false;
}

Expand Down
69 changes: 69 additions & 0 deletions src/test/java/com/samskivert/mustache/PartialThreadSafeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.samskivert.mustache;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Test;

import com.samskivert.mustache.Mustache.TemplateLoader;

public class PartialThreadSafeTest {

@Test
public void testPartialThreadSafe() throws Exception {
long t = System.currentTimeMillis();
AtomicInteger loadCount = new AtomicInteger();
TemplateLoader loader = new TemplateLoader() {

@Override
public Reader getTemplate(String name) throws Exception {
if ("partial".equals(name)) {
loadCount.incrementAndGet();
TimeUnit.MILLISECONDS.sleep(20);
return new StringReader("Hello");
}
throw new IOException(name);
}
};

Template template = Mustache.compiler().withLoader(loader).compile("{{stuff}}\n\t{{> partial }}");
ExecutorService executor = Executors.newFixedThreadPool(64);
ConcurrentLinkedDeque<Exception> q = new ConcurrentLinkedDeque<>();

Map<String, Object> m = new HashMap<>();
m.put("stuff", "Foo");
for (int i = 100; i > 0; i--) {
int ii = i;
executor.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(ii % 10);
template.execute(m);
} catch (Exception e) {
q.add(e);
}
});
}
executor.shutdown();
executor.awaitTermination(10_000, TimeUnit.MILLISECONDS);
if (!q.isEmpty()) {
System.out.println(q);
}
assertTrue(q.isEmpty());
assertEquals(1, loadCount.get());
System.out.println(loadCount);
System.out.println(System.currentTimeMillis() - t);

}

}

0 comments on commit fe6ef9c

Please sign in to comment.