in src/main/java/org/apache/freemarker/onlinetester/services/FreeMarkerService.java [135:232]
public FreeMarkerServiceResponse executeTemplate(ExecuteTemplateArgs args)
throws RejectedExecutionException {
Objects.requireNonNull(templateExecutor, "templateExecutor was null - was postConstruct ever called?");
final ExecuteTempalteTask task = new ExecuteTempalteTask(args);
Future<FreeMarkerServiceResponse> future = templateExecutor.submit(task);
synchronized (task) {
while (!task.isTemplateExecutionStarted() && !task.isTaskEnded() && !future.isDone()) {
try {
task.wait(50); // Timeout is needed to periodically check future.isDone()
} catch (InterruptedException e) {
throw new FreeMarkerServiceException("Template execution task was interrupted.", e);
}
}
}
try {
return future.get(maxTemplateExecutionTime, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
throw new FreeMarkerServiceException("Template execution task unexpectedly failed", e.getCause());
} catch (InterruptedException e) {
throw new FreeMarkerServiceException("Template execution task was interrupted.", e);
} catch (TimeoutException e) {
// Exactly one interruption should be enough, and it should abort template processing pretty much
// immediately. But to be on the safe side we will interrupt in a loop, with a timeout.
final long abortionLoopStartTime = System.currentTimeMillis();
long timeLeft = ABORTION_LOOP_TIME_LIMIT;
boolean templateExecutionEnded = false;
do {
synchronized (task) {
Thread templateExecutorThread = task.getTemplateExecutorThread();
if (templateExecutorThread == null) {
templateExecutionEnded = true;
} else {
logger.debug("Trying to interrupt overly long template processing ({} ms left).", timeLeft);
FreeMarkerInternalsAccessor.interruptTemplateProcessing(templateExecutorThread);
}
} // sync
if (!templateExecutionEnded) {
try {
timeLeft = ABORTION_LOOP_TIME_LIMIT - (System.currentTimeMillis() - abortionLoopStartTime);
if (timeLeft > 0) {
Thread.sleep(ABORTION_LOOP_INTERRUPTION_DISTANCE);
}
} catch (InterruptedException eInt) {
logger.error("Template execution abortion loop was interrupted", eInt);
timeLeft = 0;
}
}
} while (!templateExecutionEnded && timeLeft > 0);
// If a slow operation didn't react to Thread.interrupt, we better risk this than allow
// the depletion of the thread pool:
if (!templateExecutionEnded) {
synchronized (task) {
Thread templateExecutorThread = task.getTemplateExecutorThread();
if (templateExecutorThread == null) {
templateExecutionEnded = true;
} else {
if (logger.isWarnEnabled()) {
logger.warn("Calling Thread.stop() on unresponsive long template processing, which didn't "
+ "respond to Template.interrupt() on time. Service state may will be inconsistent; "
+ "JVM restart recommended!\n"
+ "Template (quoted): " + StringUtil.jQuote(args.templateSourceCode));
}
templateExecutorThread.stop();
}
} // sync
try {
// We should now receive a result from the task, so that we don't have to die with HTTP 500
Thread.sleep(THREAD_STOP_EFFECT_WAIT_TIME);
synchronized (task) {
Thread templateExecutorThread = task.getTemplateExecutorThread();
if (templateExecutorThread == null) {
templateExecutionEnded = true;
}
} // sync
} catch (InterruptedException e2) {
// Just continue...
}
}
if (templateExecutionEnded) {
logger.debug("Long template processing has ended.");
try {
return future.get();
} catch (InterruptedException | ExecutionException e2) {
throw new FreeMarkerServiceException("Failed to get result from template executor task", e2);
}
} else {
throw new FreeMarkerServiceException(
"Couldn't stop long running template processing within " + ABORTION_LOOP_TIME_LIMIT
+ " ms. It's possibly stuck forever. Such problems can exhaust the executor pool. "
+ "Template (quoted): " + StringUtil.jQuote(args.templateSourceCode));
}
}
}