package io.trane.future;
import java.util.Optional;
import java.util.function.Supplier;
/**
* `Local`s are a mechanism similar to `ThreadLocal` but for
* asynchronous computations.
*
* It is not possible to use `ThreadLocal`s with `Future` because
* the data it holds become invalid when the computation reaches
* an asynchronous boundary. The thread returns to its thread pool
* to execute other computations, and the continuations are performed
* by the thread that sets the result of the `Promise`.
*
* `Local`s are a mechanism similar to `ThreadLocal`, but it has a
* more flexible scope. For example, this code sets the `UserSession`
* local when a request is processed:
*
* ```java
* public class UserSession {
* public static final Local<UserSession> local= Local.apply();
* // UserSession impl
* }
*
* public class MyService {
* public Future<List<Tweet>> getTweetsEndpoint(Request request) {
* UserSession.local.let(
* request.getSession(),
* () -> tweetRepo.get(request.getUserId()));
* }
* }
* ```
*
* Note that the `let` method is used to define the local value, execute
* the function defined by the second parameter, and then set the local
* to its previous value. It is a convenient method to avoid having to
* set and clear the value manually:
*
* ```java
* public class MyService {
* public Future<List<Tweet>> getTweetsEndpoint(Request request) {
* final Optional<UserSessuib> saved = UserSession.local.get();
* UserSession.local.set(Optional.of(request.getSession()));
* try {
* return tweetRepo.get(request.getUserId());
* } finally {
* UserSession.local.set(saved);
* }
* }
* }
* ```
*
* At any point of the of the request processing, even after asynchronous
* boundaries, the user session can be accessed. For instance, let's say
* that `tweetRepo` uses a `TweetStorage` that routes the query to a specific
* database shard based on the user that is requesting the tweet:
*
* ```java
* public class TweetStorage {
* public Future<RawTweet> getTweet(long tweetId) {
* databaseFor(UserSession.local.get().getUserId()).getTweet(tweetId);
* }
* }
* ```
*
* This feature is implemented with a `ThreadLocal` that is saved at the
* point of an asynchronous boundary as a `Promise` field and is restored
* when the `Promise` is satisfied, flushing its continuations with the
* original `ThreadLocal` contents.
*
* Note: This feature does not have the same behavior as Twitter's `Local`.
* The `ThreadLocal` state is captured when a `Promise` is created, whereas
* Twitter's implementation captures the state only when a `Promise`
* continuation is created (for instance, `map` is called on a `Promise`
* instance). In practice, most `Promise` creations are followed by a
* continuation, so the behavior is usually the same.
*
* @param <T>
* the type of the local value.
*/
public final class Local<T> {
protected static final Optional<?>[] EMPTY = new Optional<?>[0];
private static ThreadLocal<Optional<?>[]> threadLocal = null;
private static int size = 0;
/**
* Creates a new local value.
*
* @param <T> the type of the local value.
* @return the local value
*/
public static final <T> Local<T> apply() {
return new Local<>();
}
protected static final Optional<?>[] save() {
if (threadLocal == null)
return EMPTY;
else {
Optional<?>[] state = threadLocal.get();
if (state == null)
return EMPTY;
else
return state;
}
}
protected static final void restore(final Optional<?>[] saved) {
if (threadLocal != null)
threadLocal.set(saved);
}
private static final synchronized int newPosition() {
if (threadLocal == null)
threadLocal = new ThreadLocal<>();
return size++;
}
private final int position = newPosition();
private Local() {
}
/**
* Updates the local with the provided value.
*
* @param value value to set
*/
public final void update(final T value) {
set(Optional.of(value));
}
/**
* Sets the value of the local. It's similar to update but receives an
* optional value.
*
* @param opt optional value to set.
*/
public final void set(final Optional<T> opt) {
Optional<?>[] ctx = threadLocal.get();
if (ctx == null)
ctx = new Optional<?>[size];
else {
final Optional<?>[] oldCtx = ctx;
ctx = new Optional<?>[size];
System.arraycopy(oldCtx, 0, ctx, 0, oldCtx.length);
}
ctx[position] = opt;
threadLocal.set(ctx);
}
/**
* Gets the current value of the local.
*
* @return the local value
*/
@SuppressWarnings("unchecked")
public final Optional<T> get() {
final Optional<?>[] ctx = threadLocal.get();
if (ctx == null || ctx.length <= position)
return Optional.empty();
final Optional<?> v = ctx[position];
if (v == null)
return Optional.empty();
else
return (Optional<T>) v;
}
/**
* Executes the supplier with the provided value and then rolls back to the
* previous local value.
*
* @param value value to be set.
* @param s supplier to be executed with the provided value.
* @return the result of the supplier.
*/
public final <U> U let(final T value, final Supplier<U> s) {
final Optional<T> saved = get();
set(Optional.of(value));
try {
return s.get();
} finally {
set(saved);
}
}
}