package io.trane.future;
import java.util.ArrayList;
import java.util.Optional;
import java.util.function.Supplier;
/**
* `Tailrec` e ensures that recursive futures are stack-safe.
*
* Given the optimization that this library implements to
* avoid thread context switch, compositions are not
* stack-safe by default. It is necessary to wrap recursive
* computations with a `Tailrec` call:
*
* ```java
* public Future<Integer> factorial(Integer i) {
* Tailrec.apply(() ->
* if (i ==0) return Future.value(1);
* else factorial(i - 1).map(j -> i * j);
* )
* }
* ```
*
* This is just an example, there's no reason to use `Future`s
* to implement a factorial function. Requiring the `Tailrec`
* call for recursive computations is a reasonable compromise
* since recursive futures are uncommon.
*
* Even though the computation is wrapped by `Tailrec`, the
* execution still leverages the synchronous execution
* optimizations in batches. It executes the composition
* synchronously until it reaches the batch size and then uses
* a `Promise` to unwind the stack and then run the next batch.
*
* The default batch size is defined by the system property
* "io.trane.future.defaultBatchSize", which is `512` by default.
* Alternatively, it is possible to set the batch size when
* calling `Tailrec.apply`:
*
* ```java
* public Future<Integer> factorial(Integer i) {
* Tailrec.apply(1024, () ->
* if (i ==0) return Future.value(1);
* else factorial(i - 1).map(j -> i * j);
* )
* }
* ```
*
* Note that the first parameter defines the batch size as
* `1024`. Typically, the users do not need to tune this
* parameter unless a `StackOverflowException` is thrown or
* the user wants to increase the batch size for performance
* reasons. Larger batches tend to improve performance but
* increase the risk of a `StackOverflowException`.
*/
public final class Tailrec {
private static final int DEFAULT_BATCH_SIZE = Optional
.ofNullable(System.getProperty("io.trane.future.defaultBatchSize")).map(Integer::parseInt).orElse(512);
private static final ThreadLocal<Tailrec> local = new ThreadLocal<Tailrec>() {
@Override
public Tailrec initialValue() {
return new Tailrec();
}
};
private ArrayList<Runnable> tasks = null;
private int syncPermits = 0;
private boolean running = false;
private final boolean runSync() {
return syncPermits-- > 0;
}
private final void submit(final Runnable r, final int batchSize) {
syncPermits = batchSize;
tasks = new ArrayList<>(1);
tasks.add(r);
if (!running) {
run();
syncPermits = 0;
}
}
private final void run() {
running = true;
while (tasks != null) {
final ArrayList<Runnable> pending = tasks;
tasks = null;
for (int i = 0; i < pending.size(); i++)
pending.get(i).run();
}
running = false;
}
/**
* Runs the recursive future using the default batch size.
*
* @param sup the supplier to be called on each recursion.
* @return the stack-safe recursive future.
*/
public static final <T> Future<T> apply(final Supplier<Future<T>> sup) {
return apply(DEFAULT_BATCH_SIZE, sup);
}
/**
* Runs the recursive future using the custom batch size.
*
* @param batchSize the custom batch size.
* @param sup the supplier to be called on each recursion.
* @return the stack-safe recursive future.
*/
public static final <T> Future<T> apply(final int batchSize, final Supplier<Future<T>> sup) {
final Tailrec scheduler = local.get();
if (scheduler.runSync())
return sup.get();
else {
final TailrecPromise<T> p = new TailrecPromise<>(sup);
scheduler.submit(p, batchSize);
return p;
}
}
}
final class TailrecPromise<T> extends Promise<T> implements Runnable {
private final Supplier<Future<T>> sup;
protected TailrecPromise(final Supplier<Future<T>> sup) {
super();
this.sup = sup;
}
@Override
public final void run() {
become(sup.get());
}
}