Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.9.x] [MNG-7401] [MNG-7474] Keep a single maven session and fix session scope #743

Merged
merged 2 commits into from
Aug 23, 2022
Merged
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 @@ -54,7 +54,7 @@ public class MavenSession

private Properties executionProperties;

private MavenProject currentProject;
private ThreadLocal<MavenProject> currentProject = new ThreadLocal<>();

/**
* These projects have already been topologically sorted in the {@link org.apache.maven.Maven} component before
Expand Down Expand Up @@ -83,14 +83,15 @@ public void setProjects( List<MavenProject> projects )
{
if ( !projects.isEmpty() )
{
this.currentProject = projects.get( 0 );
MavenProject first = projects.get( 0 );
this.currentProject = ThreadLocal.withInitial( () -> first );
this.topLevelProject =
projects.stream().filter( project -> project.isExecutionRoot() ).findFirst()
.orElse( currentProject );
.orElse( first );
}
else
{
this.currentProject = null;
this.currentProject = new ThreadLocal<>();
this.topLevelProject = null;
}
this.projects = projects;
Expand Down Expand Up @@ -151,12 +152,12 @@ public MavenExecutionRequest getRequest()

public void setCurrentProject( MavenProject currentProject )
{
this.currentProject = currentProject;
this.currentProject.set( currentProject );
}

public MavenProject getCurrentProject()
{
return currentProject;
return currentProject.get();
}

public ProjectBuildingRequest getProjectBuildingRequest()
Expand Down Expand Up @@ -233,7 +234,12 @@ public MavenSession clone()
{
try
{
return (MavenSession) super.clone();
MavenSession clone = (MavenSession) super.clone();
// the default must become the current project of the thread that clones this
MavenProject current = getCurrentProject();
// we replace the thread local of the clone to prevent write through and enforce the new default value
clone.currentProject = ThreadLocal.withInitial( () -> current );
return clone;
}
catch ( CloneNotSupportedException e )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ public ProjectBuildList calculateProjectBuilds( MavenSession session, List<TaskS
for ( MavenProject project : projects )
{
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
MavenProject currentProject = session.getCurrentProject();
try
{
BuilderCommon.attachToThread( project ); // Not totally sure if this is needed for anything
MavenSession copiedSession = session.clone();
copiedSession.setCurrentProject( project );
projectBuilds.add( new ProjectSegment( project, taskSegment, copiedSession ) );
session.setCurrentProject( project );
projectBuilds.add( new ProjectSegment( project, taskSegment, session ) );
}
finally
{
session.setCurrentProject( currentProject );
Thread.currentThread().setContextClassLoader( tccl );
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.apache.maven.session.scope.internal.SessionScope;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;

Expand Down Expand Up @@ -66,9 +65,6 @@ public class LifecycleModuleBuilder
@Requirement
private List<ProjectExecutionListener> projectExecutionListeners;

@Requirement
private SessionScope sessionScope;

public void setProjectExecutionListeners( final List<ProjectExecutionListener> listeners )
{
this.projectExecutionListeners = listeners;
Expand All @@ -88,10 +84,6 @@ public void buildProject( MavenSession session, MavenSession rootSession, Reacto

long buildStartTime = System.currentTimeMillis();

// session may be different from rootSession seeded in DefaultMaven
// explicitly seed the right session here to make sure it is used by Guice
sessionScope.enter( reactorContext.getSessionScopeMemento() );
sessionScope.seed( MavenSession.class, session );
try
{

Expand Down Expand Up @@ -145,8 +137,6 @@ public void buildProject( MavenSession session, MavenSession rootSession, Reacto
}
finally
{
sessionScope.exit();

session.setCurrentProject( null );

Thread.currentThread().setContextClassLoader( reactorContext.getOriginalContextClassLoader() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ public void execute( MavenSession session )

ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus( session.getProjectDependencyGraph() );
reactorContext =
new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus,
sessionScope.memento() );
reactorContext = new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus );

String builderId = session.getRequest().getBuilderId();
Builder builder = builders.get( builderId );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
*/

import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.session.scope.internal.SessionScope;

/**
* Context that is fixed for the entire reactor build.
Expand All @@ -40,17 +39,13 @@ public class ReactorContext

private final ReactorBuildStatus reactorBuildStatus;

private final SessionScope.Memento sessionScope;

public ReactorContext( MavenExecutionResult result, ProjectIndex projectIndex,
ClassLoader originalContextClassLoader, ReactorBuildStatus reactorBuildStatus,
SessionScope.Memento sessionScope )
ClassLoader originalContextClassLoader, ReactorBuildStatus reactorBuildStatus )
{
this.result = result;
this.projectIndex = projectIndex;
this.originalContextClassLoader = originalContextClassLoader;
this.reactorBuildStatus = reactorBuildStatus;
this.sessionScope = sessionScope;
}

public ReactorBuildStatus getReactorBuildStatus()
Expand All @@ -73,11 +68,4 @@ public ClassLoader getOriginalContextClassLoader()
return originalContextClassLoader;
}

/**
* @since 3.3.0
*/
public SessionScope.Memento getSessionScopeMemento()
{
return sessionScope;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,151 +19,129 @@
* under the License.
*/

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.util.Providers;

/**
* SessionScope
*/
public class SessionScope
implements Scope
{
/**
* @since 3.3.0
*/
public static class Memento
{
final Map<Key<?>, Provider<?>> seeded;

Memento( final Map<Key<?>, Provider<?>> seeded )
{
this.seeded = Collections.unmodifiableMap( new HashMap<>( seeded ) );
}
}

private static final Provider<Object> SEEDED_KEY_PROVIDER = new Provider<Object>()
private static final Provider<Object> SEEDED_KEY_PROVIDER = () ->
{
public Object get()
{
throw new IllegalStateException();
}
throw new IllegalStateException();
};

/**
* ScopeState
*/
private static final class ScopeState
protected static final class ScopeState
{
private final Map<Key<?>, Provider<?>> seeded = new HashMap<>();
private final Map<Key<?>, CachingProvider<?>> provided = new ConcurrentHashMap<>();

private final Map<Key<?>, Object> provided = new HashMap<>();
}
public <T> void seed( Class<T> clazz, Provider<T> value )
{
provided.put( Key.get( clazz ), new CachingProvider<>( value ) );
}

private final ThreadLocal<LinkedList<ScopeState>> values = new ThreadLocal<>();
@SuppressWarnings( "unchecked" )
public <T> Provider<T> scope( Key<T> key, Provider<T> unscoped )
{
Provider<?> provider = provided.computeIfAbsent( key, k -> new CachingProvider<>( unscoped ) );
return ( Provider<T> ) provider;
}

public void enter()
{
LinkedList<ScopeState> stack = values.get();
if ( stack == null )
public Collection<CachingProvider<?>> providers()
{
stack = new LinkedList<>();
values.set( stack );
return provided.values();
}
stack.addFirst( new ScopeState() );
}

/**
* @since 3.3.0
*/
public void enter( Memento memento )
private final List<ScopeState> values = new CopyOnWriteArrayList<>();

public void enter()
{
enter();
getScopeState().seeded.putAll( memento.seeded );
values.add( 0, new ScopeState() );
}

private ScopeState getScopeState()
protected ScopeState getScopeState()
{
LinkedList<ScopeState> stack = values.get();
if ( stack == null || stack.isEmpty() )
if ( values.isEmpty() )
{
throw new IllegalStateException();
throw new OutOfScopeException( "Cannot access session scope outside of a scoping block" );
}
return stack.getFirst();
return values.get( 0 );
}

public void exit()
{
final LinkedList<ScopeState> stack = values.get();
if ( stack == null || stack.isEmpty() )
if ( values.isEmpty() )
{
throw new IllegalStateException();
}
stack.removeFirst();
if ( stack.isEmpty() )
{
values.remove();
}
}

/**
* @since 3.3.0
*/
public Memento memento()
{
LinkedList<ScopeState> stack = values.get();
return new Memento( stack != null ? stack.getFirst().seeded : Collections.<Key<?>, Provider<?>>emptyMap() );
values.remove( 0 );
}

public <T> void seed( Class<T> clazz, Provider<T> value )
{
getScopeState().seeded.put( Key.get( clazz ), value );
getScopeState().seed( clazz, value );
}

public <T> void seed( Class<T> clazz, final T value )
{
getScopeState().seeded.put( Key.get( clazz ), Providers.of( value ) );
seed( clazz, ( Provider<T> ) () -> value );
}

public <T> Provider<T> scope( final Key<T> key, final Provider<T> unscoped )
{
return new Provider<T>()
{
@SuppressWarnings( "unchecked" )
public T get()
{
LinkedList<ScopeState> stack = values.get();
if ( stack == null || stack.isEmpty() )
{
throw new OutOfScopeException( "Cannot access " + key + " outside of a scoping block" );
}
// Lazy evaluating provider
return () -> getScopeState().scope( key, unscoped ).get();
}

ScopeState state = stack.getFirst();
/**
* A provider wrapping an existing provider with a cache
* @param <T> the provided type
*/
protected static class CachingProvider<T> implements Provider<T>
{
private final Provider<T> provider;
private volatile T value;

Provider<?> seeded = state.seeded.get( key );
CachingProvider( Provider<T> provider )
{
this.provider = provider;
}

if ( seeded != null )
{
return (T) seeded.get();
}
public T value()
{
return value;
}

T provided = (T) state.provided.get( key );
if ( provided == null && unscoped != null )
@Override
public T get()
{
if ( value == null )
{
synchronized ( this )
{
provided = unscoped.get();
state.provided.put( key, provided );
if ( value == null )
{
value = provider.get();
}
}

return provided;
}
};
return value;
}
}

@SuppressWarnings( { "unchecked" } )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public void testCalculateProjectBuilds()
assertEquals( "Stub data contains 6 items", 6, segments.size() );
final ProjectSegment build = segments.get( 0 );
assertNotNull( build );

for ( ProjectSegment segment : segments )
{
assertSame( session, segment.getSession() );
}
}

private static LifecycleTaskSegmentCalculator getTaskSegmentCalculator()
Expand Down
Loading