Skip to content
Draft
Show file tree
Hide file tree
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 @@ -7,7 +7,7 @@ public enum ExperimentalFeature {

private final String featureName;

private ExperimentalFeature(String featureName) {
ExperimentalFeature(String featureName) {
this.featureName = featureName;
}

Expand Down
27 changes: 27 additions & 0 deletions rune-runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<!-- Regular tests (with JIT) -->
<execution>
<id>default-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<excludedGroups>performance</excludedGroups>
</configuration>
</execution>
<!-- Performance tests (with -Xint) -->
<execution>
<id>performance-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<groups>performance</groups>
<argLine>-Xint</argLine>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.rosetta.model.lib.context;

import java.util.HashMap;
import java.util.Map;

/**
* Abstract base class for implementing {@link FunctionScope}.
* <p>
* Subclasses should override {@link #configure()} and call {@link #addOverride(Class, Class)}
* to define the class overrides for this scope.
* <p>
* Example:
* <pre>
* public class MyScope extends AbstractFunctionScope {
* protected void configure() {
* addOverride(BaseClass.class, OverrideClass.class);
* }
* }
* </pre>
*/
public abstract class AbstractFunctionScope implements FunctionScope {
private final Map<Class<?>, Class<?>> overrides = new HashMap<>();

public AbstractFunctionScope() {
configure();
}

/**
* Configures this scope by adding class overrides.
* <p>
* Subclasses must implement this method to define their overrides using {@link #addOverride(Class, Class)}.
*/
protected abstract void configure();

/**
* Adds a class override to this scope.
*
* @param clazz the original class
* @param override the override class (must be a subclass of the original class)
* @param <T> the type of the class
* @throws IllegalArgumentException if the override is not a subclass of the original class,
* or if the class already has an override defined
*/
protected <T> void addOverride(Class<T> clazz, Class<? extends T> override) {
if (!clazz.isAssignableFrom(override)) {
throw new IllegalArgumentException("Override class " + override + " must be a subclass of the original class " + clazz);
}
if (overrides.containsKey(clazz)) {
throw new IllegalArgumentException("Class " + clazz + " is already overridden by " + overrides.get(clazz));
}
overrides.put(clazz, override);
}

@Override
@SuppressWarnings("unchecked")
public <T> Class<? extends T> getOverride(Class<T> clazz) {
return (Class<? extends T>) overrides.getOrDefault(clazz, clazz);
}

@Override
public Map<Class<?>, Class<?>> getAllOverrides() {
return overrides;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.rosetta.model.lib.context;

import com.google.inject.TypeLiteral;

import javax.inject.Inject;
import javax.inject.Provider;

/**
* A provider that resolves instances based on the current function context and its scopes.
* <p>
* This provider can be injected into classes to enable scope-aware dependency resolution.
* Instead of receiving a fixed instance at injection time, the instance is resolved dynamically
* when {@link #get()} is called, taking into account any active scopes on the current thread.
* <p>
* Example usage:
* <pre>
* public class MyClass {
* {@literal @}Inject
* private ContextAwareProvider&lt;MyDependency&gt; dependencyProvider;
*
* public void doWork() {
* MyDependency dep = dependencyProvider.get(); // Resolved based on current scope
* // ... use dep
* }
* }
* </pre>
*
* @param <T> the type to provide
*/
public class ContextAwareProvider<T> implements Provider<T> {
private final TypeLiteral<T> type;
private final FunctionContext context;

@Inject
public ContextAwareProvider(TypeLiteral<T> type,
FunctionContext context) {
this.type = type;
this.context = context;
}

@Override
@SuppressWarnings("unchecked")
public T get() {
Class<? extends T> raw = (Class<? extends T>) type.getRawType();
return context.getInstance(raw);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.rosetta.model.lib.context;

import com.google.inject.ImplementedBy;

import java.util.function.Supplier;

/**
* Provides a context for function execution with scoped overrides.
*/
@ImplementedBy(FunctionContextImpl.class)
public interface FunctionContext {
/**
* Executes code within a {@link FunctionScope}.
*
* @param scopeClass the scope to use
* @param runnable the code to execute
*/
void runInScope(Class<? extends FunctionScope> scopeClass, Runnable runnable);

/**
* Executes code within a {@link FunctionScope} and returns the result.
*
* @param scopeClass the scope to use
* @param supplier the code to execute
* @param <T> the return type
* @return the result
*/
<T> T evaluateInScope(Class<? extends FunctionScope> scopeClass, Supplier<T> supplier);

/**
* Gets an instance of the specified class, applying any active scope overrides.
*
* @param clazz the class to instantiate
* @param <T> the type
* @return an instance of the class (or its override)
*/
<T> T getInstance(Class<T> clazz);

/**
* Creates a copy of the current thread's scope state for propagation to other threads.
* <p>
* Use this method to capture the current scope stack before spawning async tasks,
* then call {@link #setStateOfCurrentThread(FunctionContextState)} in the new thread.
*
* @return a copy of the current thread's scope state
*/
FunctionContextState copyStateOfCurrentThread();

/**
* Sets the current thread's scope state, typically after receiving it from another thread.
* <p>
* Use this method in a new thread to restore scope state that was captured
* via {@link #copyStateOfCurrentThread()} in a parent thread.
*
* @param state the scope state to set for the current thread
*/
void setStateOfCurrentThread(FunctionContextState state);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.rosetta.model.lib.context;

import com.google.inject.Injector;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.function.Supplier;

/**
* Implementation of {@link FunctionContext} that maintains a stack of scopes with cached resolved overrides.
* <p>
* This implementation optimizes {@link #getInstance(Class)} to O(1) time complexity (with respect to the depth of the scope stack)
* by maintaining a cache of resolved class overrides at each scope level. When a scope is entered, the cache is computed
* by applying that scope's overrides to the parent scope's cache. When a scope is exited, the parent's
* cache is automatically restored by popping the stack.
*/
@Singleton
public class FunctionContextImpl implements FunctionContext {
private final ThreadLocal<FunctionContextState> statePerThread = ThreadLocal.withInitial(FunctionContextState::empty);
private final Injector injector;

@Inject
public FunctionContextImpl(Injector injector) {
this.injector = injector;
}

@Override
public void runInScope(Class<? extends FunctionScope> scopeClass, Runnable runnable) {
if (!pushScope(scopeClass)) {
runnable.run();
return;
}
try {
runnable.run();
} finally {
popScope();
}
}

@Override
public <T> T evaluateInScope(Class<? extends FunctionScope> scopeClass, Supplier<T> supplier) {
if (!pushScope(scopeClass)) {
return supplier.get();
};
try {
return supplier.get();
} finally {
popScope();
}
}

@Override
public <T> T getInstance(Class<T> clazz) {
Class<? extends T> resolvedClass = statePerThread.get().getOverride(clazz);
return injector.getInstance(resolvedClass);
}

@Override
public FunctionContextState copyStateOfCurrentThread() {
return statePerThread.get().copy();
}

@Override
public void setStateOfCurrentThread(FunctionContextState state) {
statePerThread.set(state);
}

private boolean pushScope(Class<? extends FunctionScope> scopeClass) {
FunctionScope scope = injector.getInstance(scopeClass);
return statePerThread.get().pushScope(scope);
}

private void popScope() {
statePerThread.get().popScope();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.rosetta.model.lib.context;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

/**
* Encapsulates the per-thread state of a {@link FunctionContext}.
* <p>
* This class is separated from {@link FunctionContext} to enable explicit propagation of context
* state across thread boundaries. Instances can be copied via {@link #copy()} and transferred
* to other threads, allowing async tasks to inherit the parent thread's context.
* <p>
* Maintains a scope stack with cached resolved overrides for O(1) lookup performance.
*/
public class FunctionContextState {
private static class ScopeFrame {
final FunctionScope scope;
final Map<Class<?>, Class<?>> resolvedOverrides;

ScopeFrame(FunctionScope scope, Map<Class<?>, Class<?>> resolvedOverrides) {
this.scope = scope;
this.resolvedOverrides = resolvedOverrides;
}
}
private final Deque<ScopeFrame> scopeStack;

public static FunctionContextState empty() {
return new FunctionContextState();
}

private FunctionContextState(FunctionContextState otherState) {
this.scopeStack = new ArrayDeque<>(otherState.scopeStack);
}
private FunctionContextState() {
this.scopeStack = new ArrayDeque<>();
}

/**
* Creates a shallow copy of this state for propagation to another thread.
* <p>
* The scope stack is copied, so modifications in one thread won't affect the other.
*
* @return a copy of this state
*/
public FunctionContextState copy() {
return new FunctionContextState(this);
}

@SuppressWarnings("unchecked")
public <T> Class<? extends T> getOverride(Class<T> clazz) {
// O(1) cache lookup from the top of the stack
if (scopeStack.isEmpty()) {
return clazz;
}
Map<Class<?>, Class<?>> resolvedOverrides = scopeStack.peekLast().resolvedOverrides;
return (Class<? extends T>) resolvedOverrides.getOrDefault(clazz, clazz);
}

/**
* Pushes a new scope onto the stack, if the scope is different from the last.
* @param scope the scope to push
* @return true if the scope was pushed, false if the scope equals the top of the stack
*/
public boolean pushScope(FunctionScope scope) {
if (!scopeStack.isEmpty() && scopeStack.peekLast().scope.equals(scope)) {
return false;
}

// Get the current resolved overrides (from parent scope or empty)
Map<Class<?>, Class<?>> parentResolvedOverrides = scopeStack.isEmpty()
? new HashMap<>()
: scopeStack.peekLast().resolvedOverrides;

// Apply new scope's overrides to all classes in the parent overrides.
// This ensures that if class A was already overridden to B,
// and the new scope overrides B to C, then A will resolve to C.
Map<Class<?>, Class<?>> newResolvedOverrides = new HashMap<>();
for (Map.Entry<Class<?>, Class<?>> entry : parentResolvedOverrides.entrySet()) {
Class<?> baseClass = entry.getKey();
Class<?> currentOverride = entry.getValue();
Class<?> newOverride = scope.getOverride(currentOverride);
newResolvedOverrides.put(baseClass, newOverride);
}

// Also add any new overrides from this scope that aren't in the cache yet.
Map<Class<?>, Class<?>> scopeOverrides = scope.getAllOverrides();
for (Map.Entry<Class<?>, Class<?>> entry : scopeOverrides.entrySet()) {
Class<?> baseClass = entry.getKey();
if (!newResolvedOverrides.containsKey(baseClass)) {
newResolvedOverrides.put(baseClass, entry.getValue());
}
}

scopeStack.addLast(new ScopeFrame(scope, newResolvedOverrides));
return true;
}

public void popScope() {
scopeStack.removeLast();
}
}
Loading
Loading