Skip to content

Commit

Permalink
Fixed bug where nested "parallel" tags would fail with a casting error
Browse files Browse the repository at this point in the history
  • Loading branch information
mbosecke committed Oct 28, 2014
1 parent ab3e95c commit 60d2efc
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,17 @@ public void render(final PebbleTemplateImpl self, Writer writer, final Evaluatio

final EvaluationContext contextCopy = context.deepCopy(self);

final Writer stringWriter = new StringWriter();
final StringWriter newStringWriter = new StringWriter();
final Writer newFutureWriter = new FutureWriter(newStringWriter);

Future<String> future = es.submit(new Callable<String>() {

@Override
public String call() throws PebbleException, IOException {
body.render(self, stringWriter, contextCopy);
return stringWriter.toString();
body.render(self, newFutureWriter, contextCopy);
newFutureWriter.flush();
newFutureWriter.close();
return newStringWriter.toString();
}
});
((FutureWriter) writer).enqueue(future);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public void evaluate(Writer writer, Map<String, Object> map, Locale locale) thro
*/
public void evaluate(Writer writer, EvaluationContext context) throws PebbleException, IOException {
if (context.getExecutorService() != null) {
writer = new FutureWriter(writer, context.getExecutorService());
writer = new FutureWriter(writer);
}
buildContent(writer, context);
writer.flush();
Expand Down
61 changes: 39 additions & 22 deletions src/main/java/com/mitchellbosecke/pebble/utils/FutureWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,33 @@

import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.LinkedList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* A Writer that will wrap around the user-provided writer if the user also
* provided an ExecutorService to the main PebbleEngine. A FutureWriter is
* capable of handling Futures that will return a string.
*
* It is not thread safe but that is okay. Each thread will have it's own
* writer, provided by the "parallel" node; i.e. they will never share writers.
*
* @author Mitchell
*
*/
public class FutureWriter extends Writer {

private final ConcurrentLinkedQueue<Future<String>> orderedFutures = new ConcurrentLinkedQueue<>();

private final ExecutorService es;
private final LinkedList<Future<String>> orderedFutures = new LinkedList<>();

private final Writer internalWriter;

private boolean closed = false;

public FutureWriter(Writer writer, ExecutorService es) {
public FutureWriter(Writer writer) {
this.internalWriter = writer;
this.es = es;
}

public void enqueue(Future<String> future) throws IOException {
Expand All @@ -50,26 +49,44 @@ public void enqueue(Future<String> future) throws IOException {
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {

/*
* We need to make a defensive copy of the character buffer because this
* class will continue to reuse the same buffer with future invocations
* of this write method.
*/
final char[] finalCharacterBuffer = Arrays.copyOf(cbuf, len);
if (closed) {
throw new IOException("Writer is closed");
}

final String result = new String(cbuf, off, len);

if (orderedFutures.isEmpty()) {
internalWriter.write(finalCharacterBuffer, off, len);
internalWriter.write(result);
} else {
Future<String> future = es.submit(new Callable<String>() {
Future<String> future = new Future<String>() {

@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}

@Override
public boolean isCancelled() {
return false;
}

@Override
public boolean isDone() {
return true;
}

@Override
public String get() throws InterruptedException, ExecutionException {
return result;
}

@Override
public String call() throws Exception {
char[] chars = new char[len];
System.arraycopy(finalCharacterBuffer, off, chars, 0, len);
return new String(chars);
public String get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
TimeoutException {
return null;
}

});
};

orderedFutures.add(future);
}
Expand Down
38 changes: 38 additions & 0 deletions src/test/java/com/mitchellbosecke/pebble/CoreTagsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,34 @@ public void testParallelTagWhileEvaluationContextIsChanging() throws PebbleExcep
assertEquals("0123456789", writer.toString());
}

/**
* Nested parallel tags were throwing an error.
*
* @throws PebbleException
* @throws IOException
*/
@Test(timeout = 500)
public void testNestedParallel() throws PebbleException, IOException {
Loader loader = new StringLoader();
PebbleEngine pebble = new PebbleEngine(loader);
pebble.setExecutorService(Executors.newCachedThreadPool());
// @formatter:off
String source = "{% parallel %}"
+ "{% parallel %}{{ slowObject.fourth() }}{% endparallel %} {% parallel %}{{ slowObject.first() }}{% endparallel %} "
+ "{% parallel %}{{ slowObject.fourth() }}{% endparallel %} {% parallel %}{{ slowObject.first() }}{% endparallel %}"
+ "{% endparallel %}";
// @formatter:on
PebbleTemplate template = pebble.getTemplate(source);

Writer writer = new StringWriter();
Map<String, Object> context = new HashMap<>();

context.put("slowObject", new SlowObject());
template.evaluate(writer, context);

assertEquals("fourth first fourth first", writer.toString());
}

@Test(timeout = 300)
public void testIncludeWithinParallelTag() throws PebbleException, IOException {
PebbleEngine pebble = new PebbleEngine();
Expand Down Expand Up @@ -672,6 +700,16 @@ public String third() {
}
return "third";
}

public String fourth() {
try {
Thread.sleep(400);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "fourth";
}
}

public class User {
Expand Down

0 comments on commit 60d2efc

Please sign in to comment.