Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@
*/
package org.tomitribe.microscoped.core;

import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

class Scope<Key> {
private final Instance<?> NOTHING = new Instance<>(null, null, null);

private final Map<Contextual<?>, Instance> instances = new ConcurrentHashMap<>();
import javax.enterprise.context.ContextNotActiveException;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;

class Scope<Key> {
private final Map<Contextual<?>, CompletableFuture<Instance>> instances = new ConcurrentHashMap<>();
private volatile boolean closed = false;
private final Key key;

public Scope(final Key key) {
Expand All @@ -43,7 +46,37 @@ public Key getKey() {
* @return existing or newly created bean instance, never null
*/
public <T> T get(final Contextual<T> contextual, final CreationalContext<T> creationalContext) {
return (T) instances.computeIfAbsent(contextual, c -> new Instance<>(contextual, creationalContext)).get();
if (closed) {
throw new ContextNotActiveException(String.format("Context not active [key=%s, contextual=%s, creationalContext=%s]",
String.valueOf(key),
(contextual instanceof Bean) ? ((Bean<?>) contextual).getBeanClass().getName() : String.valueOf(contextual),
String.valueOf(creationalContext)));
} else {
CompletableFuture<Instance> future = instances.get(contextual);
if (future == null) {
final CompletableFuture<Instance> newFuture = new CompletableFuture<>();
// Atomically place the new future in the map.
future = instances.putIfAbsent(contextual, newFuture);
if (future == null) {
// We won the race. Our future is now in the map. We are responsible for creating the bean.
future = newFuture;
try {
// The creation logic is now safely executed by only one thread.
final Instance<T> createdInstance = new Instance<>(contextual, creationalContext);
future.complete(createdInstance); // Publish the result for other threads.
} catch (final Throwable e) {
// If creation fails, complete the future exceptionally and remove it from the map
// so that subsequent requests can try again.
future.completeExceptionally(e);
instances.remove(contextual, future);
// Re-throw the original exception.
throw e;
}
}
}
// All threads (the winner and the waiters) wait here for the result.
return fastWait(future);
}
}

/**
Expand All @@ -53,29 +86,66 @@ public <T> T get(final Contextual<T> contextual, final CreationalContext<T> crea
* @return existing the bean instance or null
*/
public <T> T get(final Contextual<T> contextual) {
return (T) instances.getOrDefault(contextual, NOTHING).get();
if (closed) {
throw new ContextNotActiveException(String.format("Context not active [key=%s, contextual=%s]", String.valueOf(key),
(contextual instanceof Bean) ? ((Bean<?>) contextual).getBeanClass().getName() : String.valueOf(contextual)));
} else {
final CompletableFuture<Instance> future = instances.get(contextual);
return fastWait(future);
}
}

private static <T> T fastWait(final CompletableFuture<Instance> future) throws Error {
final T value;
if (future == null) {
value = null;
} else if (future.isDone() && !future.isCompletedExceptionally()) {
// Completed normally: read the result without join()
final Instance<T> instance = future.getNow(null); // never null in this branch
value = instance.get();
} else {
try {
final Instance<T> instance = future.get();
value = instance.get();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} catch (final ExecutionException ee) {
final Throwable cause = ee.getCause() != null ? ee.getCause() : ee;
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
throw new RuntimeException(cause);
}
}
}
return value;
}

/**
* Destroy all the instances in this scope
*/
public void destroy() {
// TODO We really should ensure no more instances can be added during or after this
instances.values().stream().forEach(Instance::destroy);
closed = true;
instances.values().forEach((final CompletableFuture<Instance> future) -> {
// There's a small chance
if (!future.isCompletedExceptionally()) {
future.join().destroy();
}
Comment thread
exabrial marked this conversation as resolved.
});
instances.clear();
}

private class Instance<T> {
private static class Instance<T> {
private final T instance;
private final CreationalContext<T> creationalContext;
private final Contextual<T> contextual;

public Instance(final Contextual<T> contextual, final CreationalContext<T> creationalContext) {

this(contextual, creationalContext, contextual.create(creationalContext));
}

public Instance(Contextual<T> contextual, CreationalContext<T> creationalContext, T instance) {
public Instance(final Contextual<T> contextual, final CreationalContext<T> creationalContext, final T instance) {
this.instance = instance;
this.creationalContext = creationalContext;
this.contextual = contextual;
Expand All @@ -86,7 +156,9 @@ public T get() {
}

public void destroy() {
contextual.destroy(instance, creationalContext);
if (contextual != null) {
contextual.destroy(instance, creationalContext);
}
}
}
}